Canvas 变换矩阵:translate/rotate/scale 的心智模型
在 Canvas 里,几乎所有「移动」「旋转」「缩放」相关的问题,最后都会落到三个函数上:
translate、rotate、scale。
这一篇不打算推矩阵公式,而是从工程视角讲清楚几件事:变换是怎么叠加的、为什么顺序很重要,以及如何用这套机制围绕任意点旋转和实现画布缩放/平移。
1. 把「坐标系」当成会动的东西
在没有任何变换时:
- 原点在画布左上角;
- x 轴向右,y 轴向下。
调用 translate / rotate / scale 时:
- 本质上是在「移动/旋转/拉伸」当前坐标系;
- 后续所有绘图命令(包括
fillRect、lineTo等)都会在新的坐标系下执行。
可以用一个直觉模型来理解:
- 不是图形在动,而是坐标系在动;图形永远是在「当前坐标系」的原点附近被画出来。
2. translate(x, y):移动原点
translate(dx, dy) 的作用是:
- 把坐标系的原点从当前
(0, 0)移动到(dx, dy); - 随后以
(dx, dy)为新的(0, 0)。
常见用法:
- 先
translate到目标位置,再在局部坐标系中画图形; - 比如绘制一个以
(cx, cy)为中心的局部坐标系下的图形,就可以:
1ctx.save();
2ctx.translate(cx, cy);
3// 在 (0, 0) 附近绘制局部图形
4ctx.restore();
这样可以避免在每个点的计算中反复加上 (cx, cy) 偏移。
3. rotate(angle):围绕原点旋转坐标系
rotate(angle) 会围绕当前原点把整个坐标系旋转一个角度(弧度制):
- 之后的所有绘制都会在旋转后的坐标轴下进行;
- 常见语法是先平移再旋转,这样可以围绕特定点旋转。
3.1 围绕任意点旋转的套路
如果想围绕某个点 (cx, cy) 旋转一个图形:
- 思路是:
- 把坐标系平移到该点(
translate(cx, cy)); - 在新原点处旋转坐标系(
rotate(angle)); - 在局部坐标内绘制图形。
- 把坐标系平移到该点(
示意结构:
1ctx.save();
2ctx.translate(cx, cy);
3ctx.rotate(angle);
4// 在 (0, 0) 周围画图,例如以原点为中心的矩形/图标
5ctx.restore();
这样图形看起来就是「围绕 (cx, cy) 旋转」的。
4. scale(sx, sy):缩放坐标系
scale(sx, sy) 会按给定比例拉伸坐标系:
sx > 1:水平方向放大;0 < sx < 1:缩小;sy同理,对垂直方向生效。
后续所有绘制命令中的坐标和尺寸都会被相应缩放:
- 例如在
scale(2, 2)后画fillRect(0, 0, 10, 10),视觉效果是 20×20 的矩形。
常见用法包括:
- 实现画布整体缩放(常见于画板/地图类应用);
- 配合
translate实现以某个点为中心的缩放。
5. 顺序很重要:变换是「右乘叠加」的
多个变换调用的顺序会直接影响最终效果:
translate→rotate与rotate→translate并不等价。
一个形象的理解方式是:
- 每次调用变换 API,相当于往「当前变换矩阵」右侧再乘一个矩阵;
- 后调用的变换更靠近绘制命令,对最终结果影响在更内层。
一个常用的安全写法是:
- 把「平移到中心 → 旋转 → 绘制」这三步的顺序固定下来;
- 在需要多重变换时,使用
save/restore把局部变换和全局变换隔离开。
6. save / restore:管理变换与样式的栈
变换和样式是叠加的,使用不当会导致后续绘制全被「带偏」。save() / restore() 提供了一套简单的状态栈机制:
save():把当前所有状态(包括变换矩阵、样式等)压入栈中;restore():从栈顶弹出状态并恢复。
典型用法:
1ctx.save();
2ctx.translate(cx, cy);
3ctx.rotate(angle);
4// 绘制局部对象
5ctx.restore();
6
7// 这里的坐标系和样式又回到了 save() 之前的状态
这使得在复杂场景中可以把每个局部对象的绘制包裹在一对 save / restore 中,避免相互污染。
7. 视图缩放与平移:用变换实现「画布移动」
在画板、地图、流程图等场景中,常常需要:
- 整体缩放视图(鼠标滚轮放大/缩小);
- 平移视图(拖拽画布)。
一个常见实现思路是:
- 把用户的视图变换映射为 Canvas 的全局变换:
- 维护一个「当前缩放比例」和「当前平移偏移」;
- 在每次重绘时,先执行:
translate(offsetX, offsetY);scale(scale, scale);
- 然后在「逻辑坐标系」下绘制所有对象。
这样:
- 内部对象的逻辑坐标不变;
- 视图放大/缩小和平移都通过变换矩阵来管理。
在交互处理时,需要做逆变换:
- 把鼠标事件的屏幕坐标,通过当前缩放与平移还原到逻辑坐标系中;
- 再用逻辑坐标进行命中测试与选中判断。
8. 小结:用统一的心智模型看待 Canvas 变换
可以把这一篇压缩为几条记忆点:
translate/rotate/scale并不是直接在「图形」上操作,而是在修改「坐标系本身」;- 围绕任意点旋转或缩放,通常需要先
translate到该点,再做变换,然后绘制; - 变换调用的顺序会叠加,后调用的影响在更内层,使用
save/restore可以把局部变换与全局状态隔离开; - 视图级的缩放与平移(画布移动)也可以通过变换矩阵来管理,同时在交互中使用逆变换把事件坐标还原到逻辑空间。
掌握这套心智模型之后,再往上看 Canvas 的交互、动画和复杂场景,就可以始终围绕「坐标系在变,图形在当前坐标系里画」这条主线来思考。