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