Canvas系列(7):形变

时间:2022-07-27
本文章向大家介绍Canvas系列(7):形变,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

CSS3中有一个很重要的点,就是形变。他分为移动,缩放、旋转和倾斜。在Canvas中,形变都是基于坐标做的,所以,并没有直接的API支持倾斜,其它几种都是有独立的API来支持,命名和CSS是一样的。今天我们就看一下这几种吧。


平移

平移是最简单的一种形变,我们直接来看一个例子吧:

context.fillRect(10,10,20,20);

// x平移20px y平移20px
context.translate(20, 20);
context.fillRect(10,10,20,20);

context.translate(20, 20);
context.fillRect(10,10,20,20);

效果:

通过上面我们可以看到,平移(形变)移动的是坐标系,移动以后会以新的坐标系进行绘图,当多次平移(形变)以后每次都会以上一次的坐标系为准。此时你可能会问,那形变不是很危险吗,每次使用了形变就会使用新的坐标系,以后所有绘制的图片都会受到影响?没错是这样的,那改怎么解决呢?还记得之前的状态吗?现在给一个简单的例子:

// 形变前往往需要保存状态
context.save();

context.translate(20, 20);
context.fillRect(10,10,20,20);

// 形变结束,恢复之前的状态
context.restore();

// 此时以之前的坐标系绘制
context.beginPath();
context.fillStyle='red';
context.fillRect(10,10,20,20);

效果:

缩放

缩放也是相对于坐标系来说的,看一下这个例子:

context.strokeStyle='red';
context.lineWidth=10;
context.strokeRect(20, 20, 50, 50);

context.beginPath();
// x是原来的1.5倍 y是原来的1.5倍
context.save();
context.scale(1.5, 1.5);
context.strokeStyle='blue';
context.strokeRect(20, 20, 50, 50);
context.restore();

效果:

可以看到,缩放改变的也是坐标系,在新的坐标系系中,宽度也放大了,一个像素已经不再是真正的一个像素了,而是放大以后的大小。所以可以看到都是10个像素的边框,宽度也不一样了。需要注意的是缩放的值大于1的时候是放大,0~1之间是缩小,1和原来是一样大的。

旋转

直接上代码:

context.strokeStyle='red';
context.lineWidth=10;
context.strokeRect(80, 20, 50, 50);

context.beginPath();
// x是原来的1.5倍 y是原来的1.5倍
context.save();
context.rotate(Math.PI / 180 * 45);

context.strokeStyle='blue';
context.strokeRect(80, 20, 50, 50);
context.restore();

效果

可以看到旋转是基于坐标的原点的,如果不希望按照原点旋转的话,可以先平移再旋转。另外旋转也是根据弧度来旋转的而不是角度。

矩阵变换

矩阵变换使用的API是context.transform(a, b, c, d, e, f);,所对应的矩阵的位置是下面这个样子:

什么,看不懂?前方高能!!!多年前欠下的线性代数债,现在要还了。

上面矩阵是平移时候的矩阵,将中间的矩阵带入矩阵的API有:context.transform(1, 0, 0, 1, e, f);。也就是说context.translate(e, f);等价于context.transform(1, 0, 0, 1, e, f);

也就是说context.scale(a, d);等价于context.transform(a, 0, 0, d, 0, 0);

也就是说context.rotate(angle);等价于context.transform(cos(angle), sin(angle), -sin(angle), cos(angle), 0, 0);

所以我们上面的几个例子,我们做下面的等价替换,效果是一样的:

context.translate(20, 20);
// 等价于
context.transform(1, 0, 0, 1, 20, 20);

context.scale(1.5, 1.5);
// 等价于
context.transform(1.5, 0, 0, 1.5, 0, 0);

context.rotate(Math.PI / 180 * 45);
// 等价于
var theta = Math.PI / 180 * 45;
context.transform(Math.cos(theta), Math.sin(theta), - Math.sin(theta), Math.cos(theta), 0, 0);

除了transform外还有一个矩阵变换的方法叫setTransform,参数是一模一样的context.setTransform(a, b, c, d, e, f);。两者的区别是后者始终以最初的坐标做为参照,而transform以上次变换后的坐标做为参照(类似于前面的三个API)。