Canvas API
Canvas的概念最初是由苹果公司提出的,用于在Mac OS X WebKit中创建控制板部件(dashboard widget)。在Canvas出现之前,开发人员若要在浏览器中使用绘图API,只能使用Adobe的Flash和SVG插件,或者只有IE才支持的VML,以及其他一些稀奇古怪的javascript技巧。
SVG和Canvas对比
"Canvas本质上是一个位图画布,其上绘制的图形是不可缩放的,不能像SVG图像那样可以被放大缩小。此外,用Canvas绘制出来的对象不属于页面DOM结构或者任何命名空间——这点被认为是一个缺陷。SVG图像却可以在不同的分辨率下流畅地缩放,并且支持单击检测(能检测到鼠标单击图像上的哪个点)"。
既然如此,为什么WHATWG的HTML5规范不使用SVG呢?尽管Canvas有明显的不足,但HTML Canvas API有两个方面优势可以弥补:首先,不需要将所绘制图像中的每个图元当作对象存储,因此执行性能非常好;其次,在其他编程语言现有的优秀二维绘图API的基础上实现Canvas API相对来说比较简单。
在网页上使用canvas元素时,他会创建一块矩形区域。默认情况下该矩形区域宽为300像素,高为150像素,但也可以自定义具体的大小或者设置canvas元素的其他特性。
1 <canvas></canvas>
在页面加入了canvas元素后,我们便可以通过javascript来自由的控制它。可以在其中添加图片、线条、以及文字,也可以在里面绘图,甚至还可以加入高级动画。
使用canvas编程,首先要获取其上下文(context)。接着在上下文中执行动作,最后将这些动作应用到上下文中。可以将canvas的这种编辑方式想象成为数据库事务:开发人员先发起一个事务,然后执行某些操作,最后提交事务。
canvas中的坐标是从左上角开始的,x轴沿着水平方向(按像素)向右延伸,y轴沿着垂直方向向下延伸。左上角的坐标为x=0,y=0的点称作原点。
在某些情况下,如果其他元素已经够用了,就不应该再使用canvas元素。例如,用canvas元素在HTML页面中动态绘制所有不同的标题,就不如直接使用标题样式(H1、H2等),他们所实现的效果是一样的。
访问页面的时候,如果浏览器不支持canvas元素,或者不支持HTML5 Canvas API中的某些特性,那么开发人员最好提供一份替代代码。例如,开发人员可以通过一张替代图片或者一些说明性的文字告诉访问者,使用最新的浏览器可以获得更佳的浏览效果。
1 <canvas> 2 Update your browser to enjoy canvas! 3 </canvas>
canvas元素的可访问性怎么样?
“提供替代图像或替代文本引出了可访问性这个话题——很遗憾,这是HTML5 Canvas规范中明显的缺陷。例如,没有一种原生方法能够自动为已插入到canvas中的图片生成用于替换的文字说明。同样,也没有原生方法可以生成替代文字以匹配由Canvas Text API动态生成的文字。”
Canvas API的未来迭代中,可能会包含与Canvas显示相关的可聚焦的子区域以及他们之间的交互控制。但是,如果你的图像显示需要显著的交互行为,那么可以考虑使用SVG代替Canvas API。SVG也用于绘制,而且他整合了浏览器的DOM。
同大多数HTML元素一样,canvas元素也可以通过应用css的方式来增加边框,设置内边距、外边距等,而且一些css属性还可以被canvas内的元素继承。比如字体样式,在canvas内添加的文字,其样式默认同canvas元素本身是一样的。
此外,在canvas中为context设置属性同样要遵从css语法。例如,对context应用颜色和字体样式,跟在任何HTML和CSS文档中使用的语法完全一样。
随着IE9的到来,所有浏览器厂商现在都提供了对HTML5 Canvas的支持,而且他已被大多数用户所掌握。
在创建HTML5 canvas元素之前,首先要确保浏览器能够支持它。如果不支持,就需要为那些古董级浏览器提供一些替代文字。
1 try 2 { 3 document.createElement("canvas").getContext("2d"); 4 document.getElementById("support").innerHTML = "HTML5 Canvas is supported in your browser" ; 5 } 6 catch(e) 7 { 8 document.getElementById("support").innerHTML = "HTML5 Canvas is not supported in your browser"; 9 }
上面的代码试图创建一个canvas对象,并且获取其上下文。如果发生错误,则可以捕获错误,进而得知该浏览器不支持canvas。页面中预先放入了ID为support的元素,通过以适当的信息更新该元素的内容,可以反应出浏览器的支持情况。
1 <canvas height="200" width="200"></canvas>
以上代码会在页面上显示出一块200 × 200 像素的“隐藏”区域。假如要为其增加一个边框,用标准CSS边框属性来设置。
1 <canvas id="diagonal" style="border:1px solid;" width="200" height="200"></canvas>
注意,上面的代码中增加了一个值为“diagonal”的ID特性,这么做的意义在于以后的开发过程中可以通过ID来快速找到该canvas元素。
1 <script> 2 function drawDiagonal(){ 3 //取得canvas元素及绘图上下文 4 var canvas = document.getElementById('diagonal'); 5 var context = canvas.getContext('2d'); 6 7 //用绝对坐标来创建一条路径 8 context.beginPath(); 9 context.moveTo(70, 140); 10 context.lineTo(140, 70); 11 12 //将这条线绘制到canvas 13 context.stroke(); 14 } 15 16 window.addEventListener("load", drawDiagonal, true); 17 </script>
首先通过引用特定的canvas ID值来获取对canvas对象的访问权。这段代码中的ID就是diagonal。接着定义一个context变量,调用canvas对象的getContext方法,并传入希望使用的canvas类型。其中,"2d" 为2D、 "webgl"为3D。
接下来,基于这个上下文执行画线的操作。代码中调用了三个方法——beginPath、moveTo、lineTo,传入了这条线的起点和终点的坐标。
方法moveTo和lineTo实际上并不画线,而是在结束canvas操作的时候,通过调用context.stroke()方法完成线条的绘制。moveTo(x, y),将上下文移动到坐标指定点;lineTo(x, y),从指定点画线至目标点。
从上面的代码可以看出,canvas中所有的操作都是通过上下文对象来完成的。只有当对路径应用绘制(stroke)或填充(fill)方法时,结果才会显示出来。否则,只有在显示图像、显示文本或者绘制、填充和清除矩形框的时候,canvas才会马上更新。
也许会认为使用变换增加了不必要的复杂性,但并非如此,其实变换是实现复杂canvas操作的最好方式。了解变换最简单的方法就是把它当成是介于开发人员发出的指令和canvas显示结果质检的修正层(modification layer)。不管在开发中是否使用变换,修正层始终都是存在的。
修正——在绘制系统中的说法是变换——在应用的时候可以被顺序应用、组合或者随意修改。每个绘制操作的结果显示在canvas上之前都要经过修正层去做修正。虽然增加了额外的复杂性,但却为绘制系统增加了更为强大的功能,可以像目前主流图像编辑工具那样支持实时图像处理,所以API中这部分内容的复杂性是必要的。
不在代码中调用变换函数并不意味着可以提升canvas的性能。canvas在执行的时候,变换会被呈现引擎隐式调用。
关于可重用代码有一条重要的建议:一般绘制都应从原点(坐标系中的0,0点)开始,应用变换(缩放、平移、旋转等),然后不断修改代码直至达到希望的效果。
1 <script> 2 function drawDiagonal(){ 3 var canvas = document.getElementById('diagonal'); 4 var context = canvas.getContext('2d'); 5 6 //保存当前绘图状态 7 context.save(); 8 9 //向右下方移动绘图上下文 10 context.translate(70, 140); 11 12 //以原点为起点,绘制与前面相同的线段 13 context.beginPath(); 14 context.moveTo(0, 0); 15 context.lineTo(70, -70); 16 context.stroke(); 17 18 //恢复原有的绘图状态 19 context.restore(); 20 } 21 window.addEventListener("load", drawDiagonal, true); 22 </script>
分析上段代码:
(1)首先,通过ID找到并访问canvas对象。(ID是diagonal)
(2)接着通过调用canvas对象的getContext函数获取上下文对象。
(3)接下来,保存尚未修改的context,这样即使进行了绘制和变换操作,也可以恢复到初始状态。如果不保存,那么在进行了平移和缩放等操作以后,其影响会带到后续的操作中,而这不一定是我们所希望的。
(4)下一步是在context中调用translate函数。通过这个操作,当平移行为发生的时候,我们提供的变换坐标会被加到结果坐标(对角线)上,结果就是将要绘制的对角线移动到了新的位置上。不过,对角线呈现在canvas上是绘制操作结束之后。
(5)应用平移后,就可以使用普通的绘制操作来画对角线了。代码中调用了三个函数来绘制对角线——beginPaht, moveTo, lineTo。绘制的起点是原点(0, 0),而非坐标点(70, 140)。
(6)在线条勾画出来之后,可以通过调用context.stroke()函数将其显示在canvas上。
(7)最后,恢复context至原始状态,这样后续的canvas操作就不会被刚才的平移操作影响了。
HTML5 Canvas API 中的路径代表你希望呈现的任何形状。前面代码中调用beginPath就说明是要开始绘制路径了。路径可以要多复杂有多复杂:多条线、曲线段,甚至是子路径。
按照惯例,不论开始绘制任何图形,第一个需要调用的就是beginPath。这个简单的函数不带任何参数,它用来通知canvas将要开始绘制一个新的图形了。对于canvas来说,beginPath函数最大的用处是canvas需要据此来计算图形的内部和外部范围,以便完成后续的描边和填充。
路径会跟踪当前坐标,默认值是原点。canvas本身也跟踪当前坐标,不过可以通过绘制代码来修改。
调用了beginPath之后,就可以使用context的各种方法来绘制想要的形状了。目前为止,已经用到了几个简单的context路径函数
·moveTo(x, y) : 不绘制,只是将当前位置移动到新的目标坐标(x, y)。
·lineTo(x, y) : 不仅将当前位置移动到新的目标坐标(x, y),而且在两个坐标之间画一条直线。
简而言之,上面两个函数区别在于:moveTo就像是提起画笔,移动到新位置,而lineTo告诉canvas用画笔从纸上的旧坐标画条直线到新坐标。不过,再次提醒一下,不管调用他们哪一个,都不会真正画出图形,因为我们还没有调用stroke或者fill函数。目前,我们只是在定义路径的位置,以便后面绘制时使用。
下一个特殊的路径函数叫做closePath。这个函数的行为同lineTo很像。唯一的差别在于closePath会将路径的起始坐标自动做为目标坐标。closePath还会通知canvas当前绘制的图形已经闭合或者形成了完全封闭的区域,这对将来的填充和描边都非常有用。
此时,可以在已有的路径中,继续创建其他的子路径,或者随时调用beginPath重新绘制新路径并完全清除之前的所有路径。
1 function createCanopyPath(context){ 2 //绘制树冠 3 context.beginPath(); 4 5 context.moveTo(-25, -50); 6 context.lineTo(-10, -80); 7 context.lineTo(-20, -80); 8 context.lineTo(-5, -110); 9 context.lineTo(-15, -110); 10 11 //树的顶点 12 context.lineTo(0, -140); 13 14 context.lineTo(15, -110); 15 context.lineTo(5, -110); 16 context.lineTo(20, -80); 17 context.lineTo(10, -80); 18 context.lineTo(25, -50); 19 20 //链接起点,闭合路径 21 context.closePath(); 22 }
从上面的代码中可以看到,我们用到的仍然是前面用过的移动和画线命令,只不过调用次数多了一些。这些线条表现树冠的轮廓,最后我们闭合了路径。
1 function drawTrails(){ 2 var canvas = document.getElementById('trails'); 3 var context = canvas.getContext('2d'); 4 5 context.save(); 6 context.translate(130, 250); 7 8 //创建表现树冠的路径 9 createCanopyPath(context); 10 11 //绘制当前路径 12 context.stroke(); 13 context.restore(); 14 }
以上代码已然很熟悉,先获取canvas的上下文对象,保存以便后续使用,将当前位置变换到新位置,画树冠,绘制到canvas上,最后恢复上下文的初始状态。
1 //加宽线条 2 context.lineWidth = 4; 3 4 //平滑路径的接合点 5 context.lineJoin = 'round'; 6 7 //将颜色改成棕色 8 context.strokeStyle = '#663300'; 9 10 //最后,绘制树冠 11 context.stroke();
设置上面这些属性可以改变以后将要绘制的图形外观,这个外观起码可以保持到我们将context恢复到上一个状态。
首先,我们将线条宽度加粗到4像素。
其次,我们将lineJoin属性设置为round,这是修改当前形状中线段的连接方式,让拐角变得更圆滑;也可以把lineJoin属性设置成bevel或者miter(相应的context.miterLimit值也需要调整)来变换拐角样式。
最后,通过strokeStyle属性改变了线条的颜色。
还有一个没有用到的属性——lineCap,可以把它的值设置为butt、square或者round,以此来指定线条末端的样式。示例中的线是闭合的,没有端点。
另一个常用于修改图形的方法是指定如何填充其路径和子路经。
1 //将填充色设置为绿色并填充树冠 2 context.fillStyle = '#339900'; 3 context.fill();
首先,我们将fillStyle属性设置成合适的颜色。然后,只要调用context的fill函数就可以让canvas对当前图形中所有的闭合路径内部的像素点进行填充。
由于我们是先描边后填充,因此填充会覆盖一部分描边路径。如果希望看到完整的描边路径,可以在绘制路径(调用context.stroke())之前填充(调用context.fill())。
1 //将填充色设置为棕色 2 context.fillStyle = '#663300'; 3 4 //填充用作树干的矩形区域 5 context.fillRect(-5, -50, 10, 50);
调用fillRect并设置x、y两个位置参数和宽度、高度两个大小参数,随后,Canvas会马上使用当前的样式进行填充。
与之相关的函数还有strokeRect和clearRect。strokeRect的作用是基于给出的位置和坐标画出矩形的轮廓,clearRect的作用是清除矩形区域内的所有内容并将它回复到初始状态,即透明色。
canvas动画
“在HTML5 Canvas API中,canvas的清除矩形功能是创建动画和游戏的核心功能。通过反复绘制和清除canvas片段,就可能实现动画效果,互联网上有许多这样的例子。但是如果希望创建运行起来比较流畅的动画,就需要使用剪裁(clipping)功能了,有可能还需要二次缓存canvas,以便最小化由于频繁的清除动作而导致的画面闪烁。”
canvas提供了一系列绘制曲线的函数,这里使用最简单的曲线函数——二次曲线。
1 //保存canvas的状态并绘制路径 2 context.save(); 3 4 context.translate(-10, 350); 5 context.beginPath(); 6 7 //第一条曲线向右上方弯曲 8 context.moveTo(0, 0); 9 context.quadraticCurveTo(170, -50, 260, -190); 10 11 //第二条曲线向右下方弯曲 12 context.quadraticCurveTo(310, -250, 410, -250); 13 14 //使用棕色的粗线条来绘制路径 15 context.strokeStyle = '#663300'; 16 context.lineWidth = 20; 17 context.stroke(); 18 19 //恢复之前的canvas状态 20 context.restore();
跟之前一样,第一步是保存当前canvas的context状态,因为我们即将变换坐标系并修改轮廓设置。
quadraticCurveTo函数绘制曲线的起点是当前坐标,带有两组(x, y)参数。第二组是值曲线的终点,第一组代表控制点(control point)。所谓的控制点位于曲线的旁边(不是曲线之上),其作用相当于对曲线产生一个拉力。通过调整控制点的位置,就可以改变曲线的曲率。
HTML5 Canvas API的其他曲线功能还涉及bezierCurveTo、atcTo和arc函数。这些函数通过多种控制点(如半径、角度等)让曲线更具有可塑性。
在canvas中显示图片非常简单。可以通过修正层为图片添加印章、拉伸图片或者修改图片等,并且图片通常会成为canvas上的焦点。
不过,图片增加了canvas操作的复杂度:必须等到图片完全加载后才能对其进行操作。浏览器通常会在页面脚本执行的同时异步加载图片。如果试图在图片未完全加载之前就将其呈现到canvas上,那么canvas将不会显示任何图片。因此,开发人员要特别注意,在呈现之前,应确保图片已经加载完毕。
示例中,为保证在呈现之前图片已完全加载,这里提供了回调,即仅当图像加载完成时才执行后续代码。
1 //加载图片bark.jpg 2 var bark = new Image(); 3 bark.src = "bark.jpg"; 4 5 //图片加载完成后,将其显示在canvas上 6 bark.onload = function(){ 7 drawTrails(); 8 }
上面的代码中,我们为bark.jpg图片添加了onload处理函数,以保证仅在图像加载完成时才调用主drawTrails函数。这样做可以保证后续的调用能够把图片正常显示出来。
1 //用背景图案填充作为树干的矩形 2 //the filled rectangle was before 3 context.drawImage(bark, -5, -50, 10, 50);
在drawImage函数中,除了图片本身外,还指定了x、y、width和height参数。这些参数会对贴图进行调整以适应预定的10×50像素树干区域。
渐变是指在颜色集上使用逐步抽样算法,并将结果应用于描边样式和填充样式中。使用渐变需要三个步骤:
(1)创建渐变对象;
(2)为渐变对象设置颜色,指明过渡方式;
(3)在context上为填充样式或者描边样式设置渐变。
可以将渐变看作是颜色沿着一条线进行缓慢地变化。
要设置颜色哪种颜色,在渐变对象上使用addColorStop函数即可。这个函数允许指定两个参数:颜色和偏移量。颜色参数指开发人员希望在偏移位置描边或填充时所使用的颜色。偏移量是一个0.0到1.0之间的数值,代表沿着渐变线渐变的距离有多远。
除了可以变换成其他颜色外,还可以为颜色设置alpha值(例如透明),并且alpha值也是可以变化的。例如内置alpha组件的CSS rgba函数。
1 //创建用作树干纹理的三阶水平渐变 2 var trunkGradient = context.createLinearGradient(-5, -50, 5, -50); 3 4 //树干的左侧边缘是一般程度的棕色 5 trunkGradient.addColorStop(0, '#663300'); 6 7 //树干中间偏左的位置颜色要淡一些 8 trunkGradient.addColorStop(0.4, '#996600'); 9 10 //树干右侧边缘的颜色要深一些 11 trunkGradient.addColorStop(1, '#552200'); 12 13 //使用渐变色填充树干 14 context.fillStyle = trunkGradient; 15 context.fillRect(-5, -50, 10, 50); 16 17 //接下来,创建垂直渐变,以用作树冠在树干上投影 18 var canopyShadow = context.createLinearGradient(0, -50, 0, 0); 19 20 //投影渐变的起点是透明度设为50%的黑色 21 canopyShadow.addColorStop(0, 'rgba(0, 0, 0, 0.5)'); 22 23 //方向垂直向下,渐变色在很短的距离内迅速渐变至完全透明 24 //这段长度之外的树干上没有投影 25 canopyShadow.addColorStop(0.2, 'rgba(0, 0, 0, 0.0)'); 26 27 //在树干上填充投影渐变 28 context.fillStyle = canopyShadow; 29 context.fillRect(-5, -50, 10, 50);
使用了两个渐变后,最终绘制出来的树干有了平滑的光照效果。
除了刚才用到的线性渐变以外,HTML5 Canvas API还支持放射性渐变,所谓放射性渐变就是颜色会介于指定圆间的锥形区域平滑变化。
1 createRadialGradient(x0, y0, r0, x1, y1, r1);
前三个参数代表以(x0, y0)为圆心,r0为半径的圆,后三个参数代表以(x1, y1)为圆心,r1为半径的另一个圆。渐变会在两个圆中间的区域出现。
这里将调用createPattern函数来替代之前的drawImage函数。
1 //加载砾石背景图 2 var gravel = new Image(); 3 gravel.src = "gravel.jpg"; 4 gravel.onload = function(){ 5 drawTrails(); 6 } 7 8 //用背景图替代棕色粗线条 9 context.strokeStyle = context.createPattern(gravel, 'repeat'); 10 context.lineWidth = 20; 11 context.stroke();
从上面的代码可以看出,绘制的时候还是使用stroke()函数,只不过这次我们先设置了context上的strokeStyle属性,把调用context.createPattern的返回值赋给该属性。再次强调一下,图片必须提前加载完毕,以便canvas执行后续操作。
context.createPattern的第二个参数是重复性标记:
repeat (默认值)图片会在两个方向平铺
repeat-x 横向平铺
repeat-y 纵向平铺
no-repeat 图片只显示一次,不平铺
我们计划把示例代码中用于绘制树的操作独立出来,当作一个单独的例程,成为drawTree。
1 //创建树对象绘制函数,以便重用 2 function drawTree(context){ 3 var trunkGradient = context.createLinearGradient(-5, -50, 5, 50); 4 trunkGradient.addColorStop(0, '#663300'); 5 trunkGradient.addColorStop(0.4, '#996600'); 6 trunkGradient.addColorStop(1, '#552200'); 7 context.fillStyle = trunkGradient; 8 context.fillRect(-5, -50, 10, 50); 9 10 var canopyShadow = context.createLinearGradient(0, -50, 0, 0); 11 canopyShadow.addColorStop(0, 'rgba(0, 0, 0, 0.5)'); 12 canopyShadow.addColorStop(0.2, 'rgba(0, 0, 0, 0.0)'); 13 context.fillStyle = canopyShadow; 14 context.fillRect(-5, -50, 10, 50); 15 16 createCanopyPath(context); 17 18 context.lineWidth = 4; 19 context.lineJoin = 'round'; 20 context.strokeStyle = '#663300'; 21 context.stroke(); 22 23 context.fillStyle = '#339900'; 24 context.fill(); 25 }
可以看到,drawTree函数包括了之前绘制树冠、树干和树干渐变的所有代码。为了在新的位置画出大一点的树,我们将使用另一种变换方式——缩放函数context.scale。
1 //在(130, 250)的位置绘制第一棵树 2 context.save(); 3 context.translate(130, 250); 4 drawTree(context); 5 context.restore(); 6 7 //在(260, 500)的位置绘制第二颗树 8 context.save(); 9 context.translate(260, 500); 10 11 //将第二颗树的宽高分别放大至原来的2倍 12 context.scale(2, 2); 13 drawTree(context); 14 context.restore();
scale函数带有两个参数来分别代表x、y两个纬度的值。
始终在原点执行图形和路径的变换操作
“示例中演示了为什么要在原点执行图形的路径的变换操作,执行完后再统一平移。理由就是缩放(scale)和旋转(rotate)等变换操作都是针对原点进行的。
如果对一个不在原点的图形进行旋转变换,那么rotate变换函数会将图形绕着原点旋转而不是在原地旋转。与之类似,如果进行缩放操作时没有将图形放置到合适的坐标上,那么所有路径坐标都会被同时缩放。取决于缩放比例的大小,新的坐标可能会全部超出canvas范围,进而给开发人员带来困惑,为什么我的缩放操作会把图像删除了?”
变换操作并不限于缩放和平移,我们可以使用函数context.rotate(angle)来旋转图像,甚至可以直接修改底层变换矩阵以完成一些高级操作,如剪切图像的绘制路径。
1 context.save(); 2 3 //旋转角度参数以弧度为单位 4 context.rotate(1.57); 5 context.drawImage(myImage, 0, 0, 100, 100); 6 7 context.restore();
下面演示如何对路径坐标进行随意变换,以从根本上改变现有树的路径显示,并最终创建一个阴影效果。
1 //创建用于填充树干的三阶水平渐变色 2 //保存canvas的当前状态 3 context.save(); 4 5 //X值随着Y值的增加而增加,借助拉伸变换 6 //可以创建一棵用作阴影的倾斜的树 7 //应用了变换以后 8 //所有坐标都与矩阵相乘 9 context.transform(1, 0, -0.5, 1, 0, 0); 10 11 //在Y轴方向,将阴影的高度压缩为原来的60% 12 context.scale(1, 0.6); 13 14 //使用透明度为20%的黑色填充树干 15 context.fillStyle = 'rgba(0, 0, 0, 0.2)'; 16 context.fillRect(-5, -50, 10, 50); 17 18 //使用已有的阴影效果重新绘制树 19 createCanopyPath(context); 20 context.fill(); 21 22 //恢复之前的canvas状态 23 context.restore();
你可以像上面那样直接修改context变换矩阵,前提是要熟悉二维绘图系统中的矩阵变换。分析这种变换背后的数学含义,可以看出我们通过调整与Y轴值对应的参数改变了X轴的值,这样做目的是为了拉伸一棵灰色的树做阴影。接下来,我们按照60%的比例将剪裁出的树缩小到了合适的尺寸。
context.transform(a, b, c, d, e, f);
a : 水平缩放绘图
b : 水平倾斜绘图
c : 垂直倾斜绘图
d : 垂直缩放绘图
e : 水平移动绘图
f : 垂直移动绘图
注意,剪裁过的“阴影”树会先被显示出来,这样一来,真正的树就会按照Z轴顺序(canvas中对象的重叠顺序)显示在阴影的上面。此外,树影的充分用到了CSS的RGBA特性,通过特性我们将透明度值设为正常情况下的20%。至此,带有半透明效果的树影就做好了。
操作canvas文本的方式与操作其他路径对象的方式相同:可以描绘文本轮廓和填充文本内部;同时,所有能够应用与其他图形的变换和样式都能用于文本。
context对象的文本绘制功能由两个函数组成:
fillText(text, x, y, maxwidth)
strokeText(text, x, y, maxwidth)
两个函数的参数完全相同,必选参数包括文本参数以及用于制定文本位置的坐标参数。maxwidth是个可选参数,用于限制字体大小,它会将文本字体强制收缩到指定尺寸。还有一个measureText函数可供使用,该函数会返回一个度量对象,其中包含了在当前context环境下指定文本的实际显示宽度。
font CSS字体字符串 例如:italic Arial, scans-serif
textAlign start、end、left、right、center 默认是start
textBaseLine top、hanging、middle、alphabetic、ideographic、bottom 默认是alphabetic
1 //在canvas上绘制标题文本 2 context.save(); 3 4 //字号为60px, 字体为impact 5 context.font = '60px impact'; 6 7 //将文本填充为棕色 8 context.fillStyle = '#996600'; 9 //将文本设为居中对齐 10 context.textAlign = 'center'; 11 12 //在canvas顶部中央的位置 13 //以大字体的形式显示文本 14 context.fillText('Happy Trails!', 200, 60, 400); 15 context.restore();
我们首先创建了一段使用Impact字体的大字号文本,然后使用已有的树皮图片座位背景进行填充。为了将文本置于canvas的上方并居中,我们定义了最大宽度和center对齐方式。
通过几个全局context属性来控制阴影
shadowColor 任何CSS中的颜色值 可以使用透明度(alpha)
shadowOffsetX 像素值 值为正数,向右移动阴影;值为负数,向左移动阴影
shadowOffsetY 像素值 值为正数,向下移动阴影;值为负数,向上移动阴影
shadowBlur 高斯模糊值 值越大,阴影边缘越模糊
shadowColor或者其他任意一项属性的值被赋为非默认值,路径、文本和图片上的阴影效果就会被触发。
1 //设置文字阴影的颜色为黑色,透明度为20% 2 context.shadowColor = 'rgba(0, 0, 0, 0.2)'; 3 4 //将阴影向右移动15px,向上移动10px 5 context.shadowOffsetX = 15; 6 context.shadowOffsetY = -10; 7 8 //轻微模糊阴影 9 context.shadowBlur = 2;
如你所见,由CSS生成的阴影只有位置上的变化,而无法与变换生成的阴影(树影)保持同步。为了一致起见,在canvas上绘制阴影时,应该尽量只用一种方法。