使用方法
- 打开页面画布上已经有一段示例笔迹(蓝色平滑曲线加一个橙色圆),拿起鼠标就能接着画,撤销一次可整笔移除示例。
- 在色板里点选 12 个预设色之一(默认 #315fe8 蓝),笔刷粗细在 1~48px 之间、默认 6px,改完立即生效。
- 按住鼠标或手指在画布上拖动即出笔迹,圆头线帽 + 中点平滑让拐角不带毛刺;笔迹坐标按 canvas.width ÷ 显示宽度自动换算,手机竖屏也不会偏。
- 切「橡皮」模式擦除是真实透明(destination-out),不是画白;「撤销」最多回退 12 步,「清空画板」也入栈,后悔来得及。
- 点「下载 PNG」导出 1000 × 600 的文件,未覆盖区域保持透明底。下方调色板对照表可「加载更多」扩到 24 色,「导出 CSV」带 BOM。
这个在线绘图看板能解决什么
临时要画一张示意图发群里:流程草图、界面布局框线、给同事标注截图重点——不用装软件,打开就画,导出即用,画完的 1000 × 600 尺寸在聊天窗口里正合适。
触屏设备上的手写便签:平板或手机浏览器里用手指直接写画,Pointer Events 统一了鼠标、触摸和手写笔三条输入路径,写出来的字走圆头平滑,不会像裸 lineTo 那样在转折处出现折线毛刺。
给设计沟通留底:调色板 12 色与十六进制颜色表同源,画完把用到的色值从对照表复制给下游,避免「就那个蓝色」式的口头传色。
本页不做图形化流程图(节点连线有专门的图表生成页),也不做图片批注裁剪;要在已有图片上圈画,先到图片工具里处理再回来叠加笔迹。
原理与规则说明
在线绘图看板的绘制内核是 Canvas 2D 的 getContext('2d') 加四件套:beginPath → moveTo → lineTo → stroke,配合 lineCap = 'round' 与 lineJoin = 'round'。线帽取圆是为了单点和收尾不出现方头断痕;拐角取圆是消除锐角处的毛刺。平滑用中点二次贝塞尔:记录相邻两取样点的中点,用 quadraticCurveTo(前一点, 中点) 连接,比逐点 lineTo 少了抖动高频分量。
输入统一走 W3C Pointer Events(pointerdown / pointermove / pointerup),down 时调 setPointerCapture 把后续指针事件锁到画布上——手指划出画布边缘不会丢事件导致「抬笔断线」。坐标换算公式是 (clientX − rect.left) × (canvas.width ÷ rect.width),画布被 CSS 缩放时同样成立。
撤销栈为什么是 12 步:每步快照用 getImageData(0, 0, 1000, 600) 存整幅位图,一帧 1,000 × 600 × 4 字节 = 2,400,000 字节 ≈ 2.29 MiB,12 步共 27.47 MiB,是中端手机可接受的上限;20 步要 45.78 MiB,低内存设备会触发页面回收。快照在「每笔落笔前」「清空前」压栈,撤销即 putImageData 弹栈还原。
画布背板为什么固定 1000 × 600 而不乘 devicePixelRatio:高分屏(dpr = 2)下背板要开 2000 × 1200,单帧 9.16 MiB,12 步撤销栈涨到约 110 MiB,收益只是导出图更清晰,而本页定位是聊天与文档插图,1000 × 600 已超过常见展示尺寸。这是清晰度与内存的显式折衷,坐标换算公式保证了 CSS 缩放下的绘制精度不受影响。
橡皮的实现是 globalCompositeOperation = 'destination-out':把笔迹覆盖处的已有像素 alpha 抹成 0,得到真透明;导出走 toDataURL('image/png')——PNG(ISO/IEC 15948)保留 alpha 通道,未画区域在深色背景上也是透明的,这是选 PNG 而不是 JPG 的原因。
完整示例(数值仅作演示)
默认示例:打开页面时程序先压入一张空白快照,再用 6px 蓝色(#315fe8)画一条中点平滑曲线、6px 橙色(#f97316)画一个圆——点一次「撤销」,整段示例笔迹消失,回到空白;再点一次提示「撤销栈为空」。控件默认色 #315fe8、默认笔刷 6px,与结果区文案、调色板第 9 行三处一致。
橡皮示例:选「橡皮」模式在示例曲线中间划一道,曲线会露出真实透明缺口;此时下载 PNG 并垫在深色页面上,缺口透出的是页面背景色而非白色——这就是 destination-out 与「画白橡皮」的可验证差异。
笔刷边界示例:填 1 得到最细 1px 线(圆头线帽下仍是 1px 圆点收尾),填 48 得到 48px 宽带;填 0 或 100 会被拦下并提示范围。
输入范围与对照表
输入范围:笔刷 1~48px、步长 1;颜色 12 预设 + 自定义拾色器;画布固定 1000 × 600;撤销栈深度 12。看板没有文本输入框,所有操作即时生效。
| 操作 / 输入 | 处理 | 边界说明 |
|---|---|---|
| 拖动画笔 | 压栈 → 平滑绘制 | 每笔一次快照,12 步后挤掉最旧 |
| 橡皮拖动 | destination-out 抹透明 | 同样入撤销栈 |
| 撤销 | putImageData 弹栈还原 | 栈空时提示,不静默 |
| 清空画板 | 压栈后整幅抹透明 | 可撤销回来 |
| 笔刷填 0 / 100 | 拒绝并提示 1~48 | 不静默夹到边界 |
| 下载 PNG | toDataURL 导出 | 1000 × 600,透明底保留 |
调色板对照表 12 行与画笔色板逐格一致;「加载更多」追加的 12 行(#b91c1c、#f59e0b、#10b981、#1e3a8a 等)与站内十六进制颜色表同记法,方便直接抄给下游。
输入范围、边界与精度说明
每条边界都有具体文案:撤销到栈底提示「已经没有更早的笔迹了:撤销栈最多保存 12 步,且已全部用完」;笔刷填非法值提示「笔刷粗细必须是 1~48 之间的整数:当前填的是 X」;导出异常(极端罕见,如浏览器内存不足时 toDataURL 抛错)会附原始报错而不吞掉。坐标换算在画布被 CSS 压缩到手机宽度(360px)时仍逐像素对位,误差不超过 1 画布像素。
精度边界:平滑取样跟随 pointermove 原生频率,高刷屏不插值、低刷屏不丢点;1px 笔刷是视觉下限,再细由抗锯齿承担;48px 以上线宽的意义不大——超过画布高度 8% 的笔迹更适合当色块填充而不是笔迹。
内存边界回顾:12 步 × 2.29 MiB = 27.47 MiB 是本页刻意设置的停车线,为了这个数字放弃了 dpr 缩放(见原理一节)。若浏览器标签页在后台被冻结,撤销栈随页面内存一起释放,属于浏览器行为而非本页逻辑。
常见误用:以为橡皮是「涂白」——它是真透明,导出图垫在深色底上会与白底版本明显不同;要白底成品,先用白色画笔把画布涂满再作画。另一个误用是拿本页画高精度印刷图:1000 × 600 在 300dpi 下只有约 8.5 × 5.1 厘米,印刷请用矢量工具重绘。
常见问题
撤销栈为什么只有 12 步?
每步要存一张整幅位图快照:1000 × 600 × 4 字节 ≈ 2.29 MiB,12 步 27.47 MiB。再多一步是手机内存的冒险,本页选择固定深度而不是「越多越好」。
手机上画画会不会误触页面滚动?
画布 CSS 设了 touch-action: none,手指落在画布上时浏览器把滚动权交给绘图,页面不会跟着滚;想滚动页面请从画布外划。
导出的 PNG 是白底还是透明底?
透明底。未画过的地方 alpha = 0,PNG 保留透明通道;垫白看不出来,垫深色就显形。需要白底请先用白色画满。
为什么导出尺寸固定 1000 × 600?
背板尺寸即导出尺寸。固定背板让撤销栈内存可预算,也让坐标换算只有一档缩放比;要更大尺寸的图请用图片放大类工具后处理。
画错了一笔想只删这一笔怎么办?
撤销回退的是「笔」而不是像素:快照在落笔前压栈,撤销一次撤一整笔(含清空和示例)。像素级局部修改请用小号橡皮。
手写笔和鼠标有区别吗?
没有。Pointer Events 把鼠标、触摸、手写笔统一成同一套事件,压感信息本页未使用,三种输入画出的线条规则一致。
画的内容会被上传吗?
不会。Canvas 是浏览器本地内存,导出用 toDataURL 本地生成,全程无网络请求,可在开发者工具 Network 面板自查。
刷新页面后画还在吗?
不在。本页刻意不做本地持久化,避免把草稿悄悄写进用户磁盘;要留存请点「下载 PNG」。
相关工具
在线绘图看板解决「随手画一张」,周边的屏幕与图表工具还有这些。