之前研究了下 css 中的 2d transform ,最近又看了下 canvas 的一些关于 2d 变换的 api,发现 api 间有很大的重复性( rotate translate scale setTransform …),不过一些重复功能的 api 确实暴露了更方便的接口。
例如对一个物体(矩阵)进行平移,旋转,缩放的复合操作,至少有三种实现手段。
rotate/scale/translate 多次变换坐标系
最简单的做法是直接直接调用 rotate , scale translate 等 api,示例
context.save(); context.translate(x, y); context.rotate(rotation * Math.PI / 180); context.scale(scaleX, scaleY); context.moveTo(points[0].x, points[0].y); for (var i = 1; i < points.length; i++) { var p = points[i]; context.lineTo(p.x, p.y); } context.closePath(); context.fillStyle = '#000'; context.fill(); context.restore();
这种做法是使得 cavans 变换自己的坐标系,因此为了不影响之后的调用,需要调用 save 和 restore api
不变换坐标系
由于每次坐标系变换都是同 css transform 一样等同于一个变换矩阵,那么可以累计出一个总的变换矩阵,并作用于新坐标系的坐标值就可以得到对应点在老坐标系下的坐标值,示例如下
var i, p; var matrix = new Matrix(1, 0, 0, 1, 0, 0); // inverse order // matrix = scale * matrix matrix.scale(scaleX, scaleY); // matrix = rotate * matrix; matrix.rotate(rotation * Math.PI / 180); // matrix = translate * matrix; matrix.translate(x, y); var ns=[]; for (i = 0; i < points.length; i++) { p = points[i]; ns[i] = matrix.transformPoint(p, true, true); } context.moveTo(ns[0].x, ns[0].y); for (i = 1; i < ns.length; i++) { p = ns[i]; context.lineTo(p.x, p.y); } context.closePath(); context.fillStyle = '#000'; context.fill();
这种做法可以不对 canvas 做坐标变换从而不会影响后面的 canvas 操作,需要注意的是,矩阵累计的顺序和 canvas 坐标变换的顺序要相反,这样才能体现出后面的坐标变换是在前一次的基础上。
setTransform 一次性变换坐标系
可以说是1,2做法的结合体吧,第一种做法调用了多次 api 而多次变换坐标系,第二种做法手工计算点在老坐标系下的值,第三次做法则是计算得到多次坐标系变换对应的最终变换矩阵,然后调用 setTransform api 一次性进行坐标系变换,示例:
var i, p; var matrix = new Matrix(1, 0, 0, 1, 0, 0); // inverse order matrix.scale(scaleX, scaleY); matrix.rotate(rotation * Math.PI / 180); matrix.translate(x, y); var ns=points; context.save(); context.setTransform(matrix.a,matrix.b,matrix.c,matrix.d,matrix.tx,matrix.ty); context.moveTo(ns[0].x, ns[0].y); for (i = 1; i < ns.length; i++) { p = ns[i]; context.lineTo(p.x, p.y); } context.closePath(); context.fillStyle = '#000'; context.fill(); context.restore();
demo
最终殊途同归,结果自然是一样的: demo .