请你跟着本篇示例代码实现每个示例,30分钟后,你会高喊:“HTML5 Canvas?!在哥面前,那都不是事儿!

呵呵。不要被滚动条吓到,很多都是代码和图片。我没有分开写,不过上面给大家提供了目录,方便查看。

学习笔记,纯手工码字,有错别字什么的请指出,觉得好的请点个赞小小的支持下。谢谢亲们。


  本篇,我们将探索如何使用HTML5和Canvas API。Canvas API很酷,可以通过它来动态生成和展示图形、图表、图像以及动画。

  本篇将使用渲染API(Rendering API)的基本功能来创建一幅可以放大缩小并自适应浏览器环境的图。还会演示如何基于用户输入来动态创建图像,生成热点图。

  本篇只涉及了最基本的图形知识,因此,你大可不必蛋馨学不会而跳过。

  当然,我们也会提醒你在使用HTML5 Canvas时需要注意的问题,并且分享解决这些问题的方法。


目录:

1. HTML5 Canvas概述

1.1 历史

1.2 Canvas 是什么

1.3 Canvas 坐标

1.4 什么情况下不用Canvas

1.5 替代内容

1.6 CSS 和 Canvas

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

2. 使用HTML5 Canvas API

2.1  检测浏览器支持情况

2.2  在页面中加入 canvas

2.3  变换

2.4  路径

2.5  描边样式

2.6  填充样式

2.7  填充矩形区域

2.8  绘制曲线

2.9  在 canvas 中插入图片

2.10 渐变

2.11 背景图

2.12 缩放 canvas 对象

2.13 Canvas 变换

2.14 Canvas 文本

2.15 应用阴影

2.16 像素数据

2.17 Canvas 的安全机制

3. 案例:地图热点覆盖

1. HTML5 Canvas概述

  关于HTML5 Canvas API完全可以写一本书(还不会是一本很薄的书),所以在这里我们将讨论API中哪些我们认为最常用的功能。

1.1 历史

  Canvas的概念最初是由苹果公司提出的,用于在Mac OS X WebKit中创建控制板部件(Dashboard Widget)。在Canvas出现之前,开发人员若要在浏览器中使用绘图API,只能使用Adobe的Flash和SVG(Scalable Vector Graphics,可伸缩矢量图形)插件,或者只有IE才支持的VML(Vector Markup Language,矢量标记语言),以及其他一些稀奇古怪的Javascript技巧。

  假设我们要在没有Canvas元素的条件下绘制一条对角线——听起来似乎很简单,但实际上如果没有一套二维绘图API的话,这会是一项相当复杂的工作。HTML5 Canvas能够提供这样的功能,对浏览器端来说此功能非常有用,因此Canvas被纳入了HTML5规范,

  起初,苹果公司曾暗示可能会为WHATWG(Web Hypertext Application Technology Working Group,Web超文本应用技术工作组)草案中的Canvas规范申请知识产权,这在当时引起了一些Web标准化追随者的关注。不过,苹果公司最终还是按照W3C免版税专利权许可条款公开了其专利。

【SVG和Canvas对比】

  “Canvas本质上是一个位图画布,其上绘制的图形是不可缩放的,不能像SVG图像那样可以被放大缩小。此外,用Canvas绘制出来的对象不属于页面DOM结构或者任何命名空间——这点被认为是一个缺陷。SVG图像却可以在不用的分辨率下流畅地缩放,并且支持单击检测(能检测到鼠标单击了图像上的哪个点)。

  既然如此,为什么WHATWG的HatML5规范不适用SVG呢?尽管Canvas有明显的不足,但HTML Canvas API有两方面有事可以弥补:首先,不需要讲所绘制图像中的每个图元当做对象存储,因此执行性能非常好;其次,在其他编程语言现有的优秀二维绘图API的基础上实现Canvas API相对来说比较简单。毕竟,二鸟在林不如一鸟在手。”

——Peter

1.2 Canvas 是什么

  在网页上使用Canvas元素时,她会创建一块矩形区域。默认情况下该矩形区域宽为300像素,高为150像素。但也可以自定义具体的大小或者设置Canvas元素的其他特性。下述代码是可放到HTML页面中的最基本的Canvas元素:

 <canvas></canvas>

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

  大多数主流操作系统和框架支持的二维绘制操作,HTML5 Canvas API都支持。如果你在近年来曾经有改过二维图形编程的经验,那么会对HTML5 Canvas API柑橘而非常顺手,因为这个API就是参照既有系统设计的。如果没有这方面经验,则会发现与这么多年来一直使用的图片加CSS开发Web图形的方式比起来,Canvas的渲染系统有多么强大。

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

1.3 Canvas 坐标

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

1.4 什么情况下不用Canvas

  尽管Canvas元素功能非常强大,用处也很多,但在某些情况下,如果其他元素已经够用了,就不应该再使用Canvas元素。例如,用Canvas元素在HTML页面中动态绘制所有不同的标题,就不如直接使用标题样式标签(H1、H2等),它们所实现的效果是一样的。

1.5 替代内容

  访问页面的时候,如果浏览器不支持Canvas元素,或者不支持HTML5 Canvas API中的某些特性,那么开发人员最好提供一份替代代码。例如,开发人员可以通过一张替代图片或者一些说明性的文字告诉访问者,使用最新的浏览器可以获得更加的浏览效果。下述代码展示了如何在Canvas中指定替代文本,当浏览器不支持Canvas的时候会显示这些替代内容:

     <canvas>
         为支持Canvas,请更新你的<a href="www.firefox.com">浏览器</a>!
     </canvas>

  出了上面代码中的文本外,同样还可以使用图片,不论是文本还是图片都会在浏览器不支持Canvas元素的情况下显示出来。

【Canvas元素的可访问性怎么样】

  “提供替代图像或者替代文本引出了可访问性这个话题——很遗憾,这是HTML5 Canvas规范中明显的缺陷。例如,没有一种原生方法能够自动为已插入到Canvas中的图片生成用于替换的文字说明。同样,也没有原生方法可以生成替代文字以匹配由Canvas Text API动态生成的文字。不过已经有工作组开始着手这方面的设计了,让我们一起期待吧。”

——Peter

  关于如何处理可替代且可访问的Canvas内容,HTML5设计者的当前提议之一是使用前述的备用内容部分。不过,为了让Canvas的备用内容对屏幕阅读器和其他可访问性工具也有用处,它要能够支持键盘导航,即便是在浏览器支持Canvas且可正常显示的情况下也是如此。尽管一些浏览器已经支持这项功能,但你不应该依赖于浏览器来支持用户的特殊需求。

  现阶段,我们推荐使用页面上的独立部分来展示Canvas的替代内容。还有一个额外的因素,许多用户可能喜欢使用替代的控件或者期望一种更好的展示方式,以便他们可以快速理解和操纵页面或应用。

  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 IE10..的到来,所有浏览器厂商现在都提供了HTML5 Canvas的支持,而且它已被大多数用户所掌握。这是Web开发史上一个重要的里程碑,它使得2D绘图在现代网络上蓬勃发展。

  尽管旧版本IE占有的市场份额正在逐渐减小,但在使用Canvas API之前,我们还是应该首先检测当前浏览器是否支持HTML5 Canvas。 2.1将展示如何变成检测浏览器支持情况。

2. 使用HTML5 Canvas API

  下面将深入探讨HTML5 Canvas API。为此,我们将使用各种HTML5 Canvas API创建一幅类似于Logo的图像,图像时森林场景,有树,还有适合长跑比赛的美丽跑到。虽然这个示例从平面设计的角度来看毫无竞争力,但却可以合理演示HTML5 Canvas的各种功能。

2.1  检测浏览器支持情况

  在创建HTML5 Canvas元素之前,首先要确保浏览器能够支持它,如果不支持,你就要为哪些古董级浏览器提供一些替代文字,下列代码就是检测浏览器支持情况的一种方法:

     <p id="support"></p>

     <script type="text/javascript">
         try {
             document.createElement("canvas").getContext("2d");
             document.getElementById("support").innerHTML = "恭喜,您的浏览器支持HTML5 Canvas!"
         } catch (e) {
             document.getElementById("support").innerHTML = "抱歉,您的浏览器不支持HTML5 Canvas,请及时更新您的浏览器!";
         }
     </script>

编码方式检测浏览器对于HTML5 Canvas的支持情况

  上面的代码试图创建一个Canvas对象,并且获取其上下文,如果发生错误,则可以捕获错误,进而得知该浏览器不支持Canvas。页面中预先放入了ID为support的元素,通过以适当的信息更新该元素的内容,可以反映出浏览器的支持情况。

  以上代码能判断浏览器是否支持Canvas元素,但不会判断具体支持Canvas的哪些特性。不过本篇所有代码使用的API已经很稳定并且各浏览器也都提供了很好的支持,所以通常不必担心这个问题。

  此外,希望开发人员能够像上述代码一样为Canvas元素提供备用显示内容。

2.2  在页面中加入 canvas

  在HTML页面中插入Canvas元素非常直观,下列代码就是一段可以被插入到HTML页面中的Canvas代码:

 <canvas height="200" width="200"></canvas>

  以上代码会在叶敏上显示出一块200*200像素的“隐藏”区域,加入要为其增加一个边框,可以像如下代码一样,用标准CSS边框属性来设置:

 <canvas id="diagonal" style="border:1px solid black;" height="200" width="200"></canvas>

  注意:上面的代码增加了一个值为“diagonal”的ID特性,这么做的用意在于以后的开发过程中可以通过ID来快速找到该Canvas元素。对于任何Canvas对象来说,ID特性都是特别重要的,因为对Canvas元素的所有操作都是通过脚本代码控制的,没有ID的话,想要找到要操作的Canvas元素会很难。

  上述代码在浏览器中的执行效果如图:

  看起来好像没什么,但是就像那些艺术家说的,一张白纸可以画出最新最美的图画。现在,就让我们在这张“白纸”上作画吧。前面说过,在没有HTML5 Canvas的情况下,很难在页面上绘制一条对角线。现在我们来看看,有了Canvas以后,同样的事情会有多么简单。

  从下面的示例代码中可以看到,基于上面绘制好的画布,仅仅使用几行代码就可以画出一条对角线:

     <canvas id="diagonal" style="border:1px solid black;" height="200" width="200"></canvas>

     <script>
         function DrawDiagonal() {
             // 取得Canvas元素及其绘图上下文
             var canvas = document.getElementById("diagonal");
             var context = canvas.getContext("2d");

             // 用绝对坐标来创建一条路径
             context.beginPath();
             context.moveTo(70, 140);
             context.lineTo(140, 70);

             // 将这条线绘制到Canvas上
             context.stroke();
         }

         window.addEventListener("load", DrawDiagonal, true);
     </script>

在Canvas中绘制一条对角线

  上述代码运行效果如图:

  仔细看一下上面这段绘制对角线的Javascript代码,虽然简单,它却展示出了使用HTML5 Canvas API的重要流程。

  首先通过引用特定的Canvas ID值来获取对Canvas对象的访问权。这段代码中ID就是diagonal,接着定义一个context变量,调用canvas对象的getContext方法,并传入希望使用的canvas类型。代码中通过传入"2d"来获取一个二维上下文,这也是到目前为止唯一可用到的上下文。

  注意:大部分工作已经由Canvas上下文的3D版本完成,经过浏览器厂商和Khronos工作组的共同努力,WebGL规范在2011年年初发布了1.0版本,WebGL所基于的概念和设计与流程的OpenGl库是相同的,它为HTML5和Javascript提供相似的API,如果你想在支持WebGL的浏览器中创建3D绘图上下文,只需要将字符串"webgl"作为getContext函数的参数传入即可。作为结果返回的上下文包含一套全新的绘图API,其详细和复杂程度足够在写一本书来专门描述它,虽然一些浏览器厂商目前正致力于实现对WebGL的支持,但并非所有的浏览器厂商都已经开始行动。不过,Web上3D渲染的潜力相当引人注目,我们期待着在未来几年里浏览器厂商能够快速提升其支持力度。欲了解更多信息,可以查阅Khronos工作组网站上关于WebGL的描述(http://www.khronos.org/webgl)。

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

  方法moveTo和lineTo实际上并不画线,而是在结束canvas操作的时候,通过调用context.stroke()方法来完成线条的绘制。

  虽然这条简单的线段怎么也想象不到最新最美的图画,不过与以前的拉伸图像、怪异的CSS和DOM对象以及其他怪异的实现形式相比,使用基本的HTML技术在任意两点间绘制一条线段已经是非常大的进步了。从现在开始,就把那些怪异的做法永远忘掉吧。

  从上面的代码中可以看到,canvas中所有的操作都是通过上下文对象来完成的,在以后的canvas编程中也一样,因为所欲设计视觉输出效果的功能都只能通过上下文对象而不是画布对象来使用。这种设计使canvas拥有了良好的可扩展性,基于从其中抽象出来的上下文类型,canvas将来可以支持多种绘制模型,虽然本节经常提到对canvas采取什么样的操作,但你应该明白,我们实际操作的是画布所提供的上下文对象。

  如前面示例演示的那样,对上下文的很多操作都不会立即反映到页面上,beginPath、moveTo以及lineTo这些函数都不会直接修改canvas的展示结果。canvas中很多用于设置样式和外观的函数也同样不会直接修改显示结果。只有当路径应用绘制(stroke)或填充(fill)方法时,结果才会显示出来。否则,只有在显示图像、显示文本或者绘制、填充和清除矩形框的时候,canvas才会马上更新。

2.3  变换

  现在我们探讨一下在canvas上绘制图像的另一种方式——使用变换(transformation)。接下来的代码显示的结果跟上面是一样的,只是绘制对角线的代码不一样。这个简单示例可能会让你误以为使用变换增加了不必要的复杂性。事实并非如此,其实变换是实现复杂canvas操作的最好方式。在后面的示例中你将会看到,我们使用了大量的变换,而这对熟悉HTML5 Canvas API的复杂功能是至关重要的。

  也许了解变换最简单的方法(至少这种方法不涉及大量的数学公式,也不需手足并用地去解释)就是把它当成是介于开发人员发出的指令和canvas显示结果之间的一个修正层(modification layer)。不管在开发中是否使用变换,修正层始终都是存在的。

  修正——在绘制系统中的说法是变换——在应用的时候可以被顺序应用、组合或者随意修改。每个绘制操作的结果显示在canvas上之前都需要经过修正层去做修正。虽然这么做增加了额外的复杂性,但却为绘制系统添加了更为强大的功能,可以像目前主流图像编辑工具那样支持实时图像处理,所以API中这部分内容的复杂性是必要的。

  不在代码中调用变换函数并不意味着可以提升canvas的性能。canvas在执行的时候,变换会被呈现引擎隐式调用,这与开发人员是否直接调用无关。在接触最基本的绘制操作之前,提前了解系统背后的原理至关重要。

  关于可重用代码有一条重要的建议:一般绘制都应从原点(坐标系中的0,0点)开始,应用变换(缩放、平移、旋转等),然后不断修改代码直至达到希望的效果,如图:

  下列代码展示了如何使用最简单的变换方法——translate函数:

     <canvas id="diagonal" style="border:1px solid black;" height="200" width="200"></canvas>

     <script>
         function DrawDiagonal() {
             // 取得Canvas元素及其绘图上下文
             var canvas = document.getElementById("diagonal");
             var context = canvas.getContext("2d");

             context.save(); // 保存当前绘图状态

             context.translate(70, 140); // 向右下方移动绘图上下文

             // 以原点为起点,绘制与前面相同的线段
             context.beginPath();
             context.moveTo(0, 0);
             context.lineTo(70, -70);

             context.stroke();   // 将这条线绘制到Canvas上

             context.restore();  // 恢复所有的绘图状态
         }

         window.addEventListener("load", DrawDiagonal, true);
     </script>

用变换的方式在canvas上绘制对角线

  运行结果和之前直接绘制一样,但这次绘制使用了强大的变换功能,学习完接下来的内容,就会明白变换的强大之处:

  我们详细研究一下上面这段通过平移方式绘制对角线的Javascript代码:

  1. 首先,通过ID找到并访问Canvas对象。(ID是diagonal)
  2. 接着通过调用canvas对象的getContext函数获取上下文对象
  3. 接下来,保存尚未修改的context,这样即使进行了绘制和变换操作也可以恢复到初始状态。如果不保存,那么在进行了平移和缩放等操作以后,其影响会逮到后续的操作中,而这不一定是我们所希望的。在变换前保存context状态可以方便以后恢复。
  4. 下一步是在context中调用translate函数。通过这个操作,当平移行为发生的时候,我们提供的变换坐标会被追加到结果坐标(对角线)上,结果就是讲要绘制的对角线移动到了新的位置上。不过对角线呈现在canvas上是在绘制操作结束之后。
  5. 应用平移后,就可以使用普通的绘制操作来画对角线了。代码中调用了三个函数来绘制对角线——beginPath、moveTo以及lineTo。绘制的起点是原点(0,0),而非坐标点(70,140)。
  6. 在线条勾画出来之后,可以通过调用context、stroke()函数将其显示在canvas上。
  7. 最后,恢复context至原始状态,这样后续的canvas操作就不会被刚才的平移操作影响了。

2.4  路径

  关于绘制线条,我们还能提供很多有创意的方法,不过现在应该进一步学习稍复杂点的图形:路径。HTML5 Canvas API中的路径代表你希望呈现的任何形状。本篇对角线实例就是一条路径,你可能已经注意到了,代码中调用beginPath就说明要开始绘制路径了。实际上,路径可以要多复杂有多复杂:多条线、曲线段,甚至是子路线。如果想在Canvas上绘制任意形状,那么你需要重点关注路径API。

  按照惯例,不论开始绘制任何图形,第一个需要调用的就是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重新绘制新路径并完全清楚之前的所有路径。

  跟了解所有复杂系统一样,最好的方式还是实践。现在,我们先不管那些线条的例子,使用HTML5 Canvas API开始创建一个新场景——带有长跑跑道的树林。权且把这个团当成是我们长跑比赛的标志吧。同其他的画图方式一样,我们将从基本元素开始,在这幅图中松鼠的树冠最简单。下列代码演示了如何在canvas上绘制一颗松树的树冠:

         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);
             context.closePath();    // 连接起点,闭合路径
         }

用于绘制树冠轮廓的Javascript函数

  从上面的代码中可以看到,我们用到的仍然是前面用过的移动和画线命令,只不过调用次数多了一些。这些线条表现的是树冠的轮廓,最后我们闭合了路径。我们为这棵树的地步流出了足够的空间,后面将在这里的空白处画上树干。下俩代码演示如何使用树冠绘制函数将树的简单轮廓呈现到canvas上:

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             //绘制当前路径
             context.stroke();
             context.restore();
         }

在Canvas上画树的Javascript函数

  这段代码中所有的调用相比大家已经很熟悉了,先获取Canvas的上下文对象,保存以便后续使用,将当前位置变换到新位置,画树冠,绘制到canvas上,最后回复上下文的初始状态。下面是完整代码:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="300"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);
             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             //绘制当前路径
             context.stroke();
             context.restore();
         }
         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

使用HTML5 Canvas路径绘制松树 完整代码

  运行效果如下:

2.5  描边样式

  如果开发人员只能绘制直线,而只能使用黑色,HTML5 Canvas API就不会如此强大和流行。下面我们就使用描边样式让树冠看起来更像是树。下列代码展示了一些基本命令,其功能是通过修改context的属性,让绘制的图形更好看:

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色

使用描边样式

  设置上面的这些属性可以改变以后将要绘制的图形外观,这个外观起码可以保持到我们将context恢复到上一个状态。

  首先,我们将线条宽度加粗到4像素。

  其次,我们将lineJoin属性设置为round,这是修改当前形状中线段的链接方式,让拐角变得更圆滑;也可以吧lineJoin属性设置为bevel或者miter(相应的context.miterLimit值也需要调整)来变换拐角样式。

  最后,通过strokeStyle属性改变了线条的颜色。在这个例子中,我们使用了CSS值来设置颜色,不过在后面几节中,我们将看到strokeStyle的值还可以用于生成特殊效果的团或者渐变色。

  还有一个没有用到的属性——lineCap,可以把它的值设置为butt、squre或者round,以此来指定线条末端的样式。哦,实例中的线是闭合的,没有断点。

  应用了上述描边样式后的完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="300"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);

             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色

             //绘制当前路径
             context.stroke();
             context.restore();
         }
         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

使用描边样式后的完整代码

  上述代码运行结果如图:

2.6  填充样式

  正如你所期望的那样,能影响canvas的图形外观的并非只有描边,另一个常用语修改图形的方法是指定如何填充其路径和子路径,如下列代码所示,用宜人的绿色填充树冠有多么简单:

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

使用填充样式

  首先,我们将fillStyle属性设置成合适的颜色。(在后面,我们将看到还可以使用渐变色或者图案填充)然后,只要调用context的fill函数就可以让canvas对当前图形中所有的闭合路径内部的像素点进行填充,如图:

  填充后的完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="300"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);

             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色

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

             //绘制当前路径
             context.stroke();
             context.restore();
         }
         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

应用填充后的完整代码

  由于我们是先描边后填充,因此填充会覆盖一部分描边路径。我们实例中的路径是4像素宽,这个宽度是沿路径线居中对齐的,而填充是把路径轮廓内部所有像素全部填充,所以会覆盖描边路径的一半。如果希望看到完整的描边路径,可以在绘制路径(调用context.stroke())之前填充(调用context.fill())。

2.7  填充矩形区域

  每棵树都有一个强壮的树干。我们在原始图形中为树干预留了足够的空间。如下列代码所示,通过fillRect函数可以画出树干:

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

  在上面的代码中,再次将棕色作为填充色。不过跟上次不一样的是,我们不用lineTo功能显示画树干的边角,而是使用fillRect一步到位画出整个树干。调用fillRect并设置x、y两个位置参数和宽度、高度两个大小参数,随后,Canvas会马上使用当前的样式进行填充。

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

【Canvas动画】

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

——Brian

  填充树干后的完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="300"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);

             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色

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

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

             //绘制当前路径
             context.stroke();
             context.restore();
         }
         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

附上树干之后的完整代码

  上述代码效果图如下:

2.8  绘制曲线

  这个世界,特别是自然界,并不是只有直线和矩形。Canvas提供了一系列绘制曲线的函数。我们将用最简单的曲线函数——二次曲线,来绘制我们的林荫小路。下列代码演示了如何添加两条二次曲线:

             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 使用棕色的粗线条来绘制路径
             context.strokeStyle = "#663300";
             context.lineWidth = 20;
             context.stroke();

             context.restore();  // 恢复之前的Canvas状态

绘制曲线的示例代码

  跟以前一样,第一步要做的事情是保存当前Canvas的context状态,因为我们即将变换坐标系并修改轮廓设置。要花林荫小路,首先要把坐标恢复到修正层的原点,向右上角画一条曲线。

  从下图可以看到,第一组代表控制点(Control Point),所谓的控制点位于曲线旁边(不是曲线上),其作用相当于对区县产生一个拉力。通过调整控制点的位置,就可以改变曲线的曲率。在右上方再画一条一样的曲线,以形成一条路。然后,像之前描边树冠一样把这条路会知道Canvas上(只是线条更粗了):

  HTML5 Canvas API其他曲线功能还设计bezierCurveTo、arcTo和arc函数。这些函数通过多种控制点(如半径、角度等)让曲线更具可塑性。

  添加小路后的完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="500"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);

             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色

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

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

             //绘制当前路径
             context.stroke();
             context.restore();

             DrawQuadraticCurve(context);    // 绘制曲线的函数
         }

         // 绘制曲线的函数
         function DrawQuadraticCurve(context) {
             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 使用棕色的粗线条来绘制路径
             context.strokeStyle = "#663300";
             context.lineWidth = 20;
             context.stroke();

             context.restore();  // 恢复之前的Canvas状态
         }
         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

添加小路后的完整代码

  运行结果如下:

2.9  在 canvas 中插入图片

  在Canvas中显示图片非常简单。可以通过修正层为图片添加印章、拉伸图片或者修改图片等,并且图片通常会成为Canvas上的焦点。用HTAML5 Canvas API内置的几个简单命令可以轻松地为Canvas添加图片内容。

  不过,图片增加了Canvas操作的复杂度:必须等到图片完全加载后才能对齐进行操作。浏览器通常会在页面执行的同事异步加载图片。如果试图在图片未完全加载之前就将其呈现到Canvas上,那么canvas将不会显示任何图片。因此,开发人员要特别注意,在呈现之前,应确保图片已经加载完毕。

  本篇的示例将加载一张树皮纹理的图片作为树干以供canvas使用。为保证在呈现之前图片已完全加载,我们提供了回调,即仅当图像加载完成时待执行后续代码,加载图像的代码如下:

         var bark = new Image();
         bark.src = "bark.jpg";

         // 图片加载完成后,将其显示在canvas上
         bark.onload = function () {
             DrawTrails();
         }

加载图像

  从上面的代码中可以看到,我们为bark.jpg图片添加了onload处理函数,以保证仅在图像加载完成时才调用DrawTrails函数。这样做可以保证后续的调用能够把图片正常显示出来,填充图片的代码为: context.drawImage(bark, -5, -50, 10, 50); 。

  这段代码里,我们用纹理贴图替换了之前调用fillRect函数的填充来作为新的树干。尽管替换的动作很小,但canvas上显示出来的树干更有质感。注意,在drawImage函数中,出了图片本身外,还制定了x、y、width和height参数。这些参数会对贴图进行调整以适应预定的10*50像素树干区域。我们还可以把原图的尺寸传进来,以便在裁切区域内对图片进行更多控制。

  加载图片后的完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="500"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);

             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             context.save();
             context.translate(130, 250);
             CreateCanopyPath(context);  // 创建表现树冠的路径

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色

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

             context.fillStyle = "#663300";  // 将填充色设置为棕色
             // 用背景图案填充作为树干的矩形
             context.drawImage(bark, -5, -50, 10, 50);

             //绘制当前路径
             context.stroke();
             context.restore();

             DrawQuadraticCurve(context);    // 绘制曲线的函数
         }

         // 绘制曲线的函数
         function DrawQuadraticCurve(context) {
             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 使用棕色的粗线条来绘制路径
             context.strokeStyle = "#663300";
             context.lineWidth = 20;
             context.stroke();

             context.restore();  // 恢复之前的Canvas状态
         }

         var bark = new Image();
         bark.src = "bark.jpg";

         // 图片加载完成后,将其显示在canvas上
         bark.onload = function () {
             DrawTrails();
         }
         //window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

使用加载后的图片填充树干的完整代码

  运行效果:

2.10 渐变

  对树干还是不满意?其实我也是。我们使用另一种可以让树干变得稍微好看点的绘制方法:渐变。渐变是指在颜色集上使用逐步抽样算法,并将结果应用于描边样式和填充样式中。使用渐变需要三个步骤:

  1. 创建渐变对象
  2. 为渐变对象设置颜色,指明过度方式
  3. 在context上为填充样式或者描边样式设置渐变

  可以将渐变看做是颜色沿着一条线进行缓慢的变化。例如,如果为渐变对象提供了A、B两个点,不论是绘制还是填充,只要从A移动到B,都会带来颜色的变化。

  要设置显示哪种颜色,在渐变对象上使用addColorStop函数即可。这个函数允许指定两个参数:颜色和偏移量。颜色参数是指开发人员希望在偏移位置描边或填充时所使用的颜色。偏移量是一个0.0到1.0之间的数值,代表沿着渐变线渐变的距离有多远。

  加入要建立一个从点(0,0)到点(0,100)的渐变,并制定在0.0便宜位置使用白色,在1.0偏移位置使用黑色。当使用绘制或者填充的动作从(0,0)画到(0,100)后,就可以看到颜色从白色(起始位置)渐渐转变成了黑色(终止位置)。

  出了可以变换成其他颜色外,还可以为颜色设置Alpha值(例如透明),并且Alpha值也是可以变化的。为了达到这样的效果,需要使用颜色值的另一种方法,例如内置Alpha组件的CSS rgba函数。

  下面我们通过实例来详细了解如何使用两个渐变来填充(相应的函数为fillRect)矩形区域,并形成最终的树干,使用渐变的代码如下:

             var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);  // 创建用作树干纹理的三阶水平渐变
             trunkGradient.addColorStop(0, "#663300");   // 树干的左侧边缘是一般程度的棕色
             trunkGradient.addColorStop(0.4, "#996600"); // 树干中间偏左的位置颜色要淡一些
             trunkGradient.addColorStop(1, "#552200");   // 树干右侧边缘的颜色要深一些

             // 使用渐变色填充树干
             context.fillStyle = trunkGradient;
             context.fillRect(-5, -50, 10, 50);

             var canopyShadow = context.createLinearGradient(0, -50, 0, 0);  // 创建垂直渐变,以用作树冠在树干上投影
             canopyShadow.addColorStop(0, "rgba(0,0,0,0.5)");    // 投影渐变的起点是透明度设为50%的黑色

             canopyShadow.addColorStop(0.2, "rgba(0,0,0,0.0)");  // 方向垂直向下,渐变色在很短的距离内村苏渐变至完全透明,这段长度之外的树干上没有投影

             // 在树干上填充投影渐变
             context.fillStyle = canopyShadow;
             context.fillRect(-5, -50, 10, 50);

使用渐变颜色填充树干,实现阴影效果的代码

  效果如下:

  出了我们刚才用到的线性渐变以外,HTML5 Canvas API还支持放射性渐变,所谓放射性渐变就是颜色会介于两个指定圆间的锥形区域平滑变化。放射性渐变和现行渐变使用的颜色终止点是一样的,不过参数如下: createRadialGradient(x0, y0, r0, x1, y1, r1) 。

  代码中,前三个参数代表以(x0,y0)为圆心,r0为半径的圆,后三个参数代表以(x1,y1)为圆心,r1为半径的另一个圆。简便会在两个圆中间的区域出现。

2.11 背景图

  直接绘制图像有很多用处,但在某些情况下,像CSS那样使用图片作为背景也非常有用。我们已经了解了如何使用加粗的颜色描边和填充。在描边和填充的时候,HTML5 Canvas API还支持图片平铺。

  现在我们把林荫小路变得崎岖一点。这次不再对曲线跑道进行描边,而是使用背景图片填充的方法。为了达到预想的效果,我们将已经作废的树干图片(我们已经有了“渐变”树干)替换成砾石图片。我们将调用createPattern函数来替代之前的drawImage函数,代码如下:

             // 加载砾石背景图
             var gravel = new Image();
             gravel.src = "gravel.jpg";
             gravel.onload = function () {
                 // 用背景图替代棕色粗线条
                 context.strokeStyle = context.createPattern(gravel, "repeat");
                 context.lineWidth = 20;
                 context.stroke();
             }

使用背景图片填充小路

  效果如下:

  从上面的代码中可以看到,绘制的时候还是使用stroke()函数,只不过这次我们先设置了context上的strokeStyle属性,把调用context.createPattern的返回值赋给该属性。再次强调一下,图片必须提前加载完毕,一百年canvas执行后续操作。context.createPattern的第二个参数是重复性标记,可以在下表中选择合适的值:

重复性参数
平铺方式 意义 平铺方式 意义
repeat (默认值)图片会在两个方向平铺 repeat-x 横向平铺
repeat-y 纵向平铺 no-repeat 图片只显示一次,不平铺

2.12 缩放 canvas 对象

  树林里怎么可能只有一棵树呢?现在我们来解决这个问题。为简单起见,我们计划吧示例代码中用于绘制树的操作独立出来,当做一个单独的例程,成为DrawTree,如代码:

         // 创建树对象绘制函数,以便重用
         function DrawTree(context) {
             var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);  // 创建用作树干纹理的三阶水平渐变
             trunkGradient.addColorStop(0, "#663300");   // 树干的左侧边缘是一般程度的棕色
             trunkGradient.addColorStop(0.4, "#996600"); // 树干中间偏左的位置颜色要淡一些
             trunkGradient.addColorStop(1, "#552200");   // 树干右侧边缘的颜色要深一些

             // 使用渐变色填充树干
             context.fillStyle = trunkGradient;
             context.fillRect(-5, -50, 10, 50);

             var canopyShadow = context.createLinearGradient(0, -50, 0, 0);  // 创建垂直渐变,以用作树冠在树干上投影
             canopyShadow.addColorStop(0, "rgba(0,0,0,0.5)");    // 投影渐变的起点是透明度设为50%的黑色

             canopyShadow.addColorStop(0.2, "rgba(0,0,0,0.0)");  // 方向垂直向下,渐变色在很短的距离内村苏渐变至完全透明,这段长度之外的树干上没有投影

             // 在树干上填充投影渐变
             context.fillStyle = canopyShadow;
             context.fillRect(-5, -50, 10, 50);

             CreateCanopyPath(context);

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色
             context.stroke();

             // 将填充色设置为绿色并填充树冠
             context.fillStyle = "#339900";
             context.fill();
         }

绘制树对象的函数

  可以看到,drawTree函数包括了之前绘制树冠、树干和树干渐变的所有代码。为了在新的位置画出大一点的树,我们将使用另一种变换方式——缩放函数context.scale,如下列代码所示:

             // 在(130,250)的位置绘制第一棵树
             context.save();
             context.translate(130, 250);
             DrawTree(context);
             context.restore();

             // 在(260,500)的位置绘制第二颗树
             context.save();
             context.translate(260, 500);

             // 将第二颗树的宽高分别放大至原来的2倍
             context.scale(2, 2);
             DrawTree(context);
             context.restore(); 

             //绘制当前路径
             context.stroke();
             context.restore();

绘制树对象

  scale函数带有两个参数来粉笔而代表在x、y两个维度的值。每个参数在canvas显示图像的时候,向其传递在本方向轴上图像要放大(或者缩小)的量。如果x值为2,就代表所绘制图像中全部元素都会变成两倍宽,如果y值为0.5,绘制出来的图像全部元素会变成之前的一半高。使用这些函数,就可以方便地在canvas上创建出新的树。

  完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="550"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);
             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             // 在(130,250)的位置绘制第一棵树
             context.save();
             context.translate(130, 250);
             DrawTree(context);
             context.restore();

             // 在(260,500)的位置绘制第二颗树
             context.save();
             context.translate(260, 500);

             // 将第二颗树的宽高分别放大至原来的2倍
             context.scale(2, 2);
             DrawTree(context);
             context.restore(); 

             //绘制当前路径
             context.stroke();
             context.restore();

             DrawQuadraticCurve(context);    // 绘制曲线的函数
         }

         // 绘制曲线的函数
         function DrawQuadraticCurve(context) {
             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 加载砾石背景图
             var gravel = new Image();
             gravel.src = "gravel.jpg";
             gravel.onload = function () {
                 // 用背景图替代棕色粗线条
                 context.strokeStyle = context.createPattern(gravel, "repeat");
                 context.lineWidth = 20;
                 context.stroke();
             }

             context.restore();  // 恢复之前的Canvas状态
         }

         // 创建树对象绘制函数,以便重用
         function DrawTree(context) {
             var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);  // 创建用作树干纹理的三阶水平渐变
             trunkGradient.addColorStop(0, "#663300");   // 树干的左侧边缘是一般程度的棕色
             trunkGradient.addColorStop(0.4, "#996600"); // 树干中间偏左的位置颜色要淡一些
             trunkGradient.addColorStop(1, "#552200");   // 树干右侧边缘的颜色要深一些

             // 使用渐变色填充树干
             context.fillStyle = trunkGradient;
             context.fillRect(-5, -50, 10, 50);

             var canopyShadow = context.createLinearGradient(0, -50, 0, 0);  // 创建垂直渐变,以用作树冠在树干上投影
             canopyShadow.addColorStop(0, "rgba(0,0,0,0.5)");    // 投影渐变的起点是透明度设为50%的黑色

             canopyShadow.addColorStop(0.2, "rgba(0,0,0,0.0)");  // 方向垂直向下,渐变色在很短的距离内村苏渐变至完全透明,这段长度之外的树干上没有投影

             // 在树干上填充投影渐变
             context.fillStyle = canopyShadow;
             context.fillRect(-5, -50, 10, 50);

             CreateCanopyPath(context);

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色
             context.stroke();

             // 将填充色设置为绿色并填充树冠
             context.fillStyle = "#339900";
             context.fill();
         }

         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

提取创建树对象的函数后的完整代码

  效果图如下:

 【始终在原点执行图形和路径的变换操作】

  “示例中演示了为什么要在原点执行图形和路径的变换操作,执行完后再统一平移。理由就是缩放(scale)和旋转(rotate)等变换操作都是针对原点进行的。
  如果对一个不在原点的图形进行旋转变换,那么rotate变换函数会将图形绕着原点旋转而不是原地旋转。与之类似,如果进行缩放操作时没有将图形放置到合适的坐标上,那么所有路径坐标都会被同时缩放。取决于缩放比例的大小,新的坐标可能会全部超出canvas范围,进而给开发人员带来困惑,为什么我的缩放操作会把图像删了?”

——Brian

2.13 Canvas 变换

  变换操作并不限于缩放和平移,我们可以使用函数context.rotate(angle)来旋转图像,甚至可以直接修改底层变换矩阵以完成一些高级操作,如剪裁图像的绘制路径。如果想旋转图像,只需执行如下代码的一系列操作即可:

             context.save();

             // 旋转角度参数以弧度为单位
             context.rotate(1.57);
             context.drawImage(myImage, 0, 0, 100, 100);

             context.restore();

旋转图像

  在下列代码中,我们将演示如何对路径坐标进行随意便函,以从根本上改变现有树的路径显示,并最终创建一个阴影效果:

             // 创建用于填充树干的三阶水平渐变色
             // 保存canvas的当前状态
             context.save();
             // X值随着Y值的增加而则根据艾,借助拉伸变换,可以创建一棵用作阴影的倾斜的树
             // 应用了变换以后,所有坐标都与矩阵相乘
             context.transform(1, 0, -0.5, 1, 0, 0);
             // 在Y轴方向,将引用的高度压缩为原来的60%
             context.scale(1, 0.6);
             // 使用透明度为20%的黑色填充树干
             context.fillStyle = "rgba(0,0,0,0.2)";
             context.fillRect(-5, -50, 10, 50);
             // 使用已有的阴影效果重新绘制树
             CreateCanopyPath(context);
             context.fill();
             // 恢复之前的canvasc状态
             context.restore(); 

使用变换为大树绘制阴影部分示例代码

  你可以像上面那样直接修改context变换矩阵,前提是要熟悉二维绘图系统的矩阵变换。分析这种变换背后的数学含义,可以看出我们通过调整与Y轴值相对应的参数改变了X轴的值,这样做木笔是为了拉伸出一棵灰色的树做阴影。接下来,我们按照60%的比例将剪裁出的树缩小到了合适的尺寸。

  注意,剪裁过的“阴影”树会先被显示出来,这样一来,真正的树就会按照Z轴顺序(canvas中对象的重叠顺序)显示在阴影的上面。此外,树影的填充用到了CSS的RGBA特性,通过特性我们将透明度值设为正常情况下的20%。至此,带有半透明效果的树影就做好了。将其应用于已经缩放过的树上,全部代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="550"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);
             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             // 在(130,250)的位置绘制第一棵树
             context.save();
             context.translate(130, 250);
             DrawTree(context);
             context.restore();

             // 在(260,500)的位置绘制第二颗树
             context.save();
             context.translate(260, 500);

             // 将第二颗树的宽高分别放大至原来的2倍
             context.scale(2, 2);
             DrawTree(context);
             context.restore(); 

             //绘制当前路径
             context.stroke();
             context.restore();

             DrawQuadraticCurve(context);    // 绘制曲线的函数
         }

         // 绘制曲线的函数
         function DrawQuadraticCurve(context) {
             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 加载砾石背景图
             var gravel = new Image();
             gravel.src = "gravel.jpg";
             gravel.onload = function () {
                 // 用背景图替代棕色粗线条
                 context.strokeStyle = context.createPattern(gravel, "repeat");
                 context.lineWidth = 20;
                 context.stroke();
             }

             context.restore();  // 恢复之前的Canvas状态
         }

         // 创建树对象绘制函数,以便重用
         function DrawTree(context) {
             var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);  // 创建用作树干纹理的三阶水平渐变
             trunkGradient.addColorStop(0, "#663300");   // 树干的左侧边缘是一般程度的棕色
             trunkGradient.addColorStop(0.4, "#996600"); // 树干中间偏左的位置颜色要淡一些
             trunkGradient.addColorStop(1, "#552200");   // 树干右侧边缘的颜色要深一些

             // 使用渐变色填充树干
             context.fillStyle = trunkGradient;
             context.fillRect(-5, -50, 10, 50);

             var canopyShadow = context.createLinearGradient(0, -50, 0, 0);  // 创建垂直渐变,以用作树冠在树干上投影
             canopyShadow.addColorStop(0, "rgba(0,0,0,0.5)");    // 投影渐变的起点是透明度设为50%的黑色

             canopyShadow.addColorStop(0.2, "rgba(0,0,0,0.0)");  // 方向垂直向下,渐变色在很短的距离内村苏渐变至完全透明,这段长度之外的树干上没有投影

             // 在树干上填充投影渐变
             context.fillStyle = canopyShadow;
             context.fillRect(-5, -50, 10, 50);

             CreateCanopyPath(context);

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色
             context.stroke();

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

             // 创建用于填充树干的三阶水平渐变色
             // 保存canvas的当前状态
             context.save();
             // X值随着Y值的增加而则根据艾,借助拉伸变换,可以创建一棵用作阴影的倾斜的树
             // 应用了变换以后,所有坐标都与矩阵相乘
             context.transform(1, 0, -0.5, 1, 0, 0);
             // 在Y轴方向,将引用的高度压缩为原来的60%
             context.scale(1, 0.6);
             // 使用透明度为20%的黑色填充树干
             context.fillStyle = "rgba(0,0,0,0.2)";
             context.fillRect(-5, -50, 10, 50);
             // 使用已有的阴影效果重新绘制树
             CreateCanopyPath(context);
             context.fill();
             // 恢复之前的canvasc状态
             context.restore();
         }

         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

添加阴影后的全部代码

  运行效果如下:

2.14 Canvas 文本

  在作品即将完成之际,沃恩要在图像的上部添加一个别致的标题,以此来向大家演示HTML5 Canvas API强大的文本功能。需要特别注意的是,操作canvas文本与操作其他路径对象的方式相同:可以描绘文本轮廓和填充文本内部;同时,所有能够应用于其他图像的变换和样式都能用于文本。

  context对象的文本绘制功能由两个函数组成:

  1. fillText(text, x, y, maxwidth)
  2. strokeText(text, x, y, maxwidth)

  两个函数的参数完全相同,必选参数包括文本参数以及用于制定文本位置的坐标参数。maxwidth是可选参数,用于限制字体大小,它会将文本字体强制收缩到制定尺寸。此外,还有一个measureText函数可供使用,该函数会返回一个度量对象,其中包含了在当前context环境下指定文本的实际显示宽度。

  为了保证文本在各浏览器下都能正常显示,Canvas API为context提供了类似于CSS的属性,以此来保证实际显示效果的高度可配置。

文本呈现相关的context属性
属性 备注
font CSS字体字符串 例如:italic Arial,scans-serif
textAlign start、end、left、right、center 默认是start
textBaseline top、hanging、middle、alphabetic、ideographic、bottom 默认是alphabetic

  对上面这些context属性赋值能够改变context,而访问context属性可以查询到其当前值。下列代码中,我们首先创建了一段使用Impact字体的大字号文本,然后使用已有的树皮图片作为背景进行填充。为了将文本置于canvas的上方并居中,我们定义了最大宽度和center(居中)对齐方式:

             context.save(); // 在canvas上绘制标题文本
             context.font = "60px impact";   // 字号为60px,字体为impact
             context.fillStyle = "#0000ff";  // 将文本填充为蓝色
             context.textAlign = "center";   // 将文本设为居中对齐
             // 在canvas顶部中央的位置,以大字体的形式显示文本
             context.fillText("孤影'Blog", 200, 60, 400);
             context.restore();

使用canvas文本

  运行效果如图:

  完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="550"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);
             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             // 在(130,250)的位置绘制第一棵树
             context.save();
             context.translate(130, 250);
             DrawTree(context);
             context.restore();

             // 在(260,500)的位置绘制第二颗树
             context.save();
             context.translate(260, 500);

             // 将第二颗树的宽高分别放大至原来的2倍
             context.scale(2, 2);
             DrawTree(context);
             context.restore(); 

             //绘制当前路径
             context.stroke();
             context.restore();

             DrawQuadraticCurve(context);    // 绘制曲线的函数

             context.save(); // 在canvas上绘制标题文本
             context.font = "60px impact";   // 字号为60px,字体为impact
             context.fillStyle = "#0000ff";  // 将文本填充为蓝色
             context.textAlign = "center";   // 将文本设为居中对齐
             // 在canvas顶部中央的位置,以大字体的形式显示文本
             context.fillText("孤影'Blog", 200, 60, 400);
             context.restore();
         }

         // 绘制曲线的函数
         function DrawQuadraticCurve(context) {
             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 加载砾石背景图
             var gravel = new Image();
             gravel.src = "gravel.jpg";
             gravel.onload = function () {
                 // 用背景图替代棕色粗线条
                 context.strokeStyle = context.createPattern(gravel, "repeat");
                 context.lineWidth = 20;
                 context.stroke();
             }
             context.restore();  // 恢复之前的Canvas状态
         }

         // 创建树对象绘制函数,以便重用
         function DrawTree(context) {
             var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);  // 创建用作树干纹理的三阶水平渐变
             trunkGradient.addColorStop(0, "#663300");   // 树干的左侧边缘是一般程度的棕色
             trunkGradient.addColorStop(0.4, "#996600"); // 树干中间偏左的位置颜色要淡一些
             trunkGradient.addColorStop(1, "#552200");   // 树干右侧边缘的颜色要深一些

             // 使用渐变色填充树干
             context.fillStyle = trunkGradient;
             context.fillRect(-5, -50, 10, 50);

             var canopyShadow = context.createLinearGradient(0, -50, 0, 0);  // 创建垂直渐变,以用作树冠在树干上投影
             canopyShadow.addColorStop(0, "rgba(0,0,0,0.5)");    // 投影渐变的起点是透明度设为50%的黑色

             canopyShadow.addColorStop(0.2, "rgba(0,0,0,0.0)");  // 方向垂直向下,渐变色在很短的距离内村苏渐变至完全透明,这段长度之外的树干上没有投影

             // 在树干上填充投影渐变
             context.fillStyle = canopyShadow;
             context.fillRect(-5, -50, 10, 50);

             CreateCanopyPath(context);

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色
             context.stroke();

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

             // 创建用于填充树干的三阶水平渐变色
             // 保存canvas的当前状态
             context.save();
             // X值随着Y值的增加而则根据艾,借助拉伸变换,可以创建一棵用作阴影的倾斜的树
             // 应用了变换以后,所有坐标都与矩阵相乘
             context.transform(1, 0, -0.5, 1, 0, 0);
             // 在Y轴方向,将引用的高度压缩为原来的60%
             context.scale(1, 0.6);
             // 使用透明度为20%的黑色填充树干
             context.fillStyle = "rgba(0,0,0,0.2)";
             context.fillRect(-5, -50, 10, 50);
             // 使用已有的阴影效果重新绘制树
             CreateCanopyPath(context);
             context.fill();
             // 恢复之前的canvasc状态
             context.restore();
         }

         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

添加Canvas文本后的完整代码

2.15 应用阴影

  最后,我们将使用内置的Canvas Shadow API为文本添加模糊阴影效果。虽然我们能够通过HtmL5 Canvas API将阴影效果应用于之前执行的任何操作中,但与很多图形效果的应用类似,阴影效果的使用也要把握好“度”。

  可以通过集中全局context属性来控制阴影,如下表:

阴影属性
属性 备注
shadowColor 任何CSS中的颜色值 可以使用透明度(Alpha)
shadowOffsetX X轴像素值 值为正数,向右移动阴影;值为负数,向左移动阴影
shadowOffsetY Y轴像素值 值为正数,向下移动阴影;值为负数,向上移动阴影
shadowBlur 高斯模糊值 值越大,阴影边缘越模糊

  shadowColor或者其他任意一项属性的值被赋为非默认值时,路径、文本和图片上的阴影效果就会被触发。下列代码显示了如何为文本添加阴影效果:

             // 设置文字阴影的颜色为黑色,透明度为20%
             context.shadowColor = "rgba(0,0,0,0.2)";
             // 将阴影向右移动15px,向上移动10px
             context.shadowOffsetX = 15;
             context.shadowOffsetY = -10;
             // 轻微模糊阴影
             context.shadowBlur = 2;

应用阴影效果

  执行上述代码后,canvas渲染器会自动应用阴影效果,知道恢复canvas状态或者重置阴影属性,添加阴影后的效果如图:

  如你所见,由CSS生成的阴影只有位置上的变化,而无法与变化生成的阴影(树影)保持同步。为了一致起见,在canvas上绘制阴影时,应该尽量只用一种方法。

  完整代码如下:

 <!DOCTYPE html>

 <html>
 <head>
     <meta charset="utf-8" />
     <title>孤影'Blog http://www.cnblogs.com/LonelyShadow</title>
 </head>
 <body>
     <canvas id="trails" style="border:1px solid black;" width="400" height="550"></canvas>

     <script>
         // 用于绘制树冠轮廓的函数
         function CreateCanopyPath(context) {
             context.beginPath();    // 绘制树冠

             context.moveTo(-25, -50);
             context.lineTo(-10, -80);
             context.lineTo(-20, -80);
             context.lineTo(-5, -110);
             context.lineTo(-15, -110);

             context.lineTo(0, -140);    // 树的顶点

             context.lineTo(15, -110);
             context.lineTo(5, -110);
             context.lineTo(20, -80);
             context.lineTo(10, -80);
             context.lineTo(25, -50);

             context.lineTo(25, -50);
             context.closePath();    // 连接起点,闭合路径
         }

         // 在Canvas上画树的函数
         function DrawTrails() {
             var canvas = document.getElementById("trails");
             var context = canvas.getContext("2d");

             // 在(130,250)的位置绘制第一棵树
             context.save();
             context.translate(130, 250);
             DrawTree(context);
             context.restore();

             // 在(260,500)的位置绘制第二颗树
             context.save();
             context.translate(260, 500);

             // 将第二颗树的宽高分别放大至原来的2倍
             context.scale(2, 2);
             DrawTree(context);
             context.restore();

             //绘制当前路径
             context.stroke();
             context.restore();

             DrawQuadraticCurve(context);    // 绘制曲线的函数

             context.save(); // 在canvas上绘制标题文本
             // 设置文字阴影的颜色为黑色,透明度为20%
             context.shadowColor = "rgba(0,0,0,0.2)";
             // 将阴影向右移动15px,向上移动10px
             context.shadowOffsetX = 15;
             context.shadowOffsetY = -10;
             // 轻微模糊阴影
             context.shadowBlur = 2;
             context.font = "60px impact";   // 字号为60px,字体为impact
             context.fillStyle = "#0000ff";  // 将文本填充为蓝色
             context.textAlign = "center";   // 将文本设为居中对齐
             // 在canvas顶部中央的位置,以大字体的形式显示文本
             context.fillText("孤影'Blog", 200, 60, 400);
             context.restore();
         }

         // 绘制曲线的函数
         function DrawQuadraticCurve(context) {
             context.save();
             context.translate(-10, 350);
             context.beginPath();

             // 第一条曲线向右上角弯曲
             context.moveTo(0, 0);
             context.quadraticCurveTo(170, -50, 260, -190);

             // 第二天曲线向右下方弯曲
             context.quadraticCurveTo(310, -250, 410, -250);

             // 加载砾石背景图
             var gravel = new Image();
             gravel.src = "gravel.jpg";
             gravel.onload = function () {
                 // 用背景图替代棕色粗线条
                 context.strokeStyle = context.createPattern(gravel, "repeat");
                 context.lineWidth = 20;
                 context.stroke();
             }
             context.restore();  // 恢复之前的Canvas状态
         }

         // 创建树对象绘制函数,以便重用
         function DrawTree(context) {
             var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);  // 创建用作树干纹理的三阶水平渐变
             trunkGradient.addColorStop(0, "#663300");   // 树干的左侧边缘是一般程度的棕色
             trunkGradient.addColorStop(0.4, "#996600"); // 树干中间偏左的位置颜色要淡一些
             trunkGradient.addColorStop(1, "#552200");   // 树干右侧边缘的颜色要深一些

             // 使用渐变色填充树干
             context.fillStyle = trunkGradient;
             context.fillRect(-5, -50, 10, 50);

             var canopyShadow = context.createLinearGradient(0, -50, 0, 0);  // 创建垂直渐变,以用作树冠在树干上投影
             canopyShadow.addColorStop(0, "rgba(0,0,0,0.5)");    // 投影渐变的起点是透明度设为50%的黑色

             canopyShadow.addColorStop(0.2, "rgba(0,0,0,0.0)");  // 方向垂直向下,渐变色在很短的距离内村苏渐变至完全透明,这段长度之外的树干上没有投影

             // 在树干上填充投影渐变
             context.fillStyle = canopyShadow;
             context.fillRect(-5, -50, 10, 50);

             CreateCanopyPath(context);

             context.lineWidth = 4;  // 加宽线条
             context.lineJoin = "round"; // 平滑路径的结合点
             context.strokeStyle = "#663300";    // 将颜色改成棕色
             context.stroke();

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

             // 创建用于填充树干的三阶水平渐变色
             // 保存canvas的当前状态
             context.save();
             // X值随着Y值的增加而则根据艾,借助拉伸变换,可以创建一棵用作阴影的倾斜的树
             // 应用了变换以后,所有坐标都与矩阵相乘
             context.transform(1, 0, -0.5, 1, 0, 0);
             // 在Y轴方向,将引用的高度压缩为原来的60%
             context.scale(1, 0.6);
             // 使用透明度为20%的黑色填充树干
             context.fillStyle = "rgba(0,0,0,0.2)";
             context.fillRect(-5, -50, 10, 50);
             // 使用已有的阴影效果重新绘制树
             CreateCanopyPath(context);
             context.fill();
             // 恢复之前的canvasc状态
             context.restore();
         }

         window.addEventListener("load", DrawTrails, true);
     </script>
 </body>
 </html>

应用阴影效果后的完整代码

2.16 像素数据

  Canvas API最有用的特性之一是允许开发人员直接访问canvas底层像素数据。这种数据访问时双向的:一方面,可以以数值数组形式获取像素数据;另一方面,可以修改数组的值以将其应用于canvas。实际上,放弃本篇之前讨论的渲染调用,也可以通过直接调用像素数据的相关方法来控制canvas。这要归于context API内置的三个函数。

  第一个是context.getImageData(sx, sy, sw, sh)。这个函数返回当前canvas状态并以数值数组的方式显示。具体来说,返回的对象包括三个属性:

  1. width:每行有多少个像素
  2. height:每列有多少个像素
  3. data:一维数组,存有从canvas湖区的每个像素的rgba值。该数组为每个像素保存了四个值——红、绿、蓝和Alpha透明度。每个值都在0~255之间。因此,canvas上的每个像素在这个数组中就编程了四个整数值。数组的填充顺序是从左到右,从上到下(也就是先第一行再第二行,一次类推)。

如图:

  getImageData函数有四个参数,该函数只返回这四个参数所限定的区域内的数据。只有被x、y、width和height四个参数框定的矩形区域内的canvas上的像素才会被渠道,因此想要获取所有像素数据,就需要这样传入参数:getImageData(0, 0, canvas.width, canvas.height)。

  因为每个像素由四个图像数据表示,所以要计算指定像素点对应的值是什么就有点头疼。不要紧,下面有公式:

  在给定了width和height的canvas上,在坐标(x, y)上的像素的构成如下:

  • 红色部分:((width * y) + x) * 4
  • 绿色部分:((width * y) + x) * 4 + 1
  • 蓝色部分:((width * y) + x) * 4 + 2
  • 透明度部分:((width * y) + x) * 4 + 3

  一旦可以通过像素数据的方式访问对象,就可以通过数学方式轻松修改数组中的像素值,因为这些值都是从0到255的简单数字。修改了恩和像素的红、绿、蓝和Alpha值之后,可以通过第二个函数来更新canvas上的显示,那就是context.putImageData(imageData, dx, dy)。

  putImageData允许开发人员传入一组图像数据,其格式与最初从canvas获取来的是一样的。这个函数使用起来非常方便,因为可以直接从canvas上获取数据加以修改然后返回。一旦这个函数被调用,所有新传入的图像数据值就会立即在canvas上更新显示出来。dx和dy参数可以用来指定偏移量,如果使用,则该函数就会跳到指定的canvas位置去更新显示传进来的像素数据。

  最后,如果想预先生成一组空的canvas数据,则可调用的context.createImageData(sw, sh),这个啊还能输可以创建一组图像数据并绑定在canvas对象上。这组数据可以像先前那样处理,只是在获取canvas数据时,这组图像数据不一定会反映canvas的当前状态。

  还有一种方法可用于从canvas中获取数据:Canvas.toDataUrl API。借助它,能够通过编程来获取canvas上当前呈现的数据,获得的数据以文本格式存在,这种格式是一种标准的数据表示方法,浏览器能将其解析成图像。

  data URL是一个包含了图像数据(如png)的字符串,浏览器会像显示普通图像文件一样显示图像数据,比如博客园编辑器,复制进来的图片数据,切换到html源码,你会发现是:

data:image/png;base64, LonelyShadowBlogs...

  本例表明个事的开始是字符串data:,接着是MIME类型(如image/png),随后是标志位,他表示数据是否使用base64格式编码,最后的文本则表示图像数据本身。

  不必为格式问题范畴,以为内生成格式不需要您亲自出马。这里的重点在于通过一个简单的调用,就能够获取Canvas的内容,进而将其转换成data URL格式。调用canvas.toDataURL(type)时,可以传入开发人员期望的由Canvas数据生成的图像类型作为参数,如image/png(默认)或imge/jpg。返回的data URL可以作为页面中image元素的源,或者用在CSS央视中,如下列代码:

             // 在Canvas上进行绘制
             // 获取data URL格式的Canvas数据
             var canvasData = myCanvas.toDataURL();
             // 将数据赋值给新的image对象的src属性
             var img = new Image();
             img.src = canvasData;

利用Canvas数据创建Image元素

  获得data URL后可以不马上使用它,你甚至可以将其先存储在浏览器的locaStorage中备用。

2.17 Canvas 的安全机制

  上面讨论了直接操纵像素数据的方法,在这里有必要重点提醒一下,大多数开发者都会合法使用像素数据操作。尽管如此,还会有人处于某些邪恶的目的利用这种从canvsa直接获取并修改数据的能力。出于这个原因,origin-clean canvas的概念应运而生,换句话说,如果canvas中的图片并非来自包含他的页面所在域,页面中的脚本将不能取得其中的数据。

  如图所示,如果来自http://www.example.com的页面包含canvas元素,那么页面中的代码完全有可能在canvas里面呈现来自http://www.remote.com的图片。毕竟在任何Web页面中显示其他远程网站的图片都是完全可接受的。

  然而,在没有Canvas API以前,无法使用编程的方式获取下载图片的像素信息。来自其他网站的私有图片可以显示在本地,但无法被读取或者复制。如果允许脚本读取本地之外的图像数据,那么整个网络中的用户照片以及其他敏感的在线图片文档将被“无限制地共享”。

  为了避免如此,在getImageData函数被调用的时候,如果canvas中的图像来自其他域,就会抛出安全异常。这样的话,只要不获取显示着其他域中图片的canvas的数据,那么就可以随意呈现这些远程图片。在开发的过程中要注意这个限制条件,使用安全的渲染方式。

3. 使用HTML5 Canvas创建应用案例

  使用Canvas API可以创建许多中应用:图形、图表、图片编辑等,然而最奇妙的一个应用是修改或者覆盖已有内容。最流行的覆盖图被称为热点图。虽然热点图听起来是度量温度的意思,不过这里的热度可以用于任何可测量的活动。地图上活跃程度高的部分使用暖色标记(例如红色、黄色或白色),活跃程序低的部分不显示颜色变化,或者显示浅浅的黑色或灰色。

  举个例子,热点图可以用在城市地图上来标记交通路况,或者在世界地图上显示风暴的活动情况。在HTML5中这些应用都非常容易实现,只需要将canvas叠放在地图上显示即可。实际上就是用canvas覆盖地图,然后再基于相应的活动数据绘制出不同的热度级别。

  现在,我们使用已经学过的Canvas API只是来绘制一个简单的热点图。这个示例中,热度数据不是来源于外部,而是来源于我们的鼠标在地图上的移动情况。鼠标移动到某个区域,会使这个区域的“热度”增加。将鼠标放在特定区域不动会让该区域“温度”迅速增长至极限。为了师范,我们将在一个“难以名状的地图上进行热点图的覆盖演示”:

由于函数比较多,本文的篇幅也够长的了,源码我就不往上贴了,要的邮箱,或者去官网下载都行。

非常感谢看到这里的你,我承认,我欺骗了你,看到这里远远不止30分钟。

但是。如果你练习完了所有的示例,是不是对HTML Canvas API已经有所掌握了?至少这点没有欺骗大家吧~

本篇也是我的学习笔记,真的是真的纯手工码字,如果您觉得本文不错的请戳一下赞哦~小小的动力。

参考:《HTML5 程序设计 (第二版)》

HTML5 程序设计 - 使用HTML5 Canvas API的更多相关文章

  1. HTML5程序设计 Canvas API

    检测浏览器支持情况 <script type="text/javascript"> try { document.createElement("Canvas& ...

  2. Pro HTML5 Programming(Second Edition)2.Canvas API(2)

    1.在页面中加入canvas元素 eg: <!DOCTYPE html> <html lang="en"> <head> <meta ch ...

  3. Pro HTML5 Programming(Second Edition)2.Canvas API(1)

    1.在使用HTML5的Canvas元素时,考虑到有些浏览器不支持canvas元素,或是不支持HTML5 Canvas API中的某些特性,开发人员最好提供一份替代代码. 以下代码展示如何在canvas ...

  4. html5 Canvas API

    详细内容请点击 1.HTML Canvas API有两方面优势可以弥补:首先,不需要将所绘制图像中的每个图元当做对象存储,因此执行性能非常好:其次,在其他编程语言现有的优秀二维绘图API的基础上实现C ...

  5. HTML5(五)——Canvas API

    什么是 Canvas API? Canvas API(画布)提供了一个通过 javascript 和 html 的 canvas 元素来在网页上实时绘制图形的方式.可用于动画.游戏.图标.图片编辑等多 ...

  6. [转]人人网首页拖拽上传详解(HTML5 Drag&Drop、FileReader API、formdata)

    人人网首页拖拽上传详解(HTML5 Drag&Drop.FileReader API.formdata) 2011年12月11日 | 彬Go 上一篇:给力的 Google HTML5 训练营( ...

  7. HTML5 的绘图支持- canvas

    Canvas HTML5新增了一个canvas元素,它是一张空画布,开发者需要通过JavaScript脚本进行绘制. 在canvas上绘图,经过如下3步 (1) 获取canvas元素对应的DOM对象. ...

  8. HTML5 程序设计笔记(二)

    Canvas API 1.HTML5 Canvas 概述 1.1 历史 Canvas的概念最初是由苹果公司提出的,用于在Mac OS X WebKit中创建控制板部件(dashboard widget ...

  9. HTML5新特性之Canvas+drag(拖拽图像实现图像反转)

    1.什么是canvas 在网页上使用canvas元素时,会创建一块矩形区域,默认矩形区域宽度300px,高度150px.. 页面中加入canvas元素后,可以通过javascript自由控制.可以在其 ...

随机推荐

  1. CSS3 border-radius边框圆角

    在CSS3中提供了对边框进行圆角设定的支持,可对边框1~4个角进行圆角样式设置. 目录 1. 介绍 2. value值的格式和类型 3. border-radius 1~4个参数说明 4. 在线示例 ...

  2. redux-amrc:用更少的代码发起异步 action

    很多人说 Redux 代码多,开发效率低.其实 Redux 是可以灵活使用以及拓展的,经过充分定制的 Redux 其实写不了几行代码.今天先介绍一个很好用的 Redux 拓展-- redux-amrc ...

  3. Android性能优化之利用LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...

  4. android键盘

    在应用的开发过程中有不少的情况下会用到自定义键盘,例如支付宝的支付密码的输入,以及类似的场景.android系统给开发者们提供了系统键盘,KeyboardView,其实并不复杂,只是有些开发者不知道罢 ...

  5. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  6. bzoj3207--Hash+主席树

    题目大意: 给定一个n个数的序列和m个询问(n,m<=100000)和k,每个询问包含k+2个数字:l,r,b[1],b[2]...b[k],要求输出b[1]~b[k]在[l,r]中是否出现. ...

  7. 企业做数据缓存是使用Memcached还是选Redis?

    企业是使用Memcached还是选Redis? 在构建一款现代且由数据库驱动的Web应用程序并希望使其拥有更为出色的性能表现时,这个问题总会时不时出现.并给每一位开发人员带来困扰.在考虑对应用程序的性 ...

  8. Js 数组返回去重后的数据

    function removeRepeat(data) { var temp = ""; var mainData = []; for (var i = 0; i < dat ...

  9. 《Ansible权威指南》笔记(2)——Inventory配置

    四.Inventory配置ansible通过Inventory来定义主机和组,使用时通过-i指定读取,默认/etc/ansible/hosts.可以存在多个Inventory,支持动态生成.1.定义主 ...

  10. Hadoop3 在eclipse中访问hadoop并运行WordCount实例

    前言:       毕业两年了,之前的工作一直没有接触过大数据的东西,对hadoop等比较陌生,所以最近开始学习了.对于我这样第一次学的人,过程还是充满了很多疑惑和不解的,不过我采取的策略是还是先让环 ...