HTML代码手搓2D绘图软件

📅 2026/7/23 15:57:24 👁️ 阅读次数 📝 编程学习
HTML代码手搓2D绘图软件

禁止任何转载商用

这是简易2D绘图软件HTML代码。它是一款基于Canvas的矢量绘图工具,支持绘制直线、矩形和圆形。

```html

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>简易2D绘图软件</title>

<style>

* {

box-sizing: border-box;

user-select: none;

}

body {

background: #2b2b3a;

display: flex;

justify-content: center;

align-items: center;

min-height: 100vh;

margin: 0;

font-family: 'Segoe UI', Roboto, system-ui, sans-serif;

}

.draw-app {

background: #1e1e2a;

border-radius: 28px;

padding: 24px 28px 30px 28px;

box-shadow: 0 18px 30px rgba(0,0,0,0.6);

display: flex;

flex-direction: column;

align-items: center;

}

.toolbar {

display: flex;

flex-wrap: wrap;

align-items: center;

gap: 10px 14px;

background: #2d2d40;

padding: 12px 22px;

border-radius: 60px;

margin-bottom: 20px;

box-shadow: inset 0 2px 5px rgba(0,0,0,0.4);

}

.tool-group {

display: flex;

gap: 6px;

align-items: center;

}

.tool-btn {

background: #3d3d55;

border: none;

color: #d0d0e0;

font-size: 15px;

font-weight: 500;

padding: 8px 16px;

border-radius: 40px;

cursor: pointer;

transition: 0.15s;

letter-spacing: 0.3px;

box-shadow: 0 2px 3px rgba(0,0,0,0.3);

min-width: 58px;

}

.tool-btn:hover {

background: #5a5a7a;

color: white;

transform: scale(1.02);

}

.tool-btn.active {

background: #7f7fb0;

color: white;

box-shadow: 0 0 0 2px #b8b8f0;

}

.color-picker-wrap {

display: flex;

align-items: center;

gap: 10px;

background: #2a2a3e;

padding: 4px 14px 4px 10px;

border-radius: 40px;

}

.color-picker-wrap label {

color: #c8c8e0;

font-size: 14px;

font-weight: 400;

}

input[type="color"] {

width: 34px;

height: 34px;

border: 2px solid #5a5a7a;

border-radius: 30px;

background: transparent;

cursor: pointer;

padding: 2px;

}

input[type="color"]::-webkit-color-swatch-wrapper {

padding: 0;

}

input[type="color"]::-webkit-color-swatch {

border: none;

border-radius: 30px;

}

.clear-btn {

background: #4d3f4a;

color: #f0d0d0;

border: none;

padding: 8px 20px;

border-radius: 40px;

font-weight: 500;

font-size: 14px;

cursor: pointer;

transition: 0.15s;

box-shadow: 0 2px 3px rgba(0,0,0,0.3);

}

.clear-btn:hover {

background: #6f4f5a;

color: white;

}

.canvas-wrapper {

background: #fcf9f2;

border-radius: 22px;

padding: 8px;

box-shadow: inset 0 0 0 1px #4a4a5e, 0 12px 24px rgba(0,0,0,0.5);

}

canvas {

display: block;

width: 720px;

height: 480px;

border-radius: 16px;

background: #ffffff;

cursor: crosshair;

box-shadow: 0 0 0 1px #8a8aa0;

}

.status-bar {

margin-top: 14px;

color: #a8a8c0;

font-size: 14px;

background: #262636;

padding: 6px 24px;

border-radius: 60px;

letter-spacing: 0.3px;

box-shadow: inset 0 1px 4px #0f0f18;

}

.status-bar span {

color: #d4d4f0;

font-weight: 500;

}

</style>

</head>

<body>

<div class="draw-app">

<div class="toolbar">

<div class="tool-group">

<button class="tool-btn active" data-tool="line">📐 直线</button>

<button class="tool-btn" data-tool="rect">▭ 矩形</button>

<button class="tool-btn" data-tool="circle">⬤ 圆形</button>

</div>

<div class="color-picker-wrap">

<label for="strokeColor">🖌️ 颜色</label>

<input type="color" id="strokeColor" value="#1f3a6b">

</div>

<button class="clear-btn" id="clearCanvas">🗑️ 清空</button>

</div>

<div class="canvas-wrapper">

<canvas id="drawCanvas" width="720" height="480"></canvas>

</div>

<div class="status-bar">

<span id="toolStatus">直线</span> · 拖拽绘制 · 点击颜色可更换

</div>

</div>

<script>

(function() {

const canvas = document.getElementById('drawCanvas');

const ctx = canvas.getContext('2d');

const toolStatus = document.getElementById('toolStatus');

// ---------- 状态 ----------

let currentTool = 'line'; // 'line', 'rect', 'circle'

let strokeColor = '#1f3a6b';

let isDrawing = false;

let startX = 0, startY = 0;

let currentX = 0, currentY = 0;

// 存储所有已绘制的图形 (用于重绘)

let shapes = [];

// ---------- 辅助函数 ----------

function getCanvasCoords(e) {

const rect = canvas.getBoundingClientRect();

const scaleX = canvas.width / rect.width; // canvas物理尺寸与CSS比例

const scaleY = canvas.height / rect.height;

const clientX = e.clientX || e.touches?.[0]?.clientX || 0;

const clientY = e.clientY || e.touches?.[0]?.clientY || 0;

return {

x: (clientX - rect.left) * scaleX,

y: (clientY - rect.top) * scaleY

};

}

// 重绘所有图形 (包括当前正在绘制的预览)

function redraw(includeTemp = true) {

ctx.clearRect(0, 0, canvas.width, canvas.height);

// 绘制所有已保存的图形

for (let s of shapes) {

ctx.beginPath();

ctx.strokeStyle = s.color;

ctx.lineWidth = 2.5;

ctx.lineJoin = 'round';

ctx.lineCap = 'round';

if (s.type === 'line') {

ctx.moveTo(s.x1, s.y1);

ctx.lineTo(s.x2, s.y2);

ctx.stroke();

} else if (s.type === 'rect') {

const x = Math.min(s.x1, s.x2);

const y = Math.min(s.y1, s.y2);

const w = Math.abs(s.x2 - s.x1);

const h = Math.abs(s.y2 - s.y1);

ctx.strokeRect(x, y, w, h);

} else if (s.type === 'circle') {

const cx = (s.x1 + s.x2) / 2;

const cy = (s.y1 + s.y2) / 2;

const rx = Math.abs(s.x2 - s.x1) / 2;

const ry = Math.abs(s.y2 - s.y1) / 2;

const radius = Math.max(rx, ry); // 使用最大半径,更自然

ctx.ellipse(cx, cy, radius, radius, 0, 0, Math.PI * 2);

ctx.stroke();

}

}

// 绘制当前临时图形 (拖拽中)

if (includeTemp && isDrawing) {

ctx.beginPath();

ctx.strokeStyle = strokeColor;

ctx.lineWidth = 2.5;

ctx.lineJoin = 'round';

ctx.lineCap = 'round';

const temp = {

type: currentTool,

x1: startX, y1: startY,

x2: currentX, y2: currentY,

color: strokeColor

};

if (temp.type === 'line') {

ctx.moveTo(temp.x1, temp.y1);

ctx.lineTo(temp.x2, temp.y2);

ctx.stroke();

} else if (temp.type === 'rect') {

const x = Math.min(temp.x1, temp.x2);

const y = Math.min(temp.y1, temp.y2);

const w = Math.abs(temp.x2 - temp.x1);

const h = Math.abs(temp.y2 - temp.y1);

ctx.strokeRect(x, y, w, h);

} else if (temp.type === 'circle') {

const cx = (temp.x1 + temp.x2) / 2;

const cy = (temp.y1 + temp.y2) / 2;

const rx = Math.abs(temp.x2 - temp.x1) / 2;

const ry = Math.abs(temp.y2 - temp.y1) / 2;

const radius = Math.max(rx, ry);

ctx.ellipse(cx, cy, radius, radius, 0, 0, Math.PI * 2);

ctx.stroke();

}

}

}

// ---------- 图形完成 (保存到shapes) ----------

function finishShape(x2, y2) {

if (startX === x2 && startY === y2) {

// 点一下不生成图形

return;

}

const shape = {

type: currentTool,

x1: startX, y1: startY,

x2: x2, y2: y2,

color: strokeColor

};

shapes.push(shape);

redraw(false); // 重绘时不包含临时图形 (已经保存)

}

// ---------- 事件绑定 ----------

function onPointerDown(e) {

e.preventDefault();

const coord = getCanvasCoords(e);

isDrawing = true;

startX = coord.x;

startY = coord.y;

currentX = startX;

currentY = startY;

redraw(true);

}

function onPointerMove(e) {

e.preventDefault();

if (!isDrawing) {

// 可以不做任何事,但为了显示坐标可扩展

return;

}

const coord = getCanvasCoords(e);

currentX = coord.x;

currentY = coord.y;

redraw(true);

}

function onPointerUp(e) {

e.preventDefault();

if (!isDrawing) return;

const coord = getCanvasCoords(e);

const endX = coord.x;

const endY = coord.y;

// 如果鼠标松开时位置与起点不同,则完成图形

if (Math.abs(endX - startX) > 1 || Math.abs(endY - startY) > 1) {

finishShape(endX, endY);

} else {

// 小偏移也当作点击,不生成图形,但需要清除临时状态

redraw(false);

}

isDrawing = false;

// 重置临时坐标

startX = 0; startY = 0;

currentX = 0; currentY = 0;

}

// 鼠标事件

canvas.addEventListener('mousedown', onPointerDown);

window.addEventListener('mousemove', (e) => {

if (!isDrawing) return;

onPointerMove(e);

});

window.addEventListener('mouseup', onPointerUp);

// 触摸事件 (移动端支持)

canvas.addEventListener('touchstart', (e) => {

e.preventDefault();

const touch = e.touches[0];

if (!touch) return;

const mouseEvent = new MouseEvent('mousedown', {

clientX: touch.clientX,

clientY: touch.clientY,

});

canvas.dispatchEvent(mouseEvent);

}, { passive: false });

window.addEventListener('touchmove', (e) => {

if (!isDrawing) return;

e.preventDefault();

const touch = e.touches[0];

if (!touch) return;

const mouseEvent = new MouseEvent('mousemove', {

clientX: touch.clientX,

clientY: touch.clientY,

});

window.dispatchEvent(mouseEvent);

}, { passive: false });

window.addEventListener('touchend', (e) => {

if (!isDrawing) return;

e.preventDefault();

const mouseEvent = new MouseEvent('mouseup', {});

window.dispatchEvent(mouseEvent);

}, { passive: false });

// ---------- 工具栏切换 ----------

document.querySelectorAll('.tool-btn').forEach(btn => {

btn.addEventListener('click', () => {

document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active'));

btn.classList.add('active');

currentTool = btn.dataset.tool;

const toolNameMap = { line: '直线', rect: '矩形', circle: '圆形' };

toolStatus.textContent = toolNameMap[currentTool] || '直线';

// 切换工具时如果有绘制中的状态,取消

if (isDrawing) {

isDrawing = false;

redraw(false);

}

});

});

// ---------- 颜色选择 ----------

document.getElementById('strokeColor').addEventListener('input', (e) => {

strokeColor = e.target.value;

// 如果有正在绘制的图形,更新颜色实时预览 (redraw会使用strokeColor)

if (isDrawing) {

redraw(true);

}

});

// ---------- 清空 ----------

document.getElementById('clearCanvas').addEventListener('click', () => {

shapes = [];

if (isDrawing) {

isDrawing = false;

}

redraw(false);

});

// ---------- 初始化 绘制一个示例图形 (让人知道可以用) ----------

function initDemo() {

shapes = [

{ type: 'line', x1: 80, y1: 100, x2: 280, y2: 200, color: '#aa4a6b' },

{ type: 'rect', x1: 350, y1: 80, x2: 550, y2: 220, color: '#2a6b4a' },

{ type: 'circle', x1: 480, y1: 350, x2: 620, y2: 410, color: '#3f5f9f' },

{ type: 'line', x1: 40, y1: 380, x2: 200, y2: 420, color: '#b06a3a' },

];

redraw(false);

toolStatus.textContent = '直线';

}

initDemo();

// 额外:如果颜色改变时更新示例颜色没必要,但保留一致性

// 窗口失焦时如果有绘制,不丢失

// 处理canvas尺寸变化 (保持比例)

})();

</script>

</body>

</html>

```

绘图工具的使用与交互

可以通过简单的拖拽来创建图形,界面设计直观,易于上手。

1. 选择图形工具:点击工具栏上的“直线”、“矩形”或“圆形”按钮即可切换当前绘图模式。

2. 绘制图形:在画布上按住并拖拽鼠标,即可绘制出对应形状。松开鼠标后,图形将被固定并保存。

3. 自定义样式:可以通过颜色选择器更改当前图形的轮廓颜色,所有新绘制的图形都将使用此颜色。

4. 管理作品:点击“清空”按钮可以一键删除画布上的所有图形,方便重新开始创作。