• HTML5 程序设计笔记(二)


    Canvas API

    1、HTML5 Canvas 概述
      1.1 历史

        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相对来说比较简单。

      1.2 Canvas 是什么

        在网页上使用canvas元素时,他会创建一块矩形区域。默认情况下该矩形区域宽为300像素,高为150像素,但也可以自定义具体的大小或者设置canvas元素的其他特性。

     1 <canvas></canvas> 

        在页面加入了canvas元素后,我们便可以通过javascript来自由的控制它。可以在其中添加图片、线条、以及文字,也可以在里面绘图,甚至还可以加入高级动画。

        使用canvas编程,首先要获取其上下文(context)。接着在上下文中执行动作,最后将这些动作应用到上下文中。可以将canvas的这种编辑方式想象成为数据库事务:开发人员先发起一个事务,然后执行某些操作,最后提交事务。

      1.3 canvas坐标

        canvas中的坐标是从左上角开始的,x轴沿着水平方向(按像素)向右延伸,y轴沿着垂直方向向下延伸。左上角的坐标为x=0,y=0的点称作原点。

      1.4 什么情况下不用canvas

        在某些情况下,如果其他元素已经够用了,就不应该再使用canvas元素。例如,用canvas元素在HTML页面中动态绘制所有不同的标题,就不如直接使用标题样式(H1、H2等),他们所实现的效果是一样的。

      1.5 替代内容

        访问页面的时候,如果浏览器不支持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。

      1.6 CSS 和canvas

        同大多数HTML元素一样,canvas元素也可以通过应用css的方式来增加边框,设置内边距、外边距等,而且一些css属性还可以被canvas内的元素继承。比如字体样式,在canvas内添加的文字,其样式默认同canvas元素本身是一样的。

        此外,在canvas中为context设置属性同样要遵从css语法。例如,对context应用颜色和字体样式,跟在任何HTML和CSS文档中使用的语法完全一样。

      1.7 浏览器对HTML5 Canvas的支持情况

        随着IE9的到来,所有浏览器厂商现在都提供了对HTML5 Canvas的支持,而且他已被大多数用户所掌握。

    2、使用HTML5 Canvas API
      2.1 检测浏览器支持情况

        在创建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的元素,通过以适当的信息更新该元素的内容,可以反应出浏览器的支持情况。

      2.2 在页面中加入canvas
    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。

        接下来,基于这个上下文执行画线的操作。代码中调用了三个方法——beginPathmoveTolineTo,传入了这条线的起点和终点的坐标。

        方法moveTo和lineTo实际上并不画线,而是在结束canvas操作的时候,通过调用context.stroke()方法完成线条的绘制。moveTo(x, y),将上下文移动到坐标指定点;lineTo(x, y),从指定点画线至目标点。

        从上面的代码可以看出,canvas中所有的操作都是通过上下文对象来完成的。只有当对路径应用绘制(stroke)填充(fill)方法时,结果才会显示出来。否则,只有在显示图像、显示文本或者绘制、填充和清除矩形框的时候,canvas才会马上更新。

      2.3 变换

         也许会认为使用变换增加了不必要的复杂性,但并非如此,其实变换是实现复杂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操作就不会被刚才的平移操作影响了。

      2.4 路径

        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上,最后恢复上下文的初始状态。

      2.5 描边样式
     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,可以把它的值设置为buttsquare或者round,以此来指定线条末端的样式。示例中的线是闭合的,没有端点。

      2.6 填充样式

        另一个常用于修改图形的方法是指定如何填充其路径和子路经。

    1 //将填充色设置为绿色并填充树冠
    2 context.fillStyle = '#339900';
    3 context.fill();

        首先,我们将fillStyle属性设置成合适的颜色。然后,只要调用context的fill函数就可以让canvas对当前图形中所有的闭合路径内部的像素点进行填充。

        由于我们是先描边后填充,因此填充会覆盖一部分描边路径。如果希望看到完整的描边路径,可以在绘制路径(调用context.stroke())之前填充(调用context.fill())。

      2.7 填充矩形区域
    1 //将填充色设置为棕色
    2 context.fillStyle = '#663300';
    3 
    4 //填充用作树干的矩形区域
    5 context.fillRect(-5, -50, 10, 50);

         调用fillRect并设置x、y两个位置参数和宽度、高度两个大小参数,随后,Canvas会马上使用当前的样式进行填充。

        与之相关的函数还有strokeRectclearRect。strokeRect的作用是基于给出的位置和坐标画出矩形的轮廓,clearRect的作用是清除矩形区域内的所有内容并将它回复到初始状态,即透明色。

        canvas动画

          “在HTML5 Canvas API中,canvas的清除矩形功能是创建动画和游戏的核心功能。通过反复绘制和清除canvas片段,就可能实现动画效果,互联网上有许多这样的例子。但是如果希望创建运行起来比较流畅的动画,就需要使用剪裁(clipping)功能了,有可能还需要二次缓存canvas,以便最小化由于频繁的清除动作而导致的画面闪烁。”

      2.8 绘制曲线

        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的其他曲线功能还涉及bezierCurveToatcToarc函数。这些函数通过多种控制点(如半径、角度等)让曲线更具有可塑性。

       2.9 在canvas中插入图片

        在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像素树干区域。

       2.10 渐变

        渐变是指在颜色集上使用逐步抽样算法,并将结果应用于描边样式和填充样式中。使用渐变需要三个步骤:

          (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为半径的另一个圆。渐变会在两个圆中间的区域出现。

       2.11 背景图

        这里将调用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      图片只显示一次,不平铺

       2.12 缩放canvas对象

          我们计划把示例代码中用于绘制树的操作独立出来,当作一个单独的例程,成为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范围,进而给开发人员带来困惑,为什么我的缩放操作会把图像删除了?”

       2.13 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%。至此,带有半透明效果的树影就做好了。

      2.14 Canvas 文本

        操作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对齐方式。

      2.15 应用阴影

        通过几个全局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上绘制阴影时,应该尽量只用一种方法。

  • 相关阅读:
    Composer autoload 自动加载
    权限问题
    加载适配器和布局之间的顺序关系--Unsolved
    listview和button
    线程练习中出现的错误
    线程02
    关于初始化成员变量
    可扩展列表
    Android开发中Handler的经典总结----转载
    线程01
  • 原文地址:https://www.cnblogs.com/lightxun/p/3824637.html
Copyright © 2020-2023  润新知