本部分重点讲解了 Canvas 中心点的内容,在 Canvas 中所有内容的绘制都是基于上下文变换后的新坐标系中心点来完成的,对于坐标系中心点的理解是正确完成内容绘制的前提。同时,本章节也将重点讲述 Canvas 绘图状态相关的两个方法 save 和 restore。
2D 矩阵指的是元素在 2D 平面内发生诸如缩放、平移、旋转、拉伸四种变化,在 CSS3 中对应 4 个方法分别是 scale()、translate()、*rotate()*和 skew(),这 4 个方法是 CSS3 矩阵 matrix 的快捷方式,其本质都是由 matrix() 实现的。
类似地,在 Canvas 中与 CSS3 对应的 3 个方法分别是 scale()、translate()、rotate(),而 Canvas 对象没有 skew 方法,CSS3 中的矩阵和 Canvas 矩阵原理是相通的。matrix 方法有六个参数 matrix(a, b, c, d, x, y),六个参数默认值是:
matrix(1, 0, 0, 1, 0, 0)
这六个参数分别控制不同的变换
a 水平缩放
b 水平拉伸
c 垂直拉伸
d 垂直缩放
x 水平位移
y 垂直位移
关于 matrix 的各种变换在后面章节会有更加详细的说明,在这里只需知道 Canvas 和 CSS3 中矩阵变换的规则是一致的。
1.1 部分讲过,在 Canvas 中也存在 CSS3 2D 变换的功能,如 translate()、rotate()、scale() 等。虽然两者看起来差不多,但是 CSS3 中元素变换中心点都是针对 DOM 元素,而在 Canvas 中并非针对 DOM 元素的变换,而是虚拟画布区域。
默认情况下,Canvas 中心点是左上角,即坐标(0,0)。就像可以通过 transform-origin 来改变 CSS3 元素变换的中心点一样,Canvas 也可以改变默认中心点,只不过需要通过 translate()方法平移内部的 2D 绘图环境。
#p2 {
transform: rotate(45deg);
transform-origin: 20% 40%;
}
总之,对于中心点而言,CSS3 是针对元素本身,即 DOM 元素,而 Canvas 针对的是整个 Canvas 绘图环境,即虚拟画布区域
首先来看下在不改变中心点情况下,Canvas 旋转前后的变化:
var canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 100, 100);
// 旋转前绘制
ctx.rotate((Math.PI / 180) * 30);
ctx.fillStyle = 'blue';
ctx.fillRect(20, 20, 100, 100);
// 旋转后绘制
ctx.restore();
由上面的代码可以看到,在旋转画布前,我们在坐标(20,20)处绘制了一个 100*100 的黑色矩形,而在旋转之后,又在坐标(20,20)处绘制了一个 100*100 的蓝色矩形。最后得到的效果如下:
需要注意的是,Canvas 旋转前绘制的元素没有旋转效果,而这种旋转效果只会出现在 Canvas 旋转后绘制的元素。因此,如果要对 Canvas 里的某些图形进行旋转处理,就必须在绘图环境旋转后再进行绘制。
那么如果将 Canvas 旋转 180° 后再进行绘制,最后结果如何呢?
window.onload = function () {
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
ctx.rotate((Math.PI / 180) * 180);
var img = document.getElementById('tulip');
ctx.drawImage(img, 10, 10);
};
运行完上述代码,你会发现什么也看不到。因为 Canvas 的中心点是左上角(0,0),当将 Canvas 绘图环境旋转 180 弧度后,你会发现图形已经在 Canvas 可视区域外,这显然是不可行的。那需要如何做呢?
下面将以图解的方式进一步讲述 Canvas 上下文变换的原理,通过分析你也能进一步深入理解上文旋转 180° 的例子,即绘制的元素为啥会莫名消失。
下面分步对该图进行讲解:
第一步:不做任何原点移动的绘制
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
var img = document.getElementById('tulip');
ctx.drawImage(img, 10, 10);
此时通过 X,Y 轴指定了绘制的方向,整个图以 O(0,0)为中心进行绘制。
第二步:移动了绘制原点
ctx.translate(w / 2, h / 2);
绘制原点移动到 O`(1/2w,1/2h),而 X`和 Y`指定了最新绘制的方向。此时需要注意的是:整个 Canvas 在页面中展示区域依然是 w,h 指定的位置,只是绘图的原点发生了改变而已。所以,在 Canvas 中灰色区域是整个 w,h 指定的唯一有图像的区域,而其他区域都是空的,因为 Context 压根就没有在这些位置进行绘制。
第三步:上下文进行旋转
ctx.translate(w / 2, h / 2);
ctx.rotate(Math.PI);
ctx.drawImage(img, 0, 0);
通过这一步,X 轴,Y 轴的方向方向再次发生了变化,分别为 X``和 Y``,但是原点依然对应于 O`(1/2w,1/2h),因为 Context 没有做类似 translate 方法来改变中心点位置。这一步需要注意的是绘制的方向,由 X``和 Y``的指向来看,此时的绘图方向已经转化为向上和向右绘制。和第二步分析一致,黄色区域是唯一能看到图像的区域。到这一步,你应该明白了,现在依然没有实现元素在指定位置的 180° 旋转。请看下例:
window.onload = function () {
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
ctx.translate(500 / 2, 300 / 2);
ctx.rotate(Math.PI);
var img = document.getElementById('tulip');
ctx.drawImage(img, 10, 10);
};
原图为:
代码执行后的效果为:
图片具体表现可以通过上面的分析看出来,很显然现在绘制出来的图片只是原图的一部分,而不是完整的图片。究其原因主要是当图片尺寸超过 0.5w,0.5h 的时候,黄色区域没法完全容纳整张图片的绘制。而造成该问题的本质原因在于上下文移动的 0.5w,0.5h 距离。下面部分讲解具体的解决方法:
通过上面 2.2 的分析不难看出具体的解决方法。第一种途径是在 rotate 后继续改变绘图环境的中心点,将中心点平移到作图区域(-w/2,-h/2)。之所以为负数,是因为 X``和 Y``指定了新的坐标方向与原点要移动的位置相反。代码有:
ctx.translate(w / 2, h / 2);
ctx.rotate(Math.PI);
ctx.translate(-w / 2, -h / 2);
ctx.drawImage(img, 0, 0);
最终效果如下:
第二种途径是改变绘制图片的坐标,将图片绘制到(-w/2,-h/2),代码有:
ctx.translate(w / 2, h / 2);
ctx.rotate(Math.PI);
ctx.drawImage(img, -w / 2, -h / 2);
具体效果与上图相同。
在上面第 2 部分讲过上下文变换设置只会影响到之后的内容绘制,接下来重点讲述下 Canvas 上下文状态相关的两个主要方法, save()和 restore()。
Canvas Context 维持着绘制状态的堆栈,绘制状态主要包括以下几个维度:
1.上下文矩阵变换:例如:平移 translate(),缩放 scale(),以及旋转 rotate()等
2.剪切区域: clip()
3.特殊属性值设置: strokeStyle,fillStyle,globalAlpha,lineWidth,lineCap,lineJoin,miterLimit,shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor,globalCompositeOperation,font,textAlign,textBaseline等
需要注意的是:当前绘制路径、画布内容本身不属于绘图状态。绘制路径是持久的,只能使用 *beginPath()*方法重置,而画布内容是画布的属性,而非上下文。save() 和 restore() 的出现提供了用来操作绘制状态的快捷方法。
1.Context.save() 方法将当前绘制状态压入堆栈
2.Context.restore() 弹出堆栈上的状态,将上下文恢复到该状态。
当前示例的完整代码可以查看这里,将代码复制在任何编辑器中以.html 为文件后缀,用浏览器打开即可看到完整示例效果,下面对核心代码进行说明。
第一步: 首先设置了 Canvas 绘制的 fillStyle、shadow 属性,然后在(0,0)处绘制了一个 15x150 的矩形。接着调用了 *ctx.save()*方法将当前绘制状态压入堆栈。
ctx.fillStyle = '#FA6900';
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(0, 0, 15, 150);
ctx.save();
当前画布及堆栈的状态如下:
第二步:重新设置 fillStyle、shadow 属性,然后在坐标(30,0)处绘制一个 30x150 的矩阵。接着调用 *ctx.save()*方法将当前绘制状态压入堆栈。
ctx.fillStyle = '#E0E4CD';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(30, 0, 30, 150);
ctx.save();
当前画布以及堆栈的完整状态如下:
第三步:再次重新设置 fillStyle、shadow 属性,然后在坐标(90,0)处绘制一个 45x150 的矩阵。接着调用 *ctx.save()*方法将当前绘制状态压入堆栈。
ctx.fillStyle = '#A7DBD7';
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 15;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(90, 0, 45, 150);
ctx.save();
当前画布以及堆栈的完整状态如下:
第四步:调用*ctx.restore()*获取堆栈的最新状态,然后使用该状态绘制一个圆形。
ctx.restore();
ctx.beginPath();
ctx.arc(185, 75, 22, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
当前画布以及堆栈的完整状态如下:
第五步:继续调用*ctx.restore()*获取堆栈的最新状态,然后使用该状态绘制一个圆形。
ctx.restore();
ctx.beginPath();
ctx.arc(260, 75, 15, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
当前画布以及堆栈的完整状态如下:
第六步:继续调用*ctx.restore()*获取堆栈的最新状态,然后使用该状态绘制一个圆形。
ctx.restore();
ctx.beginPath();
ctx.arc(305, 75, 8, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
当前画布以及堆栈的完整状态如下:
本章节以图解的方式讲解了 Canvas 中心点在绘图中的作用,主要通过一个常见的元素 180° 旋转的真实例子展开。同时,重点介绍了两个 Canvas 绘图状态设置的 save()和 restore()方法。通过本章节的学习,应该会对 Canvas 绘制的上下文相关内容有一个比较深入的理解了。
Understanding save() and restore() for the Canvas Context
页面更新:2024-05-31
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号