在默认情况,在缩放/旋转的时候在左上角的位置,这里的左上角并不是指的是绘制路径的左上角,而是整个canvas的坐标原点。 但是大部分情况是旋转或缩放的时候都会让其在绘制路径的中心点进行。 原本可以直接粘能用的代码,但是我还是把分析过程说一下,一是为了方便网上不知道的人,也是为了我的以后。
一、默认情况下的缩放或旋转
看一个简单的例子(缩放):
context.save();
radio -= 0.1;
context.scale(radio, radio);
context.fillStyle = randomColor(); // 这里是我写的随机获取颜色的函数
context.fillRect(0, 0, 300, 200);
context.restore();
效果图:

通常情况刚开始验证什么的时候,都需要选择一些比较特殊的情况,比如说我这里选择坐标原点,这样就很容易发现缩放的原点是那儿。
通过上面的图片我们知道缩放的原点就是坐标原点。
再看旋转的例子(旋转):
canvas.fillStyle = randomColor();
canvas.rotate(deg*Math.PI/180);
canvas.fillRect(0, 0, 300, 200);
deg += 10;
canvas.restore();
效果图:

旋转的原点是坐标原点。
二、canvas中translate
为什么要说这个呢,因为在实现中心旋转的时候会反复使用translate。
现在当我们在缩放前使用这个函数,偏移到(100,100)的位置。然后再看效果。
context.save();
radio -= 0.1;
context.translate(100, 100);
context.scale(radio, radio);
context.fillStyle = randomColor(); // 这里是我写的随机获取颜色的函数
context.fillRect(0, 0, 300, 200);
context.restore();
效果图:

注意这句话,可以发现旋转的中心变成(100, 100)。
可能还不是很直观,下面的例子我都将添加坐标轴来辅助看。
Canvas 2D API的CanvasRenderingContext2D.translate() 方法对当前网格添加平移变换的方法。。
translate() 方法, 将 canvas 按原始 x点的水平方向、原始的 y点垂直方向进行平移变换。
这是里面的一句话,说明通过translate改变的是坐标轴的原点位置。并不是我上面所说的。知道这一点以后就可以进行下面的操作了。同时延伸一下,这三个操作(缩放/偏移/旋转)都是操作的坐标轴。
三、分析缩放/旋转中心
先看图1.1所示。
- 如果我们希望按中心点旋转,那么我们就需要知道中心点,这是必须的。同时简单计算得出中心的坐标为:(150, 100)。
- 现在我们缩放的默认中心是坐标原点,那么改变缩放中心就是通过translate将坐标移动中心的位置,这里是(150, 100)。下面看看这样操作后效果图:

context.save();
context.translate(150, 100);
context.scale(radio, radio);
context.fillStyle = randomColor();
context.fillRect(0, 0, 300, 200);
context.restore();
radio -= 0.1;
可以看出单纯这样子是不行的,但是我们注意到了,的确按照我们说的点就行缩放(可以这么理解,虽然实际上仍然是坐标原点缩放的,因为刚才的操作其实更换了坐标原点)。
- 现在虽然按照我们意思在指定点进行缩放,但是仍然存在问题,要解决这个问题,还得了解canvas是基于状态绘制的,也就是先构思然后才绘制,什么意思呢,也就是在调用
context.fillRect(0, 0, 300, 200)这个绘制函数之间的所有操作都是构思并没有真正的绘制,但是会记住每一步,记住每一步指的是会严格按照我们代码的顺序执行,其实也可以理解成没编写一行代码就会绘制这行代码,但是在真正编写代码的时候一定要记住canvas是基于状态的绘制,只是在理解上可以理解成一行一行绘制的。至于为什么使用这种好像不便于理解的方式,是因为这样能够减少有一些没必要的绘制。 - 现在再看,在执行
context.translate(150, 100)时会将坐标原点移动到(150, 100)这个点,然后再执行context.scale(radio, radio)时就基于原先的状态,也就是新的坐标原点进行绘制。如果此时在这个基础上绘制的话,你就会发现,原本100没有原来的100长了,就是因为这样,所以此时应该能想到,只要我们再偏移(-150,-100)这么多就可以了。 先看看代码:
test.save();
test.translate(150, 100);
test.scale(radio, radio);
test.translate(-150, -100);
test.fillStyle = randomColor();
test.fillRect(0, 0, 300, 200);
test.restore();
radio -= 0.1;
效果图:

/**
* canvas中的绘制区域的操作,旋转和缩放的中心点都在绘制区域的中心
* type 如果不传入的话或者传入scale,那么zoom或zoomX、zoomY必传
* 如果type是rotate时,那么degree必传,此为角度值
* 当然以上都可以不错,上下文环境会出现报错,其余的不传会达不到自己想要的效果
*/
function zoneOperation({ context, type = "scale", x = 0, y = 0, operation, degree, zoomX, zoomY, zoom }) {
if (!context) {
throw new Error("缺少绘制所需要的上下文环境context");
} else if (operation && operation.constructor !== Function) {
throw new Error("参数operation需要的是一个函数,而你传入的类型为:" + typeof operation);
}
context.save();
context.translate(x / 2, y / 2);
if (type === "rotate") {
context.rotate(degree * Math.PI / 180);
} else {
if (zoom) context.scale(zoom, zoom);
else context.scale(zoomX, zoomY);
}
context.translate(-x / 2, -y / 2);
operation && operation(context, x, y);
context.restore();
}
下面是调用实例:
zoneOperation({
context: test,
x: 300,
y: 200,
zoom: radio,
operation: function (context, x, y) {
context.fillStyle = randomColor();
context.fillRect(0, 0, x, y);
radio -= 0.1;
}
});
如果学习中发现任何问题,都可以在评论区留言,大家一块交流学习。