必须用 getboundingclientrect() 校准坐标并配合 dpr 适配,否则画线会断、偏、糊、卡;mousedown 中调 beginpath() 和 moveto(),mousemove 中只 lineto() + stroke(),禁用 canvas.width = canvas.width,移动端需 preventdefault() 并用 touches[0] 取坐标。

直接用 <canvas></canvas> + 原生 JavaScript 就能跑起来,但不处理坐标校准、状态切换和 DPR 适配,画出来的线会断、偏、糊、卡——尤其是手机上一划就跳或根本不动。
为什么 e.offsetX / e.offsetY 不能用
它被 CSS transform、margin、缩放、滚动等干扰,只要画布不在视口左上角原点,坐标就错。iPhone 上画歪、线条突然跳到左上角,八成是这个原因。
- 必须用
canvas.getBoundingClientRect()拿真实位置 - 计算方式固定:
e.clientX - rect.left和e.clientY - rect.top - 每次
mousemove都要重新取getBoundingClientRect(),因为布局可能动态变化
beginPath() 和 moveTo() 必须配对出现在 mousedown
漏掉任意一个,松开再点,线就会从 (0, 0) “炸”出来;重复调用 beginPath()(比如误写在 mousemove 里),会导致路径被拆成无数短线,stroke 时重绘全部历史点,三笔就卡顿。
-
mousedown中:先ctx.beginPath(),再ctx.moveTo(x, y) -
mousemove中:只调ctx.lineTo(x, y)+ctx.stroke(),绝不再beginPath() -
mouseup或mouseout后设isDrawing = false,否则移出 canvas 还在画
移动端 touch 事件必须加 preventDefault() 且取对坐标
不加 preventDefault(),手指一划页面就滚动,线条直接断开;用 touches[0] 而不是 changedTouches[0],否则多指操作时第二根手指抬起会触发错误坐标。
- 监听
touchstart/touchmove/touchend三个事件 - 每个回调第一行都写
e.preventDefault() - 坐标统一取
e.touches[0].clientX和e.touches[0].clientY,再过getBoundingClientRect()换算
clearRect() 是唯一安全的清空方式
别写 canvas.width = canvas.width ——它会重置整个 canvas 状态:线宽、颜色、阴影、DPR 缩放全丢,高分屏下变细变虚,CSS 宽高自适应场景还会拉伸变形。
- 正确写法:
ctx.clearRect(0, 0, canvas.width, canvas.height) - 如果已做 DPR 适配(例如
canvas.width = rect.width * window.devicePixelRatio),clearRect()的宽高参数也得乘上devicePixelRatio - 硬写死像素值(如
clearRect(0,0,800,600))在响应式布局下必留白或裁剪
最易被忽略的是 DPR 和 getBoundingClientRect() 的配合:没这步,Retina 屏画线细、虚、抖,用户第一反应是“设备坏了”,而不是代码漏了适配。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











