Canvas 图形与绘制知识体系
面向已经会 DOM / TypeScript、但没把 Canvas 当生产表面用过的前端。目标不是「会画矩形」,而是能在高 DPI、变换、交互、动画和读回像素这些条件下,把一张位图表面做成可控的渲染器。
不复述 MDN 方法清单。每节回答:机制是什么、什么时候适用、什么时候不适用、失败时长什么样。
示例默认是浏览器里的通用 TypeScript,不绑 React、Vue、小程序。框架只是宿主,backing store 和 CTM 不会因为你用了某个 UI 库而改变。
1. 先建立的心智模型
Canvas 2D 是 立即模式位图 API:
命令(path / fill / drawImage)
↓ 立刻光栅化
backing store 像素
↓ CSS 缩放显示
屏幕上的 <canvas>
它 没有 SVG 那种可事后移动的节点树。画完一条线,场景里不存在「这条线」对象,只剩像素。要做拖拽、撤销、分层,必须自己建场景图,再按帧重绘。
因此工程上的真实工作不是调用 fillRect,而是:
- 管 backing store 尺寸(CSS 像素 ≠ 设备像素)。
- 管绘制状态栈(以及 路径不入栈 这个陷阱)。
- 管自己的场景图与脏区。
- 管命中测试如何穿过 CTM。
- 管要不要读回像素(
getImageData会逼光栅器走软件路径)。
2. 学习路径
| 阶段 | 学完后你能做什么 | 章节 |
|---|---|---|
| 模型 | 解释为什么 save/restore 救不了当前 path;能把 CSS 坐标和 backing store 对齐 | 01、05 |
| 封装 | 用 Path2D + 状态隔离画复杂图形,而不是全局污染 ctx | 02 |
| 交互 | 相机缩放、命中、框选、DOM overlay | 03 |
| 运行时 | 主线程不卡、Worker 离屏、读回像素有策略 | 04、06、07 |
| 决策 | 这张图该用 Canvas、SVG、WebGL 还是 Konva/Pixi | 09 |
| 场景 | 对着白板/标注/签名/视频/图表直接开工,而不是只会机制 | 10、场景对照 |
3. 模块清单
| 目录 | 内容 |
|---|---|
| 00-总览与学习路径 | 能力分层、和本仓库其它体系的边界 |
| 01-绘制模型 | 立即模式、状态栈、路径、CTM |
| 02-绘制API工程 | context 生命周期、path/text/image、合成 |
| 03-交互与命中 | 指针坐标、isPointInPath、场景图命中 |
| 04-动画与时序 | rAF、时间基准、离屏与提交 |
| 05-高清屏与布局 | DPR、ResizeObserver、zoom |
| 06-性能与可观测 | 读回、分层、层数、测量 |
| 07-浏览器差异 | OffscreenCanvas、hint、引擎差异 |
| 08-安全与导出 | origin-clean、CORS、toDataURL |
| 09-选型边界 | Canvas vs SVG vs CSS vs WebGL vs WebGPU,Konva/Fabric/Pixi |
| 10-生产场景 | 白板、标注、签名、图像编辑、视频水印、缩略图、撤销、图表、PDF/大图、小地图、Tilemap |
4. 通用判断原则
- 先问有没有对象语义。 要选中、对齐、可访问树,优先 SVG/DOM;要像素效果、大量粒子、滤镜烘焙,才上 Canvas。
- 永远自己存场景,不要从像素反推场景。
getImageData不是查询 API。 - CTM 是第一公民。 命中、导出、叠加 HTML overlay,全部要能在「CSS 像素 / backing store / 本地坐标」之间换算。
- 读回像素是架构决策。 一旦
willReadFrequently: true,通常放弃 GPU 加速;不要在热路径上「偶尔读一下」。 - 一张 canvas 不够就分层,不要在一张上用
globalCompositeOperation硬模拟图层系统。 静态层缓存成ImageBitmap,动态层每帧清。
5. 权威资料
核对日期:2026-08-26