本部分重点讲解了Canvas中心点的内容,在Canvas中所有内容的绘制都是基于上下文变换后的新坐标系中心点来完成的,对于坐标系中心点的理解是正确完成内容绘制的前提。同时,本章节也将重点讲述Canvas绘图状态相关的两个方法save和restore。1。Canvas与CSS3坐标变换1。1CSS3matrix2D矩阵和Canvastransform2D矩阵 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。2Canvas与CSS3坐标变换中心点差异 1。1部分讲过,在Canvas中也存在CSS32D变换的功能,如translate()、rotate()、scale()等。虽然两者看起来差不多,但是CSS3中元素变换中心点都是针对DOM元素,而在Canvas中并非针对DOM元素的变换,而是虚拟画布区域。 默认情况下,Canvas中心点是左上角,即坐标(0,0)。就像可以通过transformorigin来改变CSS3元素变换的中心点一样,Canvas也可以改变默认中心点,只不过需要通过translate()方法平移内部的2D绘图环境。p2{transform:rotate(45deg);transformorigin:2040;} 总之,对于中心点而言,CSS3是针对元素本身,即DOM元素,而Canvas针对的是整个Canvas绘图环境,即虚拟画布区域2。Canvas上下文变换2。1Canvas上下文变换基础 首先来看下在不改变中心点情况下,Canvas旋转前后的变化:canvasidcanvaswidth200height200canvas!真实的画布就是200x200varcanvasdocument。getElementById(canvas);ctxcanvas。getContext(2d);ctx。save();ctx。fillSctx。fillRect(20,20,100,100);旋转前绘制ctx。rotate((Math。PI180)30);ctx。fillSctx。fillRect(20,20,100,100);旋转后绘制ctx。restore(); 由上面的代码可以看到,在旋转画布前,我们在坐标(20,20)处绘制了一个100100的黑色矩形,而在旋转之后,又在坐标(20,20)处绘制了一个100100的蓝色矩形。最后得到的效果如下: 需要注意的是,Canvas旋转前绘制的元素没有旋转效果,而这种旋转效果只会出现在Canvas旋转后绘制的元素。因此,如果要对Canvas里的某些图形进行旋转处理,就必须在绘图环境旋转后再进行绘制。 那么如果将Canvas旋转180后再进行绘制,最后结果如何呢?imgidtulipsrca2020imgdataimg。jpgdatasrcimg02。bs178。combjth7c7b07d3cb9f6a74。jpgaltTheTulipcanvasidmyCanvaswidth500height300styleborder:1pxsolidd3d3d3;background:YourbrowserdoesnotsupporttheHTML5canvastag。canvaswindow。onloadfunction(){varcdocument。getElementById(myCanvas);varctxc。getContext(2d);ctx。rotate((Math。PI180)180);varimgdocument。getElementById(tulip);ctx。drawImage(img,10,10);}; 运行完上述代码,你会发现什么也看不到。因为Canvas的中心点是左上角(0,0),当将Canvas绘图环境旋转180弧度后,你会发现图形已经在Canvas可视区域外,这显然是不可行的。那需要如何做呢?2。2Canvas上下文变换原理 下面将以图解的方式进一步讲述Canvas上下文变换的原理,通过分析你也能进一步深入理解上文旋转180的例子,即绘制的元素为啥会莫名消失。 下面分步对该图进行讲解: 第一步:不做任何原点移动的绘制varcdocument。getElementById(myCanvas);varctxc。getContext(2d);varimgdocument。getElementById(tulip);ctx。drawImage(img,10,10); 此时通过X,Y轴指定了绘制的方向,整个图以O(0,0)为中心进行绘制。第二步:移动了绘制原点 ctx。translate(w2,h2); 绘制原点移动到O(12w,12h),而X和Y指定了最新绘制的方向。此时需要注意的是:整个Canvas在页面中展示区域依然是w,h指定的位置,只是绘图的原点发生了改变而已。所以,在Canvas中灰色区域是整个w,h指定的唯一有图像的区域,而其他区域都是空的,因为Context压根就没有在这些位置进行绘制。 第三步:上下文进行旋转ctx。translate(w2,h2);ctx。rotate(Math。PI);ctx。drawImage(img,0,0); 通过这一步,X轴,Y轴的方向方向再次发生了变化,分别为X和Y,但是原点依然对应于O(12w,12h),因为Context没有做类似translate方法来改变中心点位置。这一步需要注意的是绘制的方向,由X和Y的指向来看,此时的绘图方向已经转化为向上和向右绘制。和第二步分析一致,黄色区域是唯一能看到图像的区域。到这一步,你应该明白了,现在依然没有实现元素在指定位置的180旋转。请看下例:imgidtulipsrca2020imgdataimg。jpgdatasrcimg02。bs178。combjth45b6b0800585c6cd。jpgaltTheTulipcanvasidmyCanvaswidth500height300styleborder:1pxsolidd3d3d3;background:YourbrowserdoesnotsupporttheHTML5canvastag。canvaswindow。onloadfunction(){varcdocument。getElementById(myCanvas);varctxc。getContext(2d);ctx。translate(5002,3002);ctx。rotate(Math。PI);varimgdocument。getElementById(tulip);ctx。drawImage(img,10,10);}; 原图为: 代码执行后的效果为: 图片具体表现可以通过上面的分析看出来,很显然现在绘制出来的图片只是原图的一部分,而不是完整的图片。究其原因主要是当图片尺寸超过0。5w,0。5h的时候,黄色区域没法完全容纳整张图片的绘制。而造成该问题的本质原因在于上下文移动的0。5w,0。5h距离。下面部分讲解具体的解决方法:2。3Canvas上元素旋转180的方法 通过上面2。2的分析不难看出具体的解决方法。第一种途径是在rotate后继续改变绘图环境的中心点,将中心点平移到作图区域(w2,h2)。之所以为负数,是因为X和Y指定了新的坐标方向与原点要移动的位置相反。代码有:ctx。translate(w2,h2);ctx。rotate(Math。PI);ctx。translate(w2,h2);ctx。drawImage(img,0,0); 最终效果如下: 第二种途径是改变绘制图片的坐标,将图片绘制到(w2,h2),代码有:ctx。translate(w2,h2);ctx。rotate(Math。PI);ctx。drawImage(img,w2,h2); 具体效果与上图相同。3。Canvas上下文状态保存 在上面第2部分讲过上下文变换设置只会影响到之后的内容绘制,接下来重点讲述下Canvas上下文状态相关的两个主要方法,save()和restore()。3。1save()和restore()方法详解 CanvasContext维持着绘制状态的堆栈,绘制状态主要包括以下几个维度: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()弹出堆栈上的状态,将上下文恢复到该状态。3。2save()和restore()方法示例 当前示例的完整代码可以查看这里,将代码复制在任何编辑器中以。html为文件后缀,用浏览器打开即可看到完整示例效果,下面对核心代码进行说明。 第一步:首先设置了Canvas绘制的fillStyle、shadow属性,然后在(0,0)处绘制了一个15x150的矩形。接着调用了ctx。save()方法将当前绘制状态压入堆栈。ctx。fillStyleFA6900;ctx。shadowOffsetX5;ctx。shadowOffsetY5;ctx。shadowBlur4;ctx。shadowColorrgba(204,204,204,0。5);ctx。fillRect(0,0,15,150);ctx。save(); 当前画布及堆栈的状态如下: 第二步:重新设置fillStyle、shadow属性,然后在坐标(30,0)处绘制一个30x150的矩阵。接着调用ctx。save()方法将当前绘制状态压入堆栈。ctx。fillStyleE0E4CD;ctx。shadowOffsetX10;ctx。shadowOffsetY10;ctx。shadowBlur4;ctx。shadowColorrgba(204,204,204,0。5);ctx。fillRect(30,0,30,150);ctx。save(); 当前画布以及堆栈的完整状态如下: 第三步:再次重新设置fillStyle、shadow属性,然后在坐标(90,0)处绘制一个45x150的矩阵。接着调用ctx。save()方法将当前绘制状态压入堆栈。ctx。fillStyleA7DBD7;ctx。shadowOffsetX15;ctx。shadowOffsetY15;ctx。shadowBlur4;ctx。shadowColorrgba(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。PI2,true);ctx。closePath();ctx。fill(); 当前画布以及堆栈的完整状态如下: 第五步:继续调用ctx。restore()获取堆栈的最新状态,然后使用该状态绘制一个圆形。ctx。restore();ctx。beginPath();ctx。arc(260,75,15,0,Math。PI2,true);ctx。closePath();ctx。fill(); 当前画布以及堆栈的完整状态如下: 第六步:继续调用ctx。restore()获取堆栈的最新状态,然后使用该状态绘制一个圆形。ctx。restore();ctx。beginPath();ctx。arc(305,75,8,0,Math。PI2,true);ctx。closePath();ctx。fill(); 当前画布以及堆栈的完整状态如下: 4。本章小结 本章节以图解的方式讲解了Canvas中心点在绘图中的作用,主要通过一个常见的元素180旋转的真实例子展开。同时,重点介绍了两个Canvas绘图状态设置的save()和restore()方法。通过本章节的学习,应该会对Canvas绘制的上下文相关内容有一个比较深入的理解了。参考资料 Understandingsave()andrestore()fortheCanvasContext