1. Canvas变换的本质:坐标系操作的艺术
第一次接触Canvas变换时,我也被那些"飞出屏幕"的图形折磨得够呛。直到某天突然顿悟:Canvas变换根本不是移动图形本身,而是在操作整个坐标系!这个认知转变让我彻底掌握了Canvas变换的精髓。
Canvas的坐标系就像一张透明的网格纸,默认固定在画布的左上角。所有绘图操作都是相对于这个坐标系进行的。当你调用translate()、rotate()或scale()时,实际上是在移动、旋转或缩放这张"网格纸",而不是单独操作某个图形。
1.1 坐标系变换的底层原理
每个Canvas都维护着一个变换矩阵,这个矩阵决定了如何将绘图坐标映射到实际像素。每次调用变换方法时,实际上是在修改这个矩阵:
javascript复制// 初始状态:单位矩阵
// [1, 0, 0]
// [0, 1, 0]
// [0, 0, 1]
ctx.translate(100, 50); // 矩阵变为:
// [1, 0, 100]
// [0, 1, 50]
// [0, 0, 1]
ctx.rotate(Math.PI/4); // 矩阵继续累积变换
这种矩阵变换遵循后进先出的原则。就像Photoshop的图层叠加,最新应用的变换会最先影响绘图结果。
1.2 为什么图形会"飞走"
当你在(100,100)位置画一个矩形然后旋转时,矩形会绕着坐标系原点(0,0)旋转,而不是自身中心。这就解释了为什么图形会"飞走":
javascript复制ctx.fillRect(100, 100, 50, 50); // 矩形在(100,100)位置
ctx.rotate(Math.PI/4); // 旋转是相对于(0,0)的!
这就像用绳子拴住气球的一角然后旋转,气球会绕着固定点转大圈,而不是原地自转。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 中心点变换的黄金法则
要让图形绕自身中心旋转,必须遵循"移动-旋转-绘图"的三步法则。这个流程在游戏开发和UI动画中极为常见。
2.1 标准操作流程
- 移动坐标系到图形中心:使用
translate(x + width/2, y + height/2) - 应用旋转:使用
rotate(angle) - 绘制图形:坐标要相对于新坐标系计算,通常使用负偏移
javascript复制function drawCenteredRotatedRect(ctx, x, y, width, height, angle) {
ctx.save();
// 1. 移动坐标系到图形中心
ctx.translate(x + width/2, y + height/2);
// 2. 旋转坐标系
ctx.rotate(angle);
// 3. 绘制(注意负偏移)
ctx.fillRect(-width/2, -height/2, width, height);
ctx.restore();
}
2.2 实际应用案例:仪表盘指针
这种技术特别适合制作仪表盘指针动画:
javascript复制function drawGaugeNeedle(ctx, centerX, centerY, length, angle) {
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
// 指针(从中心点向外延伸)
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.lineWidth = 4;
ctx.strokeStyle = 'red';
ctx.stroke();
// 指针尾部装饰
ctx.beginPath();
ctx.arc(0, 0, 8, 0, Math.PI*2);
ctx.fillStyle = 'silver';
ctx.fill();
ctx.restore();
}
3. 复合变换的顺序陷阱
当需要组合多种变换时,操作顺序会极大影响最终效果。错误的顺序会导致图形位置、大小完全失控。
3.1 变换顺序的重要性
正确的变换顺序应该是:
- 平移(translate)
- 缩放(scale)
- 旋转(rotate)
javascript复制// 正确顺序示例
ctx.save();
ctx.translate(200, 150); // 先移动
ctx.scale(1.5, 1.5); // 再缩放
ctx.rotate(Math.PI/4); // 最后旋转
ctx.fillRect(-50, -50, 100, 100);
ctx.restore();
3.2 常见错误分析
错误1:先旋转后平移
javascript复制ctx.rotate(Math.PI/4);
ctx.translate(200, 150);
// 此时平移方向已经跟着旋转了45度!
错误2:先缩放后平移
javascript复制ctx.scale(2, 2);
ctx.translate(100, 100);
// 实际平移了200px而不是100px!
4. 状态管理:save()与restore()的最佳实践
Canvas的状态栈机制是管理复杂变换的关键。合理使用可以避免很多难以调试的问题。
4.1 状态栈的工作原理
每次调用save()时,Canvas会将当前状态(包括变换矩阵、样式属性等)压入栈中。restore()则弹出最近保存的状态。
javascript复制ctx.fillStyle = 'red';
ctx.save(); // 保存状态1(红色填充)
ctx.fillStyle = 'blue';
ctx.save(); // 保存状态2(蓝色填充)
ctx.fillStyle = 'green';
ctx.restore(); // 恢复状态2(蓝色)
ctx.restore(); // 恢复状态1(红色)
4.2 实用技巧
- 嵌套变换:为每个独立图形创建状态隔离
javascript复制
