Canvas自由绘制与轨迹保存的关键在于路径管理、坐标归一和数据结构化:需用getBoundingClientRect校准坐标,每次down事件调用beginPath和moveTo,mousemove中仅lineTo+stroke;保存应兼顾PNG导出与矢量轨迹(含时间戳、颜色、简化点数组);高DPI屏须按devicePixelRatio缩放canvas及ctx;移动端需同时监听touch事件并禁用默认手势。

Canvas 实现自由绘制和轨迹保存,关键不在“画出来”,而在“怎么记、怎么画、怎么留得住”。核心是路径管理 + 坐标归一 + 数据结构化,不是把点连成线就完事。
自由绘制:别让线条断开或飞走
自由涂鸦最常出问题的是:松手再点,线从左上角炸出来;快速滑动,线条中间断开;在手机上画,位置偏移严重。这些都源于坐标不准、路径没初始化、事件没隔离。
- 每次 mousedown 或 touchstart 必须调用
ctx.beginPath()和ctx.moveTo(x, y)—— 缺一不可。不moveTo,lineTo默认从 (0, 0) 连;不beginPath,新路径会接续旧路径,导致意外连线 - 坐标必须用
canvas.getBoundingClientRect()校准:const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top;—— 别用e.offsetX/Y,它会被 CSS 缩放、margin、transform 干扰 -
mousemove/touchmove 中只做 lineTo + stroke,不再调
beginPath。保持一个开放路径持续追加端点,才能画出连续笔迹。每帧都beginPath会导致重绘全部历史路径,两笔就卡 - 设
ctx.lineCap = 'round'和ctx.lineJoin = 'round',否则线头像刀切、拐角像钉子,视觉生硬
轨迹保存:存点还是存图像?看你要什么
保存分两种思路:一种是导出图片(简单直接),一种是存原始轨迹(支持重绘、缩放、编辑)。多数场景建议两者都留。
-
导出 PNG 图片:调
canvas.toDataURL('image/png')得到 base64 字符串,可直接用<a href="data:..." download="doodle.png">下载,或发给后端存为文件 -
保存矢量轨迹:把每次落笔(down → up)记录为一个对象,含时间戳、颜色、线宽、点数组(x/y/t)。例如:
{ points: [{x:120,y:80,t:12345}, ...], color: '#333', lineWidth: 3 } - 点数组建议做轻量处理:用 RDP 算法简化冗余点,或只存间隔 ≥10ms 的点,避免手指抖动产生大量噪点
- 如果后续要支持撤销、橡皮擦、变色,必须用轨迹方式保存——图像一旦画上去,就再也分不清哪一笔是谁的
高 DPI 屏幕适配:不然画出来全是马赛克
在 iPhone、MacBook 或高分屏 Windows 上,Canvas 不做适配,线条就会模糊、锯齿、细得看不见。这不是 CSS 问题,是像素密度没对齐。
立即学习“前端免费学习笔记(深入)”;
- 获取设备像素比:
const dpr = window.devicePixelRatio || 1; - 用 JS 动态设置 canvas 的真实分辨率:
canvas.width = canvas.clientWidth * dpr;,canvas.height = canvas.clientHeight * dpr; - 再用 CSS 把显示尺寸设回目标大小:
canvas.style.width = canvas.clientWidth + 'px'; canvas.style.height = canvas.clientHeight + 'px'; - 最后缩放绘图上下文:
ctx.scale(dpr, dpr);—— 这样所有lineWidth=2才真对应 2 物理像素,不会被浏览器插值糊掉
移动端兼容:触摸不能只靠 mouse 事件
只监听 mousedown/move/up,手机就完全没法画。必须同时支持 touch,并注意事件默认行为。
- 绑定
touchstart、touchmove、touchend,且passive: false,否则preventDefault()失效,页面会跟着手指滚动 - touchmove 中取点用
e.touches[0].clientX,别用e.changedTouches(多指时容易错乱) - 为防误触,可加简单判断:
if (e.touches.length > 1) return; - 加一行
canvas.style.touchAction = 'none';,禁用浏览器默认手势(双指缩放、长按菜单等)



















