HTML5-编程高级教程-一-

85 阅读1小时+

HTML5 编程高级教程(一)

原文:Pro HTML5 Programming

协议:CC BY-NC-SA 4.0

零、简介

HTML5 是全新的。事实上,它甚至还没有完全完成。如果你听一些坏脾气的专家的话,他们会告诉你 HTML5 在十年或更长时间内都不会准备好!

那么,为什么会有人认为现在是出版一本名为 Pro HTML5 编程的书的时候了呢?那很简单。因为对于那些正在寻找额外优势使你的 web 应用鹤立鸡群的人来说,HTML5 的时机到了。这本书的作者已经研究、开发和教授 HTML5 技术两年多了,可以肯定地说,新标准的采用正以令人眩晕的速度加速。即使在写这本书的过程中,我们也被迫不断更新章节,并重新评估我们关于什么可以使用的假设。

大多数用户并没有真正理解他们正在使用的浏览器的强大功能。是的,在他们最喜欢的浏览器自动更新后,他们可能会注意到一些微小的界面增强。但他们可能不知道这个新的浏览器版本只是引入了自由形式的绘图画布或实时网络通信,或任何其他潜在的升级。

通过这本书,我们旨在帮助你打开 HTML5 的力量。

这本书是给谁的

本书的内容面向熟悉 JavaScript 编程的有经验的 web 应用开发人员。换句话说,我们不会在这篇文章中讨论 web 开发的基础知识。有许多现有的资源可以帮助您快速掌握 web 编程的基础知识。也就是说,如果你在以下任何一个要点中看到了自己,这本书可能会为你提供你正在寻找的有用的见解和信息:

  • 你有时会想,“要是我的浏览器能做到就好了。。."
  • 您发现自己正在使用页面源代码和开发工具来剖析一个特别令人印象深刻的网站。
  • 您喜欢阅读最新浏览器更新的发行说明来了解新功能。
  • 您正在寻找优化或简化应用的方法。
  • 你愿意定制你的网站,在相对较新的浏览器上为用户提供最好的体验。

如果这些中的任何一个适用于你,这本书可能很适合你的兴趣。

虽然我们会在适当的地方指出浏览器支持的局限性,但我们的目的不是为您提供复杂的变通方法,让您的 HTML5 应用在十年前的浏览器上无缝运行。经验表明,变通方法和基准浏览器支持发展如此之快,以至于像这样的书不是这类信息的最佳载体。相反,我们将重点讨论 HTML5 的规范以及如何使用它。详细的解决办法可以在互联网上找到,随着时间的推移,将变得不那么必要。

本书概述

这本书的十三个章节涵盖了一系列流行、有用和强大的 HTML5 APIs。在某些情况下,我们在前面章节中介绍的功能的基础上为您提供了更丰富的演示。

第一章“HTML 5 简介”从 HTML 规范的过去和当前版本的背景开始。介绍了新的高级语义标签,以及 HTML5 中所有最新发展背后的基本变化和基本原理。了解这个地形就好。

第二章、第三章、第四章、【音频和视频】描述了新的视觉和媒体元素。在这些章节中,重点是寻找更简单的方法来美化你的用户界面,而不需要插件或者服务器端的交互。

第五章“使用地理定位 API”介绍了一个真正的新功能,这是以前不容易模仿的——应用能够识别用户的当前位置,并使用它来定制体验。隐私在这里很重要,所以我们也包括一些注意事项。

接下来的两章,“使用通信 API”和“使用 WebSocket API”,展示了 HTML5 越来越强大的功能,让您可以与其他网站通信,并以简单和最小的开销将实时数据传输到应用。这些章节中的技术将使你能够简化当今网络上部署的许多过于复杂的架构。

第八章“使用表单应用编程接口”向您介绍了目前您可以对桌面或移动 web 应用进行的最小调整,以提高可用性,以及您可以进行的更基本的更改,以在非常常见的使用场景中检测页面输入错误。第九章“使用拖放 API”,详细阐述了新的拖放 API 特性,并展示了如何使用它们。

第十章、第十一章和第十二章——“使用 Web 工作器 API”、“使用存储 API”和“创建离线 Web 应用”——处理应用的内部管道。在这里,您将找到优化现有功能的方法,以获得更好的性能和更好的数据管理。

最后,第十三章“html 5 的未来”将给你一个即将到来的美味预览。

示例代码和配套网站

本书中给出的示例代码可以在 Apress 网站的源代码部分在线获得。访问[www.apress.com](http://www.apress.com),点击源代码,寻找这本书的书名。你可以从这本书的主页下载源代码。此外,我们在[www.prohtml5.com](http://www.prohtml5.com)为这本书主办了一个伙伴网站,你也可以从那里下载示例代码和一些实用的附加内容。

联系作者

谢谢你买这本书。我们希望你喜欢阅读它,并发现它是一个有价值的资源。尽管我们尽了最大努力避免错误,但我们意识到事情有时会从缝隙中溜走,我们希望提前对任何此类失误表示歉意。我们欢迎您对本书的内容和源代码提出个人反馈、问题和评论。您可以通过发送电子邮件至[prohtml5@gmail.com](http://prohtml5@gmail.com)与我们联系。

一、HTML5 概述

这本书是关于 HTML5 编程的。然而,在理解 HTML5 编程之前,您需要后退一步,理解 HTML5 是什么,它背后的一些历史,以及 HTML 4 和 HTML5 之间的差异。

在这一章中,我们直奔每个人都想得到答案的实际问题。为什么是 HTML5,为什么刚才那么激动?有哪些新的设计原则让 HTML5 真正具有革命性,同时又高度兼容?无插件范例的含义是什么;什么流行什么不流行?HTML 的新特性是什么,它如何为 web 开发人员开启一个全新的时代?我们开始吧。

迄今为止的故事 HTML5 的历史

HTML 可以追溯到很久以前。它于 1993 年首次作为互联网草稿发表。90 年代见证了围绕 HTML 的大量活动,出现了 2.0 版、3.2 版和 4.0 版(同一年!),最后在 1999 年,4.01 版本。在其发展过程中,万维网联盟(W3C)控制了该规范。

在这四个版本快速发布后,HTML 被广泛认为是死胡同;web 标准的焦点转移到了 XML 和 XHTML 上,而 HTML 被放在了次要位置。与此同时,HTML 拒绝消亡,web 上的大多数内容继续作为 HTML 提供服务。为了启用新的 web 应用并解决 HTML 的缺点,HTML 需要新的特性和规范。

为了将网络平台提升到一个新的高度,一小群人在 2004 年成立了网络超文本应用工作组(WHATWG)。他们创建了 HTML5 规范。他们还开始开发专门针对网络应用的新功能——他们认为这是最缺乏的领域。大约在这个时候,Web 2.0 这个术语被创造出来。这真的像是第二个新网站,静态网站让位于需要更多功能的更动态的社交网站——更多的功能。

W3C 在 2006 年再次参与 HTML,并在 2008 年发布了 HTML5 的第一个工作草案,XHTML 2 工作组在 2009 年停止了工作。又过了两年,这就是我们今天的处境。因为 HTML5 解决了非常实际的问题(您将在后面看到),浏览器供应商正在狂热地实现它的新特性,尽管规范还没有完全锁定。浏览器的实验反馈并改进了规范。HTML5 正在快速发展,以解决 web 平台的真正和实际的改进。

HTML 中的时刻

Brian 说:“嗨,我是 Brian,我是一个 HTML 守财奴。

早在 1995 年,我就创作了我的第一个主页。在那个时候,“主页”是你用来介绍自己的东西。它通常由糟糕的扫描图片、<blink>标签、关于你住在哪里和你在读什么的信息,以及你目前正在从事的与计算机相关的项目组成。我和我的大多数“万维网开发人员”都在大学上学或受雇于大学。

当时,HTML 还很原始,工具也不可用。除了一些原始的文本处理脚本,Web 应用几乎不存在。页面使用您最喜欢的文本编辑器手工编码。它们每隔几周或几个月就会更新一次。

十五年来,我们走过了漫长的道路。

如今,用户一天多次更新在线个人资料并不罕见。如果没有建立在每一代人基础上的在线工具的稳步发展,这种类型的互动是不可能的。

当你读这本书时,请记住这一点。我们在这里展示的例子有时可能看起来过于简单,但是潜力是无限的。我们这些在 20 世纪 90 年代中期第一次使用<img>标签的人可能不知道在十年之内,许多人会在网上存储和编辑他们的照片,但我们应该预测到这一点。

我们希望本书中的例子能启发你超越基础知识,为下一个十年创造新的网络基础。"

2022 年的神话和为什么没关系

我们今天看到的 HTML5 规范已经作为工作草案发布了——它还不是最终版本。那么什么时候它会被固定下来呢?以下是你需要知道的关键日期。第一个是 2012 年,这是推荐候选人的目标日期。第二个日期是 2022 年,也就是提出的推荐。等等!别这么快!在你考虑这两个日期实际上意味着什么之前,不要合上书把它放在一边十年。

第一个也是最近的日期可以说是最重要的一个,因为一旦我们到达那个阶段,HTML5 就完成了。那就在拐角处。所提出的建议(我们都同意这有点遥远)的意义在于将会有两个可互操作的实现。换句话说,两个浏览器配备了完整规范的完全可互操作的实现——一个崇高的目标,实际上使 2022 年的最后期限显得雄心勃勃。毕竟,我们甚至还没有在 HTML4 中实现这一点,只是最近才在 CSS2 中实现!

现在重要的是,浏览器供应商正在积极增加对许多非常酷的新功能的支持,其中一些已经处于最终征求意见阶段。根据您的受众,您现在就可以开始使用其中的许多功能。当然,在前进的道路上需要做出许多微小的改变,但这是享受生活在前沿的好处的小小代价。当然,如果你的观众使用的是 Internet Explorer 6.0,许多新功能将无法工作,需要模拟——但这仍然不是放弃 HTML5 的好理由。毕竟,这些用户最终也会跳到更高的版本。他们中的许多人可能会马上转向 IE 浏览器 9.0,而那个版本的 IE 浏览器支持更多的 HTML5 特性。实际上,新的浏览器和改进的仿真技术的结合意味着你可以在今天或不久的将来使用许多 HTML5 特性。

谁在开发 HTML5?

我们都知道需要一定程度的结构,显然需要有人负责 HTML5 的规范。这一挑战是三个重要组织的工作:

  • web 超文本应用技术工作组(WHATWG):WHATWG 成立于 2004 年,由为苹果、Mozilla、谷歌和 Opera 等浏览器厂商工作的个人创立,为 Web 应用开发开发 HTML 和 API,并为浏览器厂商和其他相关方提供开放协作。
  • 万维网联盟(W3C):W3C 包含 HTML 工作组,目前负责交付他们的 HTML5 规范。
  • 互联网工程任务组(IETF) :该任务组包含负责互联网协议(如 HTTP)的小组。HTML5 定义了一个新的 WebSocket API,它依赖于一个新的 WebSocket 协议,该协议正在 IETF 工作组中开发。

新的愿景

HTML5 基于各种设计原则,在 WHATWG 规范中有详细说明,真正体现了可能性和实用性的新愿景。

  • 和睦相处
  • 效用
  • 互用性
  • 普及高等教育
兼容性和铺设牛道

不用担心;HTML5 不是一场令人不安的革命。事实上,它的核心原则之一是保持一切顺利进行。如果不支持 HTML5 特性,行为必须适度降级。此外,由于 HTML 内容已经存在了大约 20 年,所以支持所有现有的内容非常重要。

在研究普通行为方面已经投入了很多努力。例如,谷歌分析了数百万个页面,以发现DIV标签的通用 ID 和Class名称,并发现了大量重复。例如,许多人使用DIV id="header"来标记标题内容。HTML5 都是解决现实问题的,对吧?那么为什么不简单地创建一个<header>元素呢?

虽然 HTML5 标准的一些特性相当具有革命性,但游戏的名称是进化而不是革命。毕竟,为什么要重新发明轮子呢?(或者,如果一定要,那至少做一个更好的吧!)

效用和选区的优先级

HTML5 规范是基于明确的选区优先级编写的。就优先级而言,“用户为王”这意味着,当有疑问时,规范更看重用户,而不是作者、实现者(浏览器)、说明符(W3C/WHATWG)和理论纯度。因此,HTML5 非常实用,尽管在某些情况下并不完美。

考虑这个例子。以下代码片段在 HTML5 中同样有效:

id="prohtml5" id=prohtml5 ID="prohtml5"

当然,有些人会反对这种宽松的语法,但底线是最终用户并不真正关心。我们并不是建议您开始编写草率的代码,但最终,当前面的任何示例生成错误并且不能呈现页面的其余部分时,最终用户会受到影响。

HTML5 还催生了 XHTML5 的创建,使 XML 工具链能够生成有效的 HTML5 代码。HTML 或 XHTML 版本的序列化应该产生差别最小的相同 DOM 树。显然,XHTML 语法要严格得多,最后两个例子中的代码是无效的。

通过设计确保安全

从一开始就非常强调 HTML5 的安全性。规范的每一部分都有关于安全性考虑的章节,安全性已经被预先考虑了。HTML5 引入了一个新的基于起源的安全模型,它不仅易于使用,而且可以被不同的 API 一致地使用。这种安全模式允许我们以过去不可能的方式做事。例如,它允许我们安全地跨域通信,而不必回到各种聪明的、创造性的、但最终不安全的黑客攻击。在这方面,我们肯定不会回顾过去的美好时光。

演示和内容的分离

HTML5 向表示和内容的彻底分离迈出了一大步。HTML5 尽可能地创建这种分离,并且使用 CSS 来实现。事实上,由于前面提到的兼容性设计原则,早期版本的 HTML 的大多数表示特性不再受支持,但仍然可以工作。然而,这个想法并不完全是新的;它已经在 HTML4 Transitional 和 XHTML1.1 中出现了。Web 设计人员长期以来一直将此作为最佳实践,但是现在,更重要的是将两者清楚地分开。表示标记的问题是:

  • 可达性差
  • 不必要的复杂性(使用所有的内联样式更难阅读您的代码)
  • 较大的文档大小(由于样式内容的重复),这意味着页面加载速度较慢
互通简化

HTML5 是关于简化和避免不必要的复杂性。HTML5 口头禅?“简单一点比较好。尽可能简化。”以下是这方面的一些例子:

  • 原生浏览器功能,而不是复杂的 JavaScript 代码
  • 一个新的,简化的DOCTYPE
  • 新的简化字符集声明
  • 强大而简单的 HTML5 APIs

我们稍后会详细讨论其中的一些。

为了实现所有这些简单性,规范变得更大,因为它需要更精确——事实上,比以前任何版本的 HTML 规范都更精确。它规定了大量定义明确的行为,以努力在 2022 年前实现真正的浏览器互操作性。含糊不清根本不会让这种情况发生。

HTML5 规范也比以前的规范更详细,以防止误解。它旨在彻底定义事物,尤其是 web 应用。难怪该规范长达 900 多页!

HTML5 的设计也是为了很好地处理错误,有各种改进的和雄心勃勃的错误处理计划。实际上,它更喜欢优雅的错误恢复,而不是硬故障,再次让 A-1 优先考虑最终用户的利益。例如,文档中的错误不会导致页面无法显示的灾难性故障。相反,错误恢复是精确定义的,因此浏览器可以以标准方式显示“损坏的”标记。

普遍接入

这一原则分为三个概念:

  • 可访问性(Accessibility):为了支持残疾用户,HTML5 与一个名为 Web Accessibility Initiative (WAI)的可访问富互联网应用(ARIA)的相关标准紧密合作。屏幕阅读器支持的 WAI-ARIA 角色已经可以添加到 HTML 元素中了。
  • 媒体独立性:如果可能的话,HTML5 功能应该在所有不同的设备和平台上工作。
  • 支持所有世界语言:例如,新的<ruby>元素支持在东亚排版中使用的 Ruby 注释。

无插件的范例

HTML5 提供了对许多功能的原生支持,这些功能过去只能通过插件或复杂的攻击来实现(原生绘图 API、原生视频、原生套接字等等)。

当然,插件会带来许多问题:

  • 插件不能总是被安装。
  • 可以禁用或阻止插件(例如,苹果 iPad 不附带 Flash 插件)。
  • 插件是一种独立的攻击媒介。
  • 插件很难与 HTML 文档的其余部分集成在一起(因为插件边界、剪裁和透明度问题)。

虽然有些插件的安装率很高(例如 Adobe Flash),但它们在受控的企业环境中经常被屏蔽。此外,一些用户选择禁用这些插件,因为它们支持不受欢迎的广告显示。然而,如果用户禁用了你的插件,他们也禁用了你用来显示内容的程序。

插件通常也很难将它们的显示与浏览器的其他内容集成在一起,这导致了某些网站设计的剪辑或透明度问题。因为插件使用一个独立的呈现模型,这个模型不同于基础网页,如果弹出菜单或其他视觉元素需要跨越页面上的插件边界,开发者会面临困难。这就是 HTML5 登场的地方,微笑着,挥舞着它的魔法棒,展示着原生功能。你可以用 CSS 设计元素的样式,用 JavaScript 编写脚本。事实上,这是 HTML5 展示其最大力量的地方,向我们展示了 HTML 以前版本中不存在的力量。不仅仅是新元素提供了新的功能。它还增加了与脚本和样式的本地交互,使我们能够做比以前更多的事情。

以新的 canvas 元素为例。它使我们能够做一些非常基本的事情,这在以前是不可能的(试着用 HTML 4 在网页上画一条对角线)。然而,最有趣的是我们可以用 API 释放的力量,以及我们可以用几行 CSS 代码应用的样式。像表现良好的孩子一样,HTML5 元素也可以很好地配合使用。例如,您可以从视频元素中抓取一帧并显示在画布上,用户只需单击画布就可以从您刚才显示的帧回放视频。这只是一个本地代码相对于插件所能提供的一个例子。事实上,当你不使用黑盒时,几乎所有事情都会变得更容易。所有这些加起来就是一个真正强大的新媒体,这就是为什么我们决定写一本关于 HTML5 编程的书,而不仅仅是关于新元素!

什么流行,什么不流行?

那么,到底是 HTML5 的什么部分呢?如果您仔细阅读规范,您可能不会发现我们在本书中描述的所有特性。例如,你不会在那里找到地理位置和网络工作者。所以这是我们瞎编的吗?都是炒作吗?不,一点也不!

HTML5 工作的许多部分最初是 HTML5 规范的一部分,然后被转移到单独的标准文档中,以保持规范的重点。人们认为,在将其中一些特性纳入官方规范之前,在单独的轨道上对它们进行讨论和编辑是更明智的做法。这样,一个小的有争议的标记问题不会阻碍整个规范的展示。

特定领域的专家可以在邮件列表上一起讨论给定的特性,而不会有太多的争论。业界仍然将包括地理定位等在内的原始功能称为 HTML5。那么,可以把 HTML5 想象成一个涵盖核心标记以及许多很酷的新 API 的总称。在撰写本文时,这些特性是 HTML5 的一部分:

  • 画布(2D 和 3D)
  • 跨文档消息传递
  • 地理定位
  • 音频和视频
  • 形式
  • 数学公式
  • 微观数据
  • 服务器发送的事件
  • 可缩放矢量图形(SVG)
  • WebSocket API 和协议
  • 网络起源概念
  • 网络存储
  • 索引数据库
  • 应用缓存(离线 Web 应用)
  • 网络工作者
  • 拖放
  • XMLHttpRequest 级别 2

如你所见,我们在本书中涉及的许多 API 都在这个列表中。我们如何选择覆盖哪些 API?我们选择覆盖至少有些烘焙的特性。翻译?它们以某种形式存在于不止一个浏览器中。其他(不太成熟的)功能可能只在一个特殊的浏览器测试版中起作用,而其他功能目前还只是想法。

就浏览器支持而言,有一些优秀的在线资源可以用来检查当前(和未来)的浏览器支持。站点[www.caniuse.com](http://www.caniuse.com)提供了按浏览器版本细分的功能和浏览器支持的详尽列表,站点[www.html5test.com](http://www.html5test.com)检查您用来访问它的浏览器对 HTML5 功能的支持。

此外,这本书并不关注为您提供仿真解决方案,让您的 HTML5 应用在老式浏览器上无缝运行。相反,我们将主要关注 HTML5 的规范以及如何使用它。也就是说,对于每个 API,我们都提供了一些示例代码,您可以使用它们来检测其可用性。我们不使用通常不可靠的用户代理检测,而是使用特性检测。为此,你也可以使用Modernizr——一个 JavaScript 库,提供非常高级的 HTML5 和 CSS3 特性检测。我们强烈建议您在应用中使用 Modernizr,因为它无疑是最好的工具。

HTML 中的更多时刻

弗兰克说:“你好,我是弗兰克,我有时会画画。

我看到的第一个 HTML 画布演示是一个基本的绘画应用,它模仿了 Microsoft Paint 的用户界面。尽管它比数字绘画的艺术水平落后了几十年,而且当时只能在现有浏览器的一小部分上运行,但它让我开始思考它所代表的可能性。

当我进行数码绘画时,我通常使用本地安装的桌面软件。虽然其中一些程序非常优秀,但它们缺乏让 web 应用如此出色的特性。简而言之,它们是脱节的。迄今为止,共享数字绘画涉及从绘画应用中导出图像并将其上传到网络。在现场画布上合作或评论是不可能的。HTML5 应用可以缩短导出周期,并使创作过程与完成的图像一起融入在线世界。

不能用 HTML5 实现的应用数量正在减少。对于文本来说,网络已经是终极的双向交流媒介。基于文本的应用有完全基于网络的形式。他们的图形对应物,如绘画、视频编辑和 3D 建模软件,现在才刚刚出现。

我们现在可以构建优秀的软件来创建和欣赏图像、音乐、电影等等。更好的是,我们制作的软件可以在网上或网下使用:一个无处不在、授权和在线的平台。"

HTML5 有什么新功能?

在开始编写 HTML5 之前,我们先来快速了解一下 HTML5 的新特性。

新文档类型和字符集

首先,网页的DOCTYPE被大大简化了。例如,比较下面的 HTML4 DOCTYPE:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"![Image](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0cb3b458bd2143d8a02d2c5bc6e03a4b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771851095&x-signature=WMyTQLDRkXxWKqGqYZfxaEjm1J4%3D)  "http://www.w3.org/TR/html4/loose.dtd">

谁会记得这些呢?我们当然不能。我们总是将一些冗长的DOCTYPE复制并粘贴到页面上,内心深处总是担心,“你绝对确定你粘贴的是正确的吗?”HTML5 巧妙地解决了这个问题,如下所示:

<!DOCTYPE html>

现在那是 a DOCTYPE你可能只记得。和新的DOCTYPE一样,字符集声明也被缩写了。曾经是

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

现在,它是:

<meta charset="utf-8">

如果您愿意,甚至可以去掉“utf-8”两边的引号。使用新的DOCTYPE触发浏览器以标准模式显示页面。例如,图 1-1 显示了如果你在 Firefox 中打开一个 HTML5 页面,点击工具Image页面信息,你会看到的信息。在此示例中,页面以标准模式呈现。

Image

***图 1-1。*以符合标准的模式呈现的页面

当您使用新的 HTML5 DOCTYPE时,它会触发浏览器以符合标准的模式呈现页面。如您所知,Web 页面可以有不同的呈现模式,比如古怪、近乎标准和标准(或无古怪)模式。DOCTYPE向浏览器指示使用哪种模式以及使用什么规则来验证您的页面。在 Quirks 模式下,浏览器试图避免破坏页面,即使它们不完全有效也要呈现它们。HTML5 引入了新元素,并将其他元素标记为过时元素(下一节将详细介绍)。如果您使用这些过时的元素,您的页面将无效。然而,浏览器会像以前一样继续呈现它们。

新的和废弃的元素

HTML5 引入了许多新的标记元素,并将其分为七种不同的内容类型。这些在下面的表 1-1 中显示。

Image

Image

这些元素中的大多数都可以用 CSS 进行样式化。此外,它们中的一些,比如canvasaudiovideo,可以单独使用,尽管它们伴随着允许细粒度本地编程控制的 API。这些 API 将在本书后面更详细地讨论。

讨论所有这些新元素超出了本书的范围,但是大多数剖切元素(在下一节中讨论)都是新的。在 HTML5 中,canvasaudiovideo元素也是新的。

同样,我们也不打算提供一个所有不推荐使用的标签的详尽列表(网上有很多关于这方面的好的在线资源),但是许多执行内联样式的元素已经被标记为过时,以利于使用 CSS,比如bigcenterfontbasefont

语义标记

包含许多新 HTML5 元素的一种内容类型是 sectioning 内容类型。HTML5 定义了一个新的语义标记来描述元素的内容。使用语义标记不会给最终用户带来任何直接的好处,但是它确实简化了 HTML 页面的设计。更重要的是,它将使你的网页更易于机器阅读和访问。例如,搜索和联合引擎在抓取和索引页面时肯定会利用这些元素。

正如我们之前说过的,HTML5 就是要铺好牛路。谷歌和 Opera 分析了数百万个页面,以发现DIV标签的通用 ID 名称,并发现了大量重复。例如,由于许多人使用DIV id="footer"来标记页脚内容,HTML5 提供了一组新的分节元素,您现在可以在现代浏览器中使用。表 1-2 显示了不同的语义标记元素。

Image

Image

所有这些元素都可以用 CSS 样式化。事实上,正如我们在前面的实用程序设计原则中所描述的,HTML5 推动了内容和表示的分离,所以您必须在 HTML5 中使用 CSS 样式来设计您的页面。清单 1-1 展示了一个 HTML5 页面可能的样子。它使用新的DOCTYPE、字符集和语义标记元素——简而言之,新的分段内容。代码文件(sample.html)在code/intro文件夹中。

***清单 1-1。*一个 HTML5 页面的例子

`

     HTML5           

Header

    

Subtitle

    
HTML5 Rocks!
  

    

                   

Nav

          Link 1           Link 2           Link 3                                                                   

Article Header

                             

Lorem ipsum dolor HTML5 nunc aut nunquam sit amet, consectetur adipiscing elit. Vivamus at                         est eros, vel fringilla urna.

              

Per inceptos himenaeos. Quisque feugiat, justo at vehicula pellentesque, turpis                      lorem dictum nunc.

                               

Article Footer

                                                                        

Article Header

                             

HTML5: "Lorem ipsum dolor nunc aut nunquam sit amet, consectetur                      adipiscing elit. Vivamus at est eros, vel fringilla urna. Pellentesque odio

                      

Article Footer

                                                        

Aside

          

HTML5: "Lorem ipsum dolor nunc aut nunquam sit amet, consectetur adipiscing                    elit. Vivamus at est eros, vel fringilla urna. Pellentesque odio rhoncus

                            

Footer

           

`

如果没有样式,页面看起来会很乏味。清单 1-2 显示了一些可用于样式化内容的 CSS 代码。代码文件(html5.css)位于code/intro文件夹中。这个样式表使用了一些新的 CSS3 特性,比如圆角(border-radius)和旋转变换(transform: rotate();)。CSS3——就像 HTML5 本身一样——仍在开发中,为了更容易被浏览器接受,它使用子规范进行了模块化(例如,转换、动画和过渡都在单独的子规范中)。

实验性的 CSS3 特性以供应商字符串为前缀,以避免规范改变时的命名空间冲突。为了显示圆角、渐变、阴影和变换,目前需要在声明中使用前缀,如-moz-(Mozilla)、o-(Opera)、-webkit-(Safari 和 Chrome 等基于 WebKit 的浏览器)和-ms-(Internet Explorer)。

***清单 1-2。*html 5 页面的 CSS 文件

`body {         background-color:#CCCCCC;         font-family:Geneva,Arial,Helvetica,sans-serif;         margin: 0px auto;         max-width:900px;         border:solid;         border-color:#FFFFFF; }

header {         background-color: #F47D31;         display:block; color:#FFFFFF;         text-align:center; }

header h2 {         margin: 0px; }

h1 {         font-size: 72px;         margin: 0px; }

h2 {         font-size: 24px;         margin: 0px;         text-align:center;         color: #F47D31; }

h3 {         font-size: 18px;         margin: 0px;         text-align:center;         color: #F47D31; }

h4 {         color: #F47D31;         background-color: #fff;         -webkit-box-shadow: 2px 2px 20px #888;         -webkit-transform: rotate(-45deg);         -moz-box-shadow: 2px 2px 20px #888;         -moz-transform: rotate(-45deg);         position: absolute;         padding: 0px 150px;         top: 50px;         left: -120px;         text-align:center;

}

nav {         display:block;         width:25%;         float:left; }

nav a:link, nav a:visited {         display: block;         border-bottom: 3px solid #fff;         padding: 10px; text-decoration: none;         font-weight: bold;         margin: 5px; }

nav a:hover {         color: white;         background-color: #F47D31; }

nav h3 {         margin: 15px;         color: white; }

#container {         background-color: #888; }

section {         display:block;         width:50%;         float:left; }

article {         background-color: #eee;         display:block;         margin: 10px;         padding: 10px;         -webkit-border-radius: 10px;         -moz-border-radius: 10px;         border-radius: 10px;         -webkit-box-shadow: 2px 2px 20px #888;         -webkit-transform: rotate(-10deg);         -moz-box-shadow: 2px 2px 20px #888;         -moz-transform: rotate(-10deg); }

article header {         -webkit-border-radius: 10px;         -moz-border-radius: 10px;         border-radius: 10px;         padding: 5px;

}

article footer {         -webkit-border-radius: 10px;         -moz-border-radius: 10px;         border-radius: 10px;         padding: 5px; }

article h1 {         font-size: 18px; }

aside {         display:block;         width:25%;         float:left; }

aside h3 {         margin: 15px;         color: white; }

aside p {         margin: 15px;         color: white;         font-weight: bold;         font-style: italic; }

footer {         clear: both;         display: block;         background-color: #F47D31;         color:#FFFFFF;         text-align:center;         padding: 15px; }

footer h2 {         font-size: 14px;         color: white; }

/* links */ a {         color: #F47D31; }

a:hover {         text-decoration: underline; }`

图 1-2 显示了清单 1-1 中页面的一个例子,使用 CSS(和一些 CSS3)样式。但是,请记住,不存在典型的 HTML5 页面。任何事情都有可能发生,这个例子使用了许多新的标签,主要是为了演示。

Image

***图 1-2。*一个包含所有新语义标记元素的 HTML5 页面

最后要记住的一点是,浏览器可能看起来好像它们实际上理解这些新元素。然而,事实是,这些元素本来可以被重命名为foobar,然后进行样式化,它们也会以同样的方式呈现(但是当然,它们在搜索引擎优化中不会有任何好处)。唯一的例外是 Internet Explorer,它要求元素是 DOM 的一部分。因此,如果您想在 IE 中看到这些元素,您必须以编程方式将它们插入到 DOM 中,并将它们显示为块元素。一个方便的脚本就是 html5shiv ( [code.google.com/p/html5shiv/](http://code.google.com/p/html5shiv/))。

使用选择器 API 简化选择

除了新的语义元素,HTML5 还引入了在页面 DOM 中查找元素的新的简单方法。表 1-3 显示了先前版本的文档对象,它允许开发人员进行一些调用来找到页面中的特定元素。

Image

有了新的选择器 API,现在有了更精确的方法来指定想要检索哪些元素,而不需要使用标准 DOM 在文档中循环和迭代。选择器 API 公开了与 CSS 中相同的选择器规则,作为在页面中查找一个或多个元素的方法。例如,CSS 已经有了基于嵌套、兄弟和子模式选择元素的便利规则。CSS 的最新版本增加了对更多伪类的支持——例如,是否启用、禁用或检查一个对象——以及您可以想象的任何属性和层次结构的组合。要使用 CSS 规则选择 DOM 中的元素,只需利用表 1-4 中所示的函数之一。

Image

还可以向选择器 API 函数发送多个选择器规则,例如:

// select the first element in the document with the // style class highClass or the style class lowClass var x = document.querySelector(“.highClass”, “.lowClass”);

querySelector()的情况下,选择匹配任一规则的第一个元素。在querySelectorAll()的情况下,将返回与任何列出的规则匹配的任何元素。多个规则以逗号分隔。

新的选择器 API 使得选择以前很难跟踪的文档部分变得容易。例如,假设您希望能够找到当前鼠标悬停在其上的表格中的任意单元格。清单 1-3 展示了使用选择器是多么简单。这个示例文件(querySelector.htmlquerySelectorAll.html)位于code/intro目录中。

***清单 1-3。*使用选择器 API

`

     Query Selector Demo

       td {       border-style: solid;       border-width: 1px;       font-size: 300%;     }

    td:hover {       background-color: cyan;     }

    #hoverResult {       color: green;       font-size: 200%;     }   

                                                                                    
A1 A2 A3
B1 B2 B3
C1 C2 C3

    

Focus the button, hover over the table cells, and hit Enter to identify them  using querySelector('td:hover').
    Find 'td:hover' target     

       

`

从这个例子中可以看出,查找用户悬停的元素是一个简单的练习,使用:

var hovered = document.querySelector("td:hover");

Image 注意选择器 API 不仅方便,而且通常比使用遗留子检索 API 遍历 DOM 更快。为了实现快速样式表,浏览器针对选择器匹配进行了高度优化。

在 W3C,选择器的正式规范与 CSS 的规范是分开的,这并不奇怪。正如您在这里看到的,选择器通常在样式之外很有用。新选择器的全部细节超出了本书的范围,但是如果您是一名正在寻找操作 DOM 的最佳方法的开发人员,我们鼓励您使用新的选择器 API 来快速导航您的应用结构。

JavaScript 日志记录和调试

尽管从技术上讲,JavaScript 日志和浏览器内调试工具并不是 HTML5 的特性,但在过去几年中,它们已经得到了很大的改进。第一个分析网页和其中运行的代码的伟大工具是 Firefox 插件 Firebug。

类似的功能现在可以在所有其他浏览器的内置开发工具中找到:Safari 的 Web Inspector、谷歌的 Chrome 开发者工具、Internet Explorer 的开发者工具和 Opera 的蜻蜓。图 1-3 展示了谷歌 Chrome 开发者工具(在 Windows 上使用快捷键 CTRL + Shift + J 或者在 Mac 上使用快捷键 Command + Option + J 来访问这个),这些工具提供了关于你的网页的大量信息;这些视图包括调试控制台、元素视图、资源视图和脚本视图,仅举几个例子。

Image

***图 1-3。*Chrome 中的开发者工具视图

许多调试工具都提供了一种设置断点来暂停代码执行以及分析程序状态和变量当前状态的方法。API 已经成为 JavaScript 开发者事实上的日志标准。许多浏览器都提供了分窗格视图,允许您查看记录到控制台的消息。使用console.log比调用alert()好得多,因为它不会暂停程序执行。

窗口。数据

JSON 是一种相对较新且越来越流行的表示数据的方式。它是将数据表示为对象文字的 JavaScript 语法的子集。由于其简单性和对 JavaScript 编程的天然适应性,JSON 已经成为 HTML5 应用中数据交换的事实上的标准。JSON 的规范 API 有两个函数,parse()stringify()(意思是序列化或者转换成字符串)。

要在旧浏览器中使用 JSON,需要一个 JavaScript 库(可以在[json.org](http://json.org)找到几个)。JavaScript 中的解析和序列化并不总是像您希望的那样快,所以为了加快速度,新的浏览器现在有了 JSON 的本机实现,可以从 JavaScript 调用。本机 JSON 对象被指定为 ECMAScript 5 标准的一部分,涵盖了下一代 JavaScript 语言。它是 ECMAScript 5 第一批被广泛实现的部分之一。现在每个现代浏览器都有window.JSON,你可以期待在 HTML5 应用中看到相当多的 JSON。

DOM 三级

web 应用开发中最受诟病的部分之一是事件处理。虽然大多数浏览器支持事件和元素的标准 API,但 Internet Explorer 有所不同。早期,Internet Explorer 实现了一个不同于最终标准的事件模型。Internet Explorer 9 (IE9)现在支持 DOM Level 2 和 3 特性,因此您最终可以在所有 HTML5 浏览器中使用相同的代码进行 DOM 操作和事件处理。这包括非常重要的addEventListener()dispatchEvent()方法。

猴子、松鼠和其他速度奇快的动物

最新一轮的浏览器创新不仅仅是新标签和新 API。最近最重要的变化之一是主流浏览器中 JavaScript/ECMAScript 引擎的快速发展。正如新的 API 开放了在上一代浏览器中不可能实现的功能一样,整个脚本引擎的执行速度加快有利于现有的 web 应用和那些使用最新 HTML5 功能的应用。认为您的浏览器无法处理复杂的图像或数据处理,或者冗长文稿的编辑?再想想。

在过去的几年里,浏览器供应商一直在进行虚拟军备竞赛,看谁能开发出最快的 JavaScript 引擎。虽然 JavaScript 最早的迭代是纯解释的,但最新的引擎将脚本代码直接编译成本机代码,与 2000 年代中期的浏览器相比,速度提高了几个数量级。

2006 年,Adobe 向 Mozilla 项目捐赠了 ECMAScript 的实时(JIT)编译引擎和虚拟机——代号为 Tamarin——时,这一行动就开始了。尽管只有少量的 Tamarin 技术保留在最新版本的 Mozilla 中,但 Tamarin 的贡献帮助在每种浏览器中产生了新的脚本引擎,它们的名字就像它们声称的性能一样有趣。

Image

总而言之,浏览器厂商之间的良性竞争使得 JavaScript 的性能越来越接近原生桌面应用代码。

HTML 中的更多时刻

彼得说:“说到竞争和速度奇快,我的名字叫彼得,跑步是我的爱好——经常跑步。

超跑是一项很棒的运动,在这里你会遇到很棒的人。在跑完 100 英里赛跑或 165 英里越野跑的最后几英里时,你真的会以一种全新的方式去了解别人。在那一点上,你真的被剥离到你的本质,伟大的友谊可以发生的地方。当然,仍然有竞争的因素,但最重要的是有一种深厚的同志情谊。但是我在这里跑题了。

为了跟踪我的朋友在我不能参加的比赛中的表现(例如,当我在写一本 HTML5 的书时),我通常会在比赛网站上关注。不足为奇的是,“实时跟踪”选项通常很不可靠。

几年前,我偶然发现了一个欧洲竞赛的网站,里面有所有正确的想法。他们给跑在前面的人发放 GPS 追踪器,然后在地图上显示这些参赛者(我们将在本书中使用地理定位和 WebSocket 构建一些类似的演示)。尽管这是一个相当原始的实现(用户必须点击“刷新页面”才能看到更新!),我可以立即看到不可思议的潜力。

现在,仅仅几年后,HTML5 为我们提供了工具来构建这种现场比赛跟踪网站,其中包括用于位置感知应用的地理定位和用于实时更新的 WebSockets 等 API。毫无疑问,HTML5 已经以胜利者的姿态冲过了终点线!"

总结

在这一章中,我们已经给了你一个 HTML5 的基本概要。

我们绘制了它的发展历史和一些即将到来的重要日期。我们还概述了 HTML5 时代背后的四个新的设计原则:兼容性、实用性、互操作性和通用访问。这些原则中的每一条都为一个充满可能性的世界打开了大门,并关闭了一系列现在已经过时的实践和惯例。然后我们介绍了 HTML5 令人吃惊的新的无插件范例,我们回顾了 HTML5 的新特性,比如新的DOCTYPE和字符集,大量新的标记元素,我们还讨论了 JavaScript 霸权的竞争。

在下一章,我们将从探索 HTML5 的编程方面开始,从 Canvas API 开始。

二、使用画布 API

在这一章中,我们将探索你可以用 Canvas API 做什么——一个很酷的 API,使你能够动态地生成和渲染图形、图表、图像和动画。我们将指导您使用渲染 API 的基础知识来创建一个可以根据浏览器环境进行缩放和调整的绘图。我们将向您展示如何在热图显示中基于用户输入创建动态图片。当然,我们也会提醒您使用 Canvas 的陷阱,并分享克服它们的技巧。

这一章只假设了少量的图形专业知识,所以不要害怕尝试 HTML5 最强大的特性之一。

html 5 画布概述

关于 Canvas API 的使用可以写一整本书(而且不会是一本小书)。因为我们只有一章,所以我们将涵盖(我们认为是)这个非常广泛的 API 中最常用的功能。

历史

canvas 的概念最初是由苹果公司引入的,用于 Mac OS X WebKit 来创建仪表板小部件。在 canvas 出现之前,你只能通过插件在浏览器中使用绘图 API,例如用于 Flash 和可缩放矢量图形(SVG)的 Adobe 插件,仅在 Internet Explorer 中使用的矢量标记语言(VML),或者其他聪明的 JavaScript 黑客。

例如,尝试在没有canvas元素的情况下绘制一条简单的对角线——这听起来很容易,但如果您没有简单的二维绘图 API,这将是一项相当复杂的任务。Canvas 正好提供了这一点,因为它在浏览器中非常有用,所以被添加到 HTML5 规范中。

早些时候,苹果暗示可能会在 WHATWG 的 canvas 规范草案中保留知识产权,这在当时引起了一些 web 标准化追随者的担忧。然而,最终苹果还是根据 W3C 的免版税专利许可条款披露了这些专利。

SVG 对 Canvas

Peter 说:“画布本质上是一个位图画布,因此在画布上绘制的图像是最终的,不能像可缩放矢量图形(SVG)图像那样调整大小。此外,绘制在画布上的对象不是页面 DOM 的一部分,也不是任何名称空间的一部分——如果您需要点击检测或基于对象的更新,这被认为是一个弱点。另一方面,SVG 图像可以在不同的分辨率下无缝缩放,并允许点击检测(精确知道图像被点击的位置)。

那么,WHATWG HTML5 规范为什么不专门使用 SVG 呢?尽管 HTML5 Canvas API 有明显的缺点,但它有两个优点:它的性能很好,因为它不必为它绘制的每个图元存储对象;基于其他编程语言中常见的许多二维绘图 API 实现 Canvas API 相对容易。归根结底,一鸟在手总比双鸟在林好。"

什么是画布?

当你在网页中使用一个canvas元素时,它会在页面上创建一个矩形区域。默认情况下,这个矩形区域是 300 像素宽和 150 像素高,但是您可以为您的canvas元素指定确切的大小和设置其他属性。清单 2-1 展示了可以添加到 HTML 页面的最基本的canvas元素。

***清单 2-1。*一个基本的画布元素

<canvas></canvas>

一旦您将一个canvas元素添加到页面中,您就可以使用 JavaScript 以任何您想要的方式操作它。您可以向其中添加图形、线条和文本。你可以在上面画画;你甚至可以给它添加高级动画。

Canvas API 支持大多数现代操作系统和框架支持的相同的二维绘制操作。如果您在最近几年中曾经编写过二维图形,您可能会对 Canvas API 如鱼得水,因为它的设计与现有系统非常相似。如果您还没有,您将会发现一个渲染系统比以前的图像和 CSS 技巧强大得多,这些是开发人员多年来用来创建 web 图形的。

要以编程方式使用画布,您必须首先获得它的上下文。然后,您可以对上下文执行操作,并最终将这些操作应用于上下文。您可以将画布修改视为类似于数据库事务:您启动一个事务,执行某些操作,然后提交该事务。

画布坐标

如图 2-1 中的所示,画布中的坐标从左上角的x=0,y=0开始——我们称之为原点——并在 x 轴的水平方向和 y 轴的垂直方向增加(以像素为单位)。

Image

***图 2-1。*画布上的 x 和 y 坐标

何时不用帆布

虽然canvas元素很棒并且非常有用,但是当另一个元素足够时,你应该而不是使用canvas元素。例如,在画布上动态绘制 HTML 文档的所有不同标题,而不是简单地使用为此目的而设计的标题样式(H1、H2 等),这并不是一个好主意。

回退内容

如果您的网页是由不支持canvas元素或 Canvas API 特性子集的浏览器访问的,那么提供一个替代源是一个好主意。例如,您可以提供一个替代图像或一些文本来解释如果用户真的使用现代浏览器,他们会喜欢什么。清单 2-2 展示了如何在canvas元素中指定替换文本。不支持canvas元素的浏览器将简单地呈现这个后备内容。

***清单 2-2。*在画布元素中使用回退文本

<canvas>   Update your browser to enjoy canvas! </canvas>

如果浏览器不支持canvas元素,您也可以指向一个可以显示的图像,而不是前面显示的文本。

画布可访问性呢?

Peter 说:“提供替代图像或替代文本引发了可访问性的问题——不幸的是,画布规范在这个领域仍然严重不足。例如,对于插入到画布中的图像,没有本地方法来插入文本替换,也没有本地方法来提供替换文本以匹配使用画布文本 API 生成的文本。在撰写本文时,还没有可用于画布中动态生成内容的可访问性挂钩,但是一个工作组正在设计它们。希望这种情况会随着时间的推移而改善。”

HTML5 设计者对于处理可替换的、可访问的画布内容的当前提议之一是使用这个回退内容部分。然而,为了让它对屏幕阅读器和其他辅助工具有用,即使支持和显示画布,回退内容也需要是键盘可导航的。虽然现在有些浏览器支持这种功能,但是您不应该依赖它来支持有特殊需求的用户。目前推荐使用页面的一个单独部分来显示画布替代。额外的好处是,许多用户可能喜欢使用替代控件或显示,作为快速理解和导航页面或应用的更好方式。

Canvas API 的未来版本可能还会包含 Canvas 显示的可聚焦子区域以及与之交互的控件。但是,如果您的图像显示需要大量的交互,可以考虑使用 SVG 作为 Canvas API 的替代。SVG 也允许绘图,但是它也集成了浏览器 DOM。

CSS 和画布

与大多数 HTML 元素一样,CSS 可以应用于canvas元素本身来添加边框、填充、边距等。另外,一些 CSS 值被canvas的内容继承;字体就是一个很好的例子,因为绘制到canvas中的字体默认为canvas元素本身的设置。

此外,在canvas操作中使用的context上设置的属性遵循您可能已经从 CSS 中熟悉的语法。例如,颜色和字体在context上使用的符号与它们在任何 HTML 或 CSS 文档中使用的符号相同。

浏览器支持 HTML5 画布

随着 Internet Explorer 9 的到来,现在所有的浏览器供应商都提供了对 HTML5 Canvas 的支持,并且它已经掌握在大多数用户的手中。这是网络发展的一个重要里程碑,让 2D 绘画在现代网络上蓬勃发展。

尽管以前版本的 Internet Explorer 的市场份额不断减少,但在使用 API 之前,先测试 HTML5 Canvas 是否受支持仍然是一个好主意。本章后面的“检查浏览器支持”一节将向您展示如何以编程方式检查浏览器支持。

使用 HTML5 画布 API

在这一节中,我们将更详细地探索 Canvas APIs 的使用。为了便于说明——没有双关语的意思——我们将使用各种 Canvas APIs 构建一个类似于徽标的森林场景显示,其中有树木和一条适合长距离比赛的美丽的小径。虽然我们的例子不会赢得任何图形设计的奖项,但它应该有助于以合理的顺序说明 HTML5 Canvas 的各种功能。

检查浏览器支持

在使用canvas元素之前,您会希望确保浏览器中有支持。这样,您可以提供一些替代文本,以防他们的老式浏览器不支持。清单 2-3 展示了一种测试浏览器支持的方法。

***清单 2-3。*检查浏览器支持

try {   document.createElement("canvas").getContext("2d");   document.getElementById("support").innerHTML =     "HTML5 Canvas is supported in your browser."; } catch (e) {   document.getElementById("support").innerHTML = "HTML5 Canvas is not supported ![Image](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0cb3b458bd2143d8a02d2c5bc6e03a4b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5biD5a6i6aOe6b6Z:q75.awebp?rk3s=f64ab15b&x-expires=1771851095&x-signature=WMyTQLDRkXxWKqGqYZfxaEjm1J4%3D)                                                   in your browser."; }

在本例中,您尝试创建一个画布对象并访问其上下文。如果有错误,您将捕捉到它,并知道 Canvas 不受支持。页面上先前定义的支持元素用合适的消息更新,以反映是否有浏览器支持。

该测试将表明浏览器是否支持canvas元素本身。它不会指出支持画布的哪些功能。在撰写本文时,该 API 是稳定的并且得到了很好的支持,所以这通常不是一个需要担心的问题。

此外,给你的canvas元素提供后备内容也是一个好主意,如清单 2-3 所示。

向页面添加画布

在 HTML 页面中添加一个canvas元素非常简单。清单 2-4 显示了可以添加到 HTML 页面的canvas元素。

***清单 2-4。*画布元素

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

生成的画布将在页面上显示为“不可见”的 200 × 200 像素矩形。如果你想在它周围添加一个边框,你可以使用清单 2-5 所示的 HTML 代码,用普通的 CSS 边框来设置画布的样式。

***清单 2-5。*带实线边框的画布元素

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

请注意添加了 ID diagonal以便于以编程方式定位这个canvas元素。ID 属性对于任何画布都是至关重要的,因为这个元素上所有有用的操作都必须通过脚本来完成。如果没有 ID,您将很难定位元素以与其进行互操作。

图 2-2 显示了清单 2-5 中的画布在浏览器中的样子。

Image

***图 2-2。*一个简单的 HTML 页面上的 HTML5 画布元素

不是很令人兴奋,但是任何艺术家都会告诉你,它充满了潜力。现在,让我们用这块原始的画布做点什么。如前所述,如果没有 HTML5 Canvas,在网页上画对角线并不容易。让我们看看现在可以使用 Canvas 有多简单。清单 2-6 展示了如何用几行代码,在我们之前添加到页面的画布上画一条对角线。

***清单 2-6。*在画布上画一条对角线

`

    // Create a path in absolute coordinates     context.beginPath();     context.moveTo(70, 140);     context.lineTo(140, 70);

    // Stroke the line onto the canvas     context.stroke();   }

   window.addEventListener("load", drawDiagonal, true); `

让我们检查一下用于创建对角线的 JavaScript 代码。这是一个简单的例子,但是它抓住了使用 Canvas API 的基本流程:

首先,通过引用特定画布的 ID 值来访问画布对象。在这个例子中,ID 是diagonal。接下来,您创建一个上下文变量,并调用 canvas 对象的getContext方法,传入您正在寻找的画布类型。您传入字符串“2d”来获得一个二维上下文——此时唯一可用的上下文类型。

Image 注意很多工作已经在三维版本的画布上完成。WebGL 规范的 1.0 版本是浏览器供应商和 Khronos Group 的共同努力,于 2011 年初发布。WebGL 基于与流行的 OpenGL 库相同的概念和设计,为 JavaScript 和 HTML5 带来了类似的 API。要在支持的浏览器中创建三维绘图上下文,只需使用字符串"webgl"作为getContext的参数。最终的上下文拥有一套全新的绘图 API:对他们自己的书来说足够全面和复杂的能力。尽管现在一些浏览器已经发布了 WebGL 的实现,但是并不是所有的供应商都已经发布了。然而,网络上三维渲染的潜力是如此引人注目,以至于我们预计在未来几年内会得到快速的支持。欲了解更多信息,请访问 Khronos 集团的 WebGL 网站([www.khronos.org/webgl](http://www.khronos.org/webgl))。我们将在本书的最后一章更详细地讨论 WebGL。

然后,您可以使用该上下文来执行绘图操作。在这种情况下,您可以通过调用三个方法来创建对角线,这三个方法是:beginPathmoveTolineTo,传递直线的起点和终点坐标。

绘图方法moveTolineTo并不实际创建线条;您通过调用context.stroke();方法来完成画布操作并画线。图 2-3 显示了用示例代码创建的对角线。

Image

***图 2-3。*画布上的对角线

凯旋!尽管这条简单的线可能看起来并不是一场革命的开始,但请记住,使用经典的 HTML 技术在任意两点之间绘制对角线是一个非常困难的操作,涉及到拉伸的图像、奇怪的 CSS 和 DOM 对象,或者其他形式的魔法。让我们再也不要提起他们。

从这个例子的代码中可以看出,画布上的所有操作都是通过 context 对象执行的。这将适用于您与画布的其他交互,因为可视化输出的所有重要功能都只能从上下文中访问,而不能从画布对象本身访问。这种灵活性允许画布在将来基于从画布中检索的上下文类型支持不同类型的绘制模型。虽然在本章中我们会经常提到我们将在画布上采取的动作,但是请记住,这实际上意味着我们将使用画布提供的上下文对象。

如前面的示例所示,上下文中的许多操作不会立即更新绘图图面。诸如beginPathmoveTolineTo等功能不会立即修改画布外观。许多设置画布样式和偏好的函数也是如此。只有当路径被敲击填充时,它才会出现在显示屏上。否则,只有在显示图像、显示文本或绘制、填充或清除矩形时,画布才会立即更新。

对图纸应用变换

现在让我们看看使用变换在画布上绘图的另一种方法。在下面的示例中,结果与前面的示例相同,但是用于绘制对角线的代码不同。对于这个简单的例子,您可能会认为转换的使用增加了不必要的复杂性。然而,您可以将转换作为更复杂的画布操作的最佳实践。您将会看到,在余下的示例中,我们会大量使用它,它对于理解 Canvas API 的复杂功能至关重要。

也许最容易想到转换系统的方法——至少,不涉及大量数学公式和手势的最简单方法——是作为一个修改层,位于您发出的命令和画布显示上的输出之间。这个修改层总是存在的,即使你选择不与它交互。

修改,或者用绘图系统的术语来说是转换,可以按顺序应用、组合和随意修改。每个绘图操作在出现在画布上之前都要经过要修改的修改层。虽然这增加了一层额外的复杂性,但也给绘图系统增加了巨大的能力。它允许对现代图像编辑工具实时支持的强大修改进行访问,而 API 的复杂程度只是绝对需要的。

如果在代码中不使用转换调用,不要误以为你在优化性能。canvas 实现在其呈现引擎中隐式地使用和应用转换,无论您是否直接调用它们。更明智的做法是预先了解系统,因为知道是否超出最基本的绘图操作是至关重要的。

对可重用代码的一个关键建议是,你通常希望在原点(坐标 0,0)处绘制,并应用变换——缩放、平移、旋转等等——将你的绘制代码修改成它的最终外观,如图图 2-4 所示。

Image

***图 2-4。*原点变换绘制概述

清单 2-7 使用最简单的转换translate展示了这个最佳实践。

***清单 2-7。*使用平移在画布上创建一条对角线

<script> `  function drawDiagonal() {     var canvas = document.getElementById('diagonal');     var context = canvas.getContext('2d');

    // Save a copy of the current drawing state     context.save();

    // Move the drawing context to the right, and down     context.translate(70, 140);

    // Draw the same line as before, but using the origin as a start     context.beginPath();     context.moveTo(0, 0);     context.lineTo(70, -70);     context.stroke();

    // Restore the old drawing state     context.restore();   }

  window.addEventListener("load", drawDiagonal, true); `

让我们检查一下用来创建第二条翻译后的对角线的 JavaScript 代码。

  1. First, access the canvas object by referring to its ID value (in this case, diagonal).
  2. Then retrieve a context variable by calling the getContext function of the canvas object.
  3. Next, you want to save the context that is still unmodified, so that you can return to its original state at the end of the drawing and conversion operation. If the state is not saved, the modifications (translation, scaling, etc.) made during the operation will continue to be applied to the context in future operations, which may not be desirable. Saving the context state before the conversion will allow us to restore it later. The next step is to apply the translate method to the context. With this operation, when drawing any figure, the translation coordinate you provided will be added to the final figure coordinate (diagonal), thus moving the line to its final position, but only after the drawing operation is completed.
  4. After applying translation, you can perform normal drawing operations to create diagonal lines. In this case, you can create diagonal lines by calling three methods (beginPath, moveTo and lineTo), this time drawing at the origin (0,0) instead of coordinates 70,140.
  5. After drawing a line, you can render it to the canvas (such as drawing a line) by calling the context.stroke method.
  6. Finally, you restore the context to its clean original state so that the translation applied in this operation will not be used when performing future canvas operations. Figure 2-5 shows the diagonal created with the sample code.

Image

***图 2-5。*翻译画布上的对角线

尽管您的新行看起来与旧行非常相似,但是您使用转换的力量创建了它,这一点随着我们在本章剩余部分的学习会变得更加明显。

使用路径

虽然我们可以提供更多令人兴奋的画线的例子,但我们现在准备进行一些更复杂的东西:路径。HTML5 Canvas API 中的路径表示您想要呈现的任何形状。我们最初的 line 示例是一个路径,您可能已经从用来启动它的那个明显的beginPath调用中收集到了这一点。但是路径可以如你所愿的那样复杂,有多条直线和曲线段,甚至还有子路径。如果您希望在画布上绘制几乎任何形状,path API 将是您的关注点。

当开始任何绘制形状或路径的例行程序时,您首先调用的是beginPath。这个简单的函数没有参数,但是它向画布发出信号,表示您希望开始一个新的形状描述。此函数对画布非常有用,它可以计算您为以后的填充和描边创建的形状的内部和外部。

路径总是跟踪当前位置的概念,默认为原点。画布在内部跟踪当前位置,但是您将使用您的绘图例程修改它。

形状开始后,您可以使用上下文中的各种功能来绘制形状的布局。您已经看到了最简单的上下文路径函数:

  • moveTo(x, y):将当前位置移动到新的目的地(x,y)而不绘制。
  • lineTo(x, y):将当前位置移动到一个新的终点(x,y),从当前位置到新的终点画一条直线。

本质上,这两个调用之间的区别在于,第一个调用类似于提起绘图笔并移动到新的位置,而第二个调用告诉画布将笔留在纸上,并以直线移动到新的目的地。然而,值得再次指出的是在你描边或填充路径之前,没有实际的绘图发生。目前,我们仅仅是在我们的道路上确定位置,以便以后可以绘制它。

下一个特殊的路径函数是对closePath的调用。该命令的行为与lineTo功能非常相似,不同之处在于目的地被自动假定为路径的起点。然而,closePath也通知画布当前形状已经闭合或者形成了一个完全包含的区域。这对将来的填充和描边很有用。

在这一点上,您可以继续在您的路径中创建更多的子路径。或者您可以随时beginPath重新开始并完全清除路径列表。

对于大多数复杂的系统来说,看到它们的运行通常会更好。让我们离开我们的 line 示例,使用 Canvas API 开始创建一个新的场景,展示一个有小径的森林。这个场景将作为我们比赛的标志。和任何图片一样,我们将从一个基本元素开始,在这个例子中是一棵简单松树的树冠。清单 2-8 显示了如何绘制松树的树冠。

***清单 2-8。*为树冠创建路径的功能

`function createCanopyPath(context) {   // Draw the tree canopy   context.beginPath();

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

  // Top of the tree   context.lineTo(0, -140);

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

  // Close the path back to its start point   context.closePath(); }`

从代码中可以看出,我们使用了与之前相同的 move 和 line 命令,但是使用了更多的命令。这些线条形成了一个简单树形结构的分支,我们在末端封闭了路径。我们的树将在底部留下一个明显的缺口,我们将在以后的章节中使用它来绘制树干。清单 2-9 展示了如何使用树冠绘制函数将我们简单的树的形状绘制到画布上。

***清单 2-9。*在画布上画一棵树的功能

`function drawTrails() {   var canvas = document.getElementById('trails');   var context = canvas.getContext('2d');

  context.save();   context.translate(130, 250);

  // Create the shape for our canopy path   createCanopyPath(context); // Stroke the current path   context.stroke();   context.restore(); }`

这个例程中的所有调用您应该已经很熟悉了。我们获取画布上下文,保存它以供将来参考,将我们的位置转换到一个新的位置,绘制天篷,将其绘制到画布上,然后恢复我们的状态。图 2-6 显示了我们手工制作的结果,一个简单的树冠线条图。随着我们的前进,我们将对此进行扩展,但这是一个良好的开端。

Image

***图 2-6。*一个树冠的简单路径

使用笔画样式

如果开发人员坚持使用简单的简笔画和黑线,Canvas API 就不会强大或受欢迎。让我们使用笔画造型功能,使我们的树冠更像树。清单 2-10 显示了一些基本命令,这些命令可以修改上下文的属性,以使描边形状看起来更有吸引力。

***清单 2-10。*使用笔画样式

`// Increase the line width context.lineWidth = 4;

// Round the corners at path joints context.lineJoin = 'round';

// Change the color to brown context.strokeStyle = '#663300';

// Finally, stroke the canopy context.stroke();`

通过在描边之前添加上述属性,我们可以更改任何未来描边形状的外观——至少在我们将上下文恢复到以前的状态之前。

首先,我们将描边线条的宽度增加到四个像素。

接下来,我们将lineJoin属性设置为round,这使得我们的形状的线段的接合处呈现出更加圆滑的拐角形状。我们也可以将lineJoin设置为bevelmiter(以及相应的context.miterLimit值来调整它)来选择其他角选项。

最后,我们通过使用strokeStyle属性来改变笔画的颜色。在我们的例子中,我们将颜色设置为一个 CSS 值,但是正如您将在后面的章节中看到的,也可以将strokeStyle设置为一个图像模式或一个渐变,用于更好的显示。

虽然我们在这里没有使用它,但是我们也可以将lineCap属性设置为buttsquareround来指定线条应该如何在端点显示。唉,我们的例子没有悬空的线端。图 2-7 显示了我们修整过的树冠,现在用一条更宽、更平滑的棕色线条代替了之前的黑色线条。

Image

***图 2-7。*时髦的抚摸着树冠

使用填充样式

如您所料,描边并不是影响画布形状外观的唯一方式。修改形状的下一个常用方法是指定如何填充其路径和子路径。清单 2-11 展示了用令人愉快的绿色填充我们的树冠是多么简单。

清单 2-11 。使用填充样式

// Set the fill color to green and fill the canopy context.fillStyle = '#339900'; context.fill();

首先,我们将fillStyle设置为适当的颜色。正如我们将在后面看到的,也可以将填充设置为渐变或图像模式。然后,我们简单地调用上下文的fill函数,让画布填充我们当前形状的所有闭合路径内的所有像素,如图图 2-8 所示。

Image

***图 2-8。*满树的树冠

因为我们在填充树冠之前对其进行了描边,所以填充覆盖了描边路径的一部分。这是因为宽笔划(在我们的例子中,四个像素宽)位于路径形状的中心。填充应用于形状内部的所有像素,因此它将覆盖一半的描边线像素。如果您希望显示完整的笔画,您可以在笔画路径之前简单地填充*。*

填充矩形内容

每棵树都应该有一个坚实的基础。谢天谢地,我们在原始形状的路径上为我们的树干留了空间。清单 2-12 展示了我们如何通过使用fillRect便利函数来添加一个树干的最简单的渲染。

***清单 2-12。*使用 fillRect 便捷功能

`// Change fill color to brown context.fillStyle = '#663300';

// Fill a rectangle for the tree trunk context.fillRect(-5, -50, 10, 50);`

在这里,我们再次设置了棕色填充样式。但是,我们将使用fillRect一步完成整个躯干的绘制,而不是使用lineTo功能显式绘制躯干矩形的角。fillRect调用获取 x 和 y 位置,以及宽度和高度,然后立即用当前填充样式填充。

虽然我们在这里没有使用它们,但是strokeRectclearRect有相应的函数。前者将根据给定的位置和尺寸绘制矩形的轮廓,而后者将从矩形区域中删除任何内容,并将其重置为原始的透明颜色。

画布动画

Brian 说:“在画布中清除矩形的能力是使用画布 API 创建动画和游戏的核心。通过重复绘制和清除画布的部分,有可能呈现动画的幻觉,并且许多这样的例子已经存在于网络上。然而,为了创建流畅的动画,你需要利用剪辑功能,甚至可能需要一个二级缓冲画布来最小化频繁的画布清除所导致的闪烁。虽然动画并不是本书的重点,但请查看本章的“实用附加”部分,了解一些使用 HTML5 制作页面动画的技巧。”

图 2-9 显示了我们简单的、扁平填充的树干,连接到我们之前的树冠路径上。

Image

***图 2-9。*矩形树干的树

绘制曲线

世界,尤其是自然界,并不是充满了直线和矩形。幸运的是,画布提供了各种函数来在我们的路径中创建曲线。我们将展示最简单的选择——一条二次曲线——来形成一条穿过虚拟森林的路径。清单 2-13 展示了两条二次曲线的添加。

***清单 2-13。*画曲线

`// Save the canvas state and draw the path context.save();

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

// The first curve bends up and right context.moveTo(0, 0); context.quadraticCurveTo(170, -50, 260, -190);

// The second curve continues down and right context.quadraticCurveTo(310, -250, 410,-250);

// Draw the path in a wide brown stroke context.strokeStyle = '#663300'; context.lineWidth = 20; context.stroke();

// Restore the previous canvas state context.restore();`

像以前一样,我们要做的第一件事是保存我们的画布上下文状态,因为我们将在这里修改翻译和笔画选项。对于我们的森林路径,我们将从回到原点开始,向右上方画第一条二次曲线。

如图 2-10 所示,quadraticCurveTo函数从当前绘图位置开始,以两个 x,y 点位置为参数。第二个是我们曲线的最后一站。第一个代表一个控制点。控制点位于曲线的一侧(不在曲线上),对曲线路径上的点几乎起着引力的作用。通过调整控制点的位置,可以调整正在绘制的路径的曲率。我们向右上方画第二条二次曲线来完成我们的路径;然后抚摸它,就像我们之前对树冠做的那样(只是更宽)。

Image

***图 2-10。*二次曲线起点、终点和控制点

HTML5 Canvas API 中的其他曲线选项包括bezierCurveToarcToarc函数。这些曲线采用额外的控制点、半径或角度来确定曲线的特征。图 2-11 显示了两条二次曲线在我们的画布上画出了一条穿过树林的路径。

Image

***图 2-11。*路径的二次曲线

将图像插入画布

在画布中显示图像非常方便。它们可以被压印、拉伸、变换修改,并且通常是整个画布的焦点。幸运的是,Canvas API 包含一些简单的命令,用于向画布添加图像内容。

但是图像也增加了画布操作的复杂性:您必须等待它们被加载。浏览器通常会在页面脚本呈现时异步加载图像。但是,如果您试图在画布完全加载之前将图像渲染到画布上,画布将根本无法渲染任何图像。因此,在尝试渲染图像之前,您应该小心确保图像已完全加载。

为了在我们简单的森林小径示例中解决这个问题,我们将加载一个树皮纹理的图像,以便直接在画布中使用。为了确保在我们渲染之前图像已经完成加载,我们将把加载代码切换为只作为图像加载完成的回调来执行,如清单 2-14 所示。

***清单 2-14。*加载图像

`// Load the bark image var bark = new Image(); bark.src = "bark.jpg";

// Once the image is loaded, draw on the canvas bark.onload = function () {   drawTrails(); }`

正如您所看到的,我们已经向bark.jpg图像添加了一个onload处理程序,以便仅在图像加载完成时调用主drawTrails函数。这保证了我们添加到画布渲染的下一个调用可以使用这个图像,如清单 2-15 中的所示。

***清单 2-15。*在画布上绘制图像

// Draw the bark pattern image where //  the filled rectangle was before context.drawImage(bark, -5, -50, 10, 50);

这里,我们用一个简单的例程替换了前面对fillRect的调用,将我们的树皮图像显示为我们的树的新树干。虽然图像是一个微妙的替代,但它为我们的显示提供了更多的纹理。注意,在这个调用中,除了图像本身,我们还指定了 x、y、width 和 height 参数。该选项将缩放图像,以适合我们为主干分配的 10 × 50 像素空间。我们还可以传入源维度,以便更好地控制要显示的传入图像的剪辑区域。

正如你在图 2-12 中所看到的,我们躯干外观的变化与我们之前使用的填充矩形仅略有不同。

Image

***图 2-12。*树干使用图像的树

使用渐变

对树干不满意?我们也不是。让我们用另一种更巧妙的方法来绘制我们的树干:渐变。渐变允许你应用一个渐进的算法采样颜色作为一个笔画或填充样式,就像在上一节中应用的模式一样。创建渐变需要三个步骤:

  1. Create the gradient object itself.
  2. Apply the color stop point to the gradient object to signal that the color changes along the transition.
  3. Set the gradient to fillStyle or strokeStyle in context.

也许最容易把渐变想象成沿着一条线移动的平滑的颜色变化。例如,如果您提供 A 点和 B 点作为创建渐变的参数,则任何从 A 点向 B 点移动的描边或填色都将转换颜色。

要确定显示什么颜色,只需对渐变对象本身使用addColorStop函数。此功能允许您指定偏移量和颜色。color 参数是要在偏移位置应用于描边或填充的颜色。偏移位置是一个介于 0.0 和 1.0 之间的值,表示颜色应该沿着渐变线到达多远。

如果创建从点(0,0)到点(0,100)的渐变,并在偏移量 0.0 处指定白色色标,在偏移量 1.0 处指定黑色色标,则当发生描边或填充时,随着渲染从点(0,0)到点(0,100),您会看到颜色逐渐从白色(开始色标)变为黑色(结束色标)。

与其他颜色值一样,可以提供一个 alpha(例如,透明度)值作为颜色的一部分,并使该 alpha 值过渡。为此,您需要使用颜色值的另一种文本表示,比如包含 alpha 组件的 CSS rgba函数。

让我们通过一个代码示例来更详细地了解这一点,该示例将两个渐变应用于代表我们最终树干的fillRect,如清单 2-16 中的所示。

***清单 2-16。*使用渐变

`// Create a 3 stop gradient horizontally across the trunk var trunkGradient = context.createLinearGradient(-5, -50, 5, -50);

// The beginning of the trunk is medium brown trunkGradient.addColorStop(0, '#663300');

// The middle-left of the trunk is lighter in color trunkGradient.addColorStop(0.4, '#996600');

// The right edge of the trunk is darkest trunkGradient.addColorStop(1, '#552200');

// Apply the gradient as the fill style, and draw the trunk context.fillStyle = trunkGradient; context.fillRect(-5, -50, 10, 50);

// A second, vertical gradient creates a shadow from the //  canopy on the trunk var canopyShadow = context.createLinearGradient(0, -50, 0, 0); // The beginning of the shadow gradient is black, but with //  a 50% alpha value canopyShadow.addColorStop(0, 'rgba(0, 0, 0, 0.5)');

// Slightly further down, the gradient completely fades to //  fully transparent. The rest of the trunk gets no shadow. canopyShadow.addColorStop(0.2, 'rgba(0, 0, 0, 0.0)');

// Draw the shadow gradient on top of the trunk gradient context.fillStyle = canopyShadow; context.fillRect(-5, -50, 10, 50);`

如图 2-13 所示,应用这两个渐变在我们渲染的树上创建了一个漂亮、平滑的光源,使它看起来弯曲,并被上面树冠的轻微阴影覆盖。我们留着吧。

Image

***图 2-13。*树干渐变的树

除了我们示例中使用的线性渐变,Canvas API 还支持径向渐变选项,该选项允许您指定两个圆形表示,在这两个圆形表示中,色标应用于两个圆形之间的圆锥体。径向渐变使用与线性渐变相同的颜色停止点,但是以清单 2-17 所示的形式获取其参数。

***清单 2-17。*应用径向渐变的例子

createRadialGradient(x0, y0, r0, x1, y1, r1)

在此示例中,前三个参数表示以(x0,y0)为中心、半径为 r0 的圆,后三个参数表示以(x1,y1)为中心、半径为 r1 的第二个圆。渐变在两个圆之间的区域上绘制。

使用背景图案

图像的直接呈现有许多用途,但在某些情况下,使用图像作为背景图块是有益的,类似于 CSS 中可用的功能。我们已经看到了如何将笔触或填充样式设置为纯色。HTML5 Canvas API 还包括一个选项,用于将图像设置为路径描边或填充的可重复模式。

为了使我们的森林小径看起来更加崎岖,我们将通过用一个使用背景图像填充的曲线替换先前的描边小径曲线来展示这一能力。在这样做的时候,我们将把现在没有使用的树皮图像换成砾石图像,我们将在这里使用。清单 2-18 显示我们用对createPattern的调用替换了对drawImage的调用。

***清单 2-18。*使用背景图案

`// Replace the bark image with // a trail gravel image var gravel = new Image(); gravel.src = "gravel.jpg"; gravel.onload = function () {     drawTrails(); }

// Replace the solid stroke with a repeated // background pattern context.strokeStyle = context.createPattern(gravel, 'repeat'); con text.lineWidth = 20; context.stroke();`

如你所见,我们仍在为我们的道路呼叫stroke()。然而,这一次我们首先在上下文上设置了一个strokeStyle属性,将调用context.createPattern的结果传入。哦,同样,为了让画布执行操作,需要预先加载图像。第二个参数是重复模式,它可以是表 2-1 中显示的选项之一。

Image

图 2-14 显示了使用背景图像而不是明确绘制的图像来表示我们的踪迹的结果。

Image

***图 2-14。*背景图案重复的痕迹

缩放画布对象

什么样的森林只有一棵树?让我们马上修理那个。为了使这变得简单一点,我们将调整我们的代码样本,将树的绘制操作隔离到一个叫做drawTree的例程中,如清单 2-19 中的所示。

***清单 2-19。*绘制树对象的功能

`// Move tree drawing into its own function for reuse 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)');   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——在一个新的位置画第二棵树,并且尺寸更大,如清单 2-20 所示。

***清单 2-20。*绘制树对象

`// Draw the first tree at X=130, Y=250 context.save(); context.translate(130, 250); drawTree(context); context.restore();

// Draw the second tree at X=260, Y=500 context.save(); context.translate(260, 500);

// Scale this tree twice normal in both dimensions context.scale(2, 2); drawTree(context); context.restore();`

scale函数将 x 和 y 维度的两个因子作为其参数。每个因素都告诉 canvas 实现在那个维度上使尺寸变大(或变小)多少;X 因子为 2 将使所有后续绘制例程的宽度加倍,而 Y 因子为 0.5 将使所有后续操作的高度减半。使用这些例程,我们现在有一个简单的方法在我们的路径画布上创建第二棵树,如图 2-15 所示。

Image

***图 2-15。*大树

总是在原点执行形状和路径程序

Brian 说(这次是认真的):“这个例子说明了为什么在原点执行形状和路径例程是个好主意的原因之一;然后在完成时翻译它们,就像我们在代码中所做的那样。原因是像scalerotate这样的变换是从原点开始操作的。

如果对偏离原点绘制的形状执行rotate变换,则rotate变换将围绕原点旋转形状,而不是原地旋转。同样,如果在将形状转换到正确位置之前对其执行了缩放操作,则路径坐标的所有位置也会乘以缩放因子。根据应用的比例因子,这个新位置甚至可以完全离开画布,让您想知道为什么您的缩放操作只是“删除”了图像。"

使用画布变换

变换操作不限于缩放和平移。也可以使用context.rotate(angle)功能旋转绘图上下文,甚至直接修改底层转换,进行更高级的操作,如剪切渲染路径。如果你想旋转图像的显示,你只需要调用清单 2-21 中的一系列操作。

***清单 2-21。*旋转后的图像

`context.save();

// rotation angle is specified in radians context.rotate(1.57); context.drawImage(myImage, 0, 0, 100, 100);

context.restore();`

然而,在清单 2-22 中,我们将展示如何将任意变换应用到路径坐标上,以彻底改变现有树路径的显示,从而创建阴影效果。

***清单 2-22。*使用变换

`// Create a 3 stop gradient horizontally across the trunk // Save the current canvas state for later context.save();

// Create a slanted tree as the shadow by applying //  a shear transform, changing X values to increase //  as Y values increase // With this transform applied, all coordinates are //  multiplied by the matrix. context.transform(1, 0,-0.5, 1, 0, 0);

// Shrink the shadow down to 60% height in the Y dimension context.scale(1, 0.6);

// Set the tree fill to be black, but at only 20% alpha context.fillStyle = 'rgba(0, 0, 0, 0.2)'; context.fillRect(-5, -50, 10, 50);

// Redraw the tree with the shadow effects applied createCanopyPath(context); context.fill();

// Restore the canvas state context.restore();`

像我们在这里所做的那样直接修改上下文转换是你应该尝试的事情,只有你熟悉支撑二维绘图系统的矩阵数学。如果你检查这个变换背后的数学,你会看到我们正在把我们的画的 X 值移动一个相应的 Y 值的系数,以便剪切被用作阴影的灰色树。然后,通过应用 60%的比例因子,被剪切的树的大小被减小。

请注意,剪切的“阴影”树首先被渲染,因此实际的树以 Z 顺序(画布对象重叠的顺序)出现在它上面。此外,阴影树是使用 RGBA 的 CSS 符号绘制的,这允许我们将 alpha 值设置为正常值的 20%。这为阴影树创建了光亮的半透明外观。一旦应用到我们的缩放树,输出呈现如图图 2-16 所示。

Image

***图 2-16。*阴影变形的树

使用画布文本

当我们接近踪迹创建的结尾时,让我们通过在显示的顶部添加一个有趣的标题来展示 Canvas API 文本函数的强大功能。请务必注意,画布上的文本渲染与任何其他 path 对象的处理方式相同:文本可以描边或填充,所有渲染转换和样式都可以应用于文本,就像它们应用于任何其他形状一样。

如您所料,文本绘制例程由上下文对象上的两个函数组成:

  • fillText (text, x, y, maxwidth)
  • strokeText (text, x, y, maxwidth)

这两个函数都接受文本以及应该绘制的位置。可选地,可以提供一个maxwidth参数,通过自动缩小字体以适合给定的大小来约束文本的大小。此外,measureText函数可用于返回包含给定文本宽度的 metrics 对象,如果它是使用当前上下文设置呈现的话。

与所有浏览器文本显示的情况一样,文本的实际外观是高度可配置的,使用类似于它们的 CSS 对应物的上下文属性,如表 2-2 所示。

所有这些上下文属性都可以被设置来改变上下文,或者被访问来查询当前值。在清单 2-23 中,我们将创建一个字体为Impact的大文本消息,并用我们现有的树皮图像的背景图案填充它。为了使文本在画布顶部居中,我们将声明一个最大宽度和一个center对齐方式。

***清单 2-23。*使用画布文本

`// Draw title text on our canvas context.save();

// The font will be 60 pixel, Impact face context.font = "60px impact";

// Use a brown fill for our text context.fillStyle = '#996600'; // Text can be aligned when displayed context.textAlign = 'center';

// Draw the text in the middle of the canvas with a max //  width set to center properly context.fillText('Happy Trails!', 200, 60, 400); context.restore();`

正如你在图 2-17 中看到的结果,轨迹绘制变得更加快乐——你猜对了。

Image

***图 2-17。*背景图案填充文本

应用阴影

最后,我们将使用内置的 canvas shadow API 为新的文本显示添加模糊的阴影效果。像许多图形效果一样,阴影最好适度应用,即使 Canvas API 允许您将阴影应用到我们已经介绍过的任何操作中。

同样,阴影由一些全局context属性控制,如表 2-3 所示。

Image

如果shadowColor和至少一个其他属性被设置为非默认值,阴影效果将在任何路径、文本或图像渲染中触发。清单 2-24 展示了我们如何给新的轨迹标题文本添加阴影。

***清单 2-24。*应用阴影

`// Set some shadow on our text, black with 20% alpha context.shadowColor = 'rgba(0, 0, 0, 0.2)';

// Move the shadow to the right 15 pixels, up 10 context.shadowOffsetX = 15; context.shadowOffsetY = -10;

// Blur the shadow slightly context.shadowBlur = 2;`

通过这些简单的添加,画布渲染器将自动应用阴影,直到画布状态恢复或阴影属性重置。图 2-18 显示了新应用的阴影。

Image

***图 2-18。*带阴影文字的标题

如你所见,CSS 生成的阴影只是位置性的,与我们为树创建的变换阴影不同步。出于一致性的考虑,在给定的画布场景中,你应该只使用一种方法来绘制阴影。

处理像素数据

Canvas API 最有用的方面之一——尽管不是很明显——是开发人员能够轻松访问画布中的底层像素。这种访问是双向的:以数字数组的形式访问像素值很容易,修改这些值并将其应用到画布上也同样容易。事实上,完全可以通过像素值调用来操纵画布,而不用我们在本章中讨论的渲染调用。这是因为context API 上存在三个函数。

首先出场的是context.getImageData(sx, sy, sw, sh)。这个函数以整数集合的形式返回画布显示的当前状态。具体来说,它返回一个包含三个属性的对象:

  • width:像素数据的每行的像素数
  • height:像素数据的每一列中的像素数
  • data:一维数组,包含从画布中检索的每个像素的实际 RGBA 值。该数组包含每个像素的四个值,即红色、绿色、蓝色和 alpha 分量,每个值从 0 到 255。因此,从画布中检索到的每个像素都变成了数据数组中的四个整数值。数据数组由像素从左到右、从上到下填充(例如,穿过第一行,然后穿过第二行,等等),如图 2-19 中的所示。

Image

***图 2-19。*像素数据和代表它的内部数据结构

调用getImageData返回的数据限于四个参数定义的区域。只有包含在由源参数xywidthheight包围的矩形区域中的画布像素将被检索。因此,要将所有像素值作为数据访问,应该传入getImageData(0, 0, canvas.width, canvas.height)

因为每个像素有四个图像数据值,所以准确计算哪个索引代表给定像素的值可能有点棘手。公式如下。

对于具有给定宽度和高度的画布中坐标(x,y)处的任何像素,您可以定位组件值:

  • 红色成分:((宽度* y) + x) * 4
  • 绿色成分:((宽度* y) + x) * 4 + 1
  • 蓝色成分:((宽度* y) + x) * 4 + 2
  • Alpha 分量:((宽度* y) + x) * 4 + 3

一旦访问了包含图像数据的对象,就很容易从数学上修改数据数组中的像素值,因为它们都是从 0 到 255 的简单整数。通过使用第二个函数:context.putImageData(imagedata, dx, dy),改变一个或多个像素的红色、绿色、蓝色或 alpha 值可以很容易地更新画布显示。

putImageData允许您以与最初检索时相同的格式传入一组图像数据;这很方便,因为你可以修改画布最初给你的值,并把它们放回去。一旦调用了这个函数,画布将立即更新,以反映您作为图像数据传入的像素的新值。如果您选择使用数据数组,那么dxdy参数允许您指定将数据数组应用到现有画布的偏移量。

最后,如果您想从一组空白的画布数据开始,您可以调用context.createImageData(sw, sh)来创建一组绑定到画布对象的新图像数据。这组数据可以像以前一样以编程方式更改,即使它在检索时不代表画布的当前状态。

还有另一种从画布中获取数据的方法:canvas.toDataURL API。该函数为您提供了一种以文本格式检索画布的当前渲染数据的编程方式,但在这种情况下,该格式是浏览器可以解释为图像的数据的标准表示。

数据 URL 是包含图像数据(如 PNG)的字符串,浏览器可以像显示普通图像文件一样显示这些数据。数据 URL 的格式最好用一个例子来说明:

data:image/png;base64, WCAYAAABkY9jZxn…

这个例子显示了格式是字符串data:后跟一个 MIME 类型(比如image/png),后跟一个指示数据是否以 base64 格式编码的标志,然后是表示数据本身的文本。

不要担心格式,因为您不会自己生成它。重要的一点是,通过一个简单的调用,您可以在这些特殊的 URL 之一中获得交付给您的画布内容。当您调用canvas.toDataURL(type)时,您可以传入您希望在其中生成画布数据的图像类型,例如image/png(默认)或image/jpeg。返回给您的数据 URL 可以用作页面或 CSS 样式中图像元素的来源,如清单 2-25 中的所示。

***清单 2-25。*从画布上创作图像

`var myCanvas = document.getElementById("myCanvas");

// draw operations into the canvas... // get the canvas data as a data URL var canvasData = myCanvas.toDataURL();

// set the data as the source of a new image var img = new Image(); img.src = canvasData;`

您不必马上使用数据 URL。您甚至可以将 URL 存储在浏览器的本地存储器中,以便以后检索和操作。浏览器存储将在本书的后面讨论。

实现画布安全性

如前一节所述,使用像素操作有一个重要的注意事项。尽管大多数开发人员将像素操作用于合法的目的,但是从画布中获取和更新数据的能力很可能被用于不正当的目的。出于这个原因,指定了一个原点清洁画布的概念,这样被污染了的画布就不能从包含页面的源之外的原点获取它们的数据。

如图 2-20 中的所示,如果从[www.example.com](http://www.example.com)提供的页面包含一个canvas元素,那么页面中的代码完全有可能试图在画布中呈现来自[www.remote.com](http://www.remote.com)的图像。毕竟,在任何给定的网页中呈现来自远程站点的图像是完全可以接受的。

Image

***图 2-20。*本地和远程图像源

然而,在 Canvas API 出现之前,不可能以编程方式检索下载图像的像素值。其他网站的私人图片可以显示在页面上,但不能被阅读或复制。允许脚本从其他来源读取图像数据将有效地与整个网络共享用户的照片和其他敏感的在线图像文件。

为了防止这种情况,如果调用getImageDatatoDataURL函数,任何包含从远程源渲染的图像的画布都将抛出安全异常。只要您(或任何其他编剧)在画布被污染后不试图从该画布获取数据,将远程图像渲染到另一个来源的画布中是完全可以接受的。请注意这一限制,并练习安全渲染。

用 HTML5 Canvas 构建应用

使用 Canvas API 有许多不同的应用可能性:图形、图表、图像编辑等等。然而,画布最有趣的用途之一是修改或覆盖现有内容。一种流行的叠加类型被称为热图。虽然这个名字意味着温度测量,但在这种情况下,热量可以指任何水平的可测量活动。地图上活跃程度高的区域被标记为热色(例如,红色、黄色或白色)。活动较少的区域没有显示任何颜色变化,或者显示最少的黑色和灰色。

例如,热图可用于在城市地图上指示交通状况,或在全球地图上指示风暴活动。在 HTML5 中,通过将画布显示与底层地图源结合起来,这样的情况很容易实现。本质上,画布可用于覆盖地图,并根据适当的活动数据绘制热量水平。

让我们使用我们在 Canvas API 中学到的功能构建一个简单的热图。在这种情况下,我们的热度数据源将不是外部数据,而是鼠标在地图上的移动。在地图的一部分上移动鼠标会导致温度升高,将鼠标保持在给定位置会使温度迅速升高到最高水平。我们可以将这样的热图显示(如图 2-21 所示)叠加在一张普通的地形图上,这只是提供一个例子。

Image

***图 2-21。*热图应用

现在,您已经看到了我们的热图应用的最终结果,让我们来浏览一下代码示例。像往常一样,工作示例可以在线下载和阅读。

让我们从这个例子中声明的 HTML 元素开始。对于这个显示,HTML 只包含一个标题、一个画布和一个按钮,我们可以用它来重置热图。画布的背景显示由一个简单的通过 CSS 应用到画布的mapbg.jpg组成,如清单 2-26 中的所示。

***清单 2-26。*热图画布元素

<style type="text/css">   #heatmap {       background-image: url("mapbg.jpg");   } `

Heatmap

Reset`

我们还声明了一些将在后面的例子中使用的初始变量。

  var points = {};   var SCALE = 3;   var x = -1;   var y = -1;

接下来,我们将为画布的全局绘制操作设置一个高透明度值,并设置合成模式以使新绘制的像素变亮,而不是替换它们。

然后,如清单 2-27 中的所示,我们将设置一个处理程序来改变显示—addToPoint—每当鼠标移动或十分之一秒过去时。

***清单 2-27。*load demo 功能

`function loadDemo() {   document.getElementById("resetButton").onclick = reset;

  canvas = document.getElementById("heatmap");   context = canvas.getContext('2d');   context.globalAlpha = 0.2;   context.globalCompositeOperation = "lighter"

function sample() {   if (x != -1) {     addToPoint(x,y)   }   setTimeout(sample, 100); }

canvas.onmousemove = function(e) {   x = e.clientX - e.target.offsetLeft;   y = e.clientY - e.target.offsetTop;   addToPoint(x,y) }

  sample(); }`

如果用户点击重置,则整个画布区域被清空,并使用画布的clearRect函数重置为初始状态,如清单 2-28 所示。

***清单 2-28。*复位功能

function reset() {   points = {};   context.clearRect(0,0,300,300);   x = -1;   y = -1; }

接下来,我们创建一个颜色查找表,在画布上绘制热量时使用。清单 2-29 显示了颜色在亮度上如何从最小到最大变化,它们将被用来代表显示器上不同的热量水平。intensity的值越大,返回的颜色越亮。

***清单 2-29。*getColor 函数

function getColor(intensity) {   var colors = ["#072933", "#2E4045", "#8C593B", "#B2814E", "#FAC268", "#FAD237"];   return colors[Math.floor(intensity/2)]; }

每当鼠标移动或悬停在画布的某个区域上时,就会绘制一个点。鼠标在邻近区域停留的时间越长,点的大小(和亮度)就越大。如清单 2-30 所示,我们使用context.arc函数绘制一个给定半径的圆,对于更大的半径值,我们通过将半径传递给我们的getColor函数来绘制一个更亮、更热的颜色。

***清单 2-30。*拉点功能

`function drawPoint(x, y, radius) {   context.fillStyle = getColor(radius);   radius = Math.sqrt(radius)*6;

  context.beginPath();   context.arc(x, y, radius, 0, Math.PI*2, true)

  context.closePath();   context.fill(); }`

addToPoint函数中——您会记得每次鼠标移动或悬停在某个点上时都会访问该函数——会增加并存储画布上该特定点的热度值。清单 2-31 显示最大点数值为 10。一旦找到给定像素的当前热度值,适当的像素及其相应的热度/半径值被传递给drawPoint

***清单 2-31。*addToPoint 函数

`function addToPoint(x, y) {   x = Math.floor(x/SCALE);   y = Math.floor(y/SCALE);

  if (!points[[x,y]]) {     points[[x,y]] = 1;   } else if (points[[x,y]]==10) {     return   } else {     points[[x,y]]++;   }   drawPoint(xSCALE,ySCALE, points[[x,y]]); }`

最后,初始的loadDemo函数被注册,以便在窗口完成加载时调用。

window.addEventListener("load", loadDemo, true);

总之,这一百多行代码说明了在不使用任何插件或外部渲染技术的情况下,您可以在短时间内使用 Canvas API 做多少事情。有了无限多的可用数据源,很容易看出如何简单有效地可视化它们。

实用额外功能:整页玻璃窗格

在示例应用中,您看到了如何在图形上应用画布。您也可以在整个浏览器窗口或部分窗口的顶部应用画布,这种技术通常被称为玻璃面板。一旦你在网页上放置了玻璃面板画布,你就可以用它做各种又酷又方便的事情。

例如,您可以使用一个例程来检索页面上所有 DOM 元素的绝对位置,并创建一个分步帮助函数,该函数可以指导 web 应用的用户完成启动和使用该应用所必须执行的步骤。

或者,您可以使用 glass pane canvas 在某人的 web 页面上使用鼠标事件来绘制输入。如果你试图在这种情况下使用画布,请记住以下几点:

  • 您需要将画布定位设置为absolute,并给它一个特定的位置、宽度和高度。如果没有明确的宽度和高度设置,画布将保持零像素大小。
  • 不要忘记在画布上设置一个高的 Z-index,这样它就可以浮动在所有可见内容之上。在所有现有内容下呈现的画布没有多少机会发光。
  • 您的玻璃窗格画布可能会阻止访问以下内容中的事件,因此请节约使用它,并在不必要时将其移除。
实用附加:为你的画布动画计时

在本章的前面,我们提到了在画布上制作元素动画是一种常见的做法。这可以用于游戏、过渡效果,或者简单地替换现有网页中的动画 gif。但是 JavaScript 缺少的一个方面是一种可靠的方式来安排动画更新。

今天,大多数开发人员使用经典的setTimeoutsetInterval调用来安排对网页或应用的更改。这两个调用都允许您在一定数量的毫秒之后安排一个回调,然后允许您在回调期间对页面进行更改。然而,使用这种方法存在一些重大问题:

  • 作为开发人员,您需要猜测未来合适的毫秒数来安排下一次更新。随着现代网络在比以往更多种类的设备上运行,很难知道高性能桌面设备和移动电话的建议帧速率。即使您猜测每秒调度多少帧,您也可能会与其他页面或机器负载竞争。
  • 用户使用多个窗口或标签进行浏览比以往任何时候都更加普遍,甚至在移动设备上也是如此。如果您使用setTimeoutsetInterval来安排您的页面更新,即使页面在后台,这些更新也会继续发生。在脚本不可见的情况下运行脚本是让用户相信你的 web 应用正在耗尽他们的手机电池的好方法!

作为替代,许多浏览器现在在window对象上提供了一个requestAnimationFrame功能。该函数将回调作为其参数,只要浏览器认为适合更新动画,就会调用回调。

让我们添加另一个例子(清单 2-32 )我们的赛道场景,这个用一个粗糙的动画暴雨来表示我们即将到来的比赛的取消。这段代码建立在前面的例子之上,多余的代码不在这里列出。

***清单 2-32。*基本动画帧要求

`// create an image for our rain texture var rain = new Image(); rain.src = "rain.png"; rain.onload = function () {   // Start off the animation with a single frame request   // once the rain is loaded   window.requestAnimFrame(loopAnimation, canvas); }

// Previous code omitted…

// this function allows us to cover all browsers // by aliasing the different browser-specific // versions of the function to a single function window.requestAnimFrame = (function(){   return  window.requestAnimationFrame       ||           window.webkitRequestAnimationFrame ||           window.mozRequestAnimationFrame    ||           window.oRequestAnimationFrame      ||           window.msRequestAnimationFrame     ||           // fall back to the old setTimeout technique if nothing           // else is available           function(/* function / callback, / DOMElement */ element){             window.setTimeout(callback, 1000 / 60);           }; })();

// This function is where we update the content of our canvas function drawAFrame() {   var context = canvas.getContext('2d');

  // do some drawing on the canvas, using the elapsedTime   // as a guide for changes.   context.save();

  // draw the existing trails picture first   drawTrails(); // Darken the canvas for an eerie sky.   // By only darkening most of the time, we create lightning flashes   if (Math.random() > .01) {     context.globalAlpha = 0.65;     context.fillStyle = '#000000';     context.fillRect(0, 0, 400, 600);     context.globalAlpha = 1.0;   }

  // then draw a rain image, adjusted by the current time   var now = Date.now();   context.fillStyle = context.createPattern(rain, 'repeat');

  // We'll draw two translated rain images at different rates to   // show thick rain and snow   // Our rectangle will be bigger than the display size, and   // repositioned based on the time   context.save();   context.translate(-256 + (0.1 * now) % 256, -256 + (0.5 * now) % 256);   context.fillRect(0, 0, 400 + 256, 600 + 256);   context.restore();

  // The second rectangle translates at a different rate for   // thicker rain appearance   context.save();   context.translate(-256 + (0.08 * now) % 256, -256 + (0.2 * now) % 256);   context.fillRect(0, 0, 400 + 256, 600 + 256);   context.restore();

  // draw some explanatory text   context.font = '32px san-serif';   context.textAlign = 'center';   context.fillStyle = '#990000';   context.fillText('Event canceled due to weather!', 200, 550, 400);   context.restore(); }

// This function will be called whenever the browser is ready // for our application to render another frame. function loopAnimation(currentTime) {   // Draw a single frame of animation on our canvas   drawAFrame();

  // After this frame is drawn, let the browser schedule   // the next one   window.requestAnimFrame(loopAnimation, canvas); }`

一旦我们更新了我们的绘图,我们可以在我们的轨迹上看到动画雨(见图 2-22 )。

Image

***图 2-22。*带雨动画的画布静止镜头

由浏览器决定多久调用一次动画帧回调。后台页面的调用频率将会降低,浏览器可能会将呈现剪辑到提供给requestAnimationFrame调用(在我们的示例中为“canvas”)的元素,以优化绘图资源。你不能保证一个帧速率,但是你不用为不同的环境安排时间了!

这种技术并不局限于 Canvas API。您可以使用requestAnimationFrame在页面内容或 CSS 的任何地方进行修改。还有其他方法可以在网页上产生移动效果——我想到了 CSS 动画——但是如果你正在处理基于脚本的变化,那么requestAnimationFrame函数是最好的方法。

总结

正如您所看到的,Canvas API 提供了一种非常强大的方法来修改 web 应用的外观,而无需求助于奇怪的文档攻击。图像、渐变和复杂路径可以组合在一起,以创建您想要呈现的几乎任何类型的显示。请记住,您通常需要在原点绘制,在尝试绘制之前加载您想要显示的任何图像,并注意不要让外来图像源污染您的画布。然而,如果你学会利用画布的力量,你就可以创建以前在网页中不可能实现的应用。

三、可缩放矢量图形

在这一章中,我们将探索 HTML5 中的另一个图形特性可以做什么:可缩放矢量图形。可缩放矢量图形(SVG)是一种表达二维图形的语言。

SVG 概述

在这一节中,我们将了解 HTML5 浏览器中的标准矢量图形支持,但首先,让我们回顾几个图形概念:光栅和矢量图形。

在光栅图形中,图像由二维像素网格表示。HTML5 Canvas 2d API 是光栅图形 API 的一个例子。使用画布 API 进行绘制会更新画布的像素。PNG 和 JPEG 是光栅图像格式的示例。PNG 和 JPEG 图像中的数据也表示像素。

矢量图形则完全不同。矢量图形用几何的数学描述来表示图像。矢量图像包含从高级几何对象(如线条和形状)绘制图像所需的所有信息。从名字就可以看出,SVG 是矢量图形的一个例子。像 HTML 一样,SVG 也是一种具有 API 的文件格式。SVG 与 DOM APIs 结合形成了矢量图形 API。可以在 SVG 中嵌入光栅图形,比如 PNG 图像,但是 SVG 主要是一种矢量格式。

历史

SVG 已经存在几年了。SVG 1.0 于 2001 年作为 W3C 推荐标准发布。SVG 最初是通过插件在浏览器中使用的。不久之后,浏览器增加了对 SVG 图像的本地支持。

HTML 中的内联 SVG 历史较短。SVG 的一个定义性特征是它基于 XML。当然,HTML 有不同的语法,你不能简单地将 XML 语法嵌入到 HTML 文档中。相反,它对 SVG 有特殊的规则。在 HTML5 之前,可以将 SVG 作为元素嵌入 HTML 页面或链接到自包含页面。svg 文档。HTML5 引入了内联 SVG,其中 SVG 元素本身可以出现在 HTML 标记中。当然,在 HTML 中,语法规则比 XML 更宽松。可以有未加引号的属性、混合大写等等。在适当的时候,您仍然需要使用自结束标记。例如,您可以在 HTML 文档中嵌入一个圆圈,只需一点标记:

<svg height=100 width=100><circle cx=50 cy=50 r=50 /></svg>

了解 SVG

图 3-1 显示了一个带有*快乐小径的 HTML5 文档!*我们在第二章中用画布 API 绘制的图像。如果你看了这一章的标题,你大概可以猜到这个版本是用 SVG 绘制的。SVG 允许您进行许多与 canvas API 相同的绘图操作。很多时候,结果在视觉上是一样的。然而,有一些重要的看不见的差异。首先,文本是可选的。你不能用画布得到它!当您在画布元素上绘制文本时,字符被冻结为像素。它们成为图像的一部分,并且不能改变,除非您重画画布的一个区域。因此,搜索引擎看不到绘制在画布上的文本。另一方面,SVG 是可搜索的。例如,Google 将网页上 SVG 内容中的文本编入索引。

Image

图 3-1 。SVG 版的快乐步道!

SVG 与 HTML 密切相关。如果您愿意,可以用标记定义 SVG 文档的内容。HTML 是一种用于构建页面的声明性语言。SVG 是一种创建可视化结构的辅助语言。您可以使用 DOM APIs 与 SVG 和 HTML 进行交互。SVG 文档是动态的元素树,您可以像 HTML 一样编写脚本和设计样式。您可以将事件处理程序附加到 SVG 元素。例如,您可以使用 click 事件处理程序来制作 SVG 按钮或有形状的可点击区域。这对于构建使用鼠标输入的交互式应用至关重要。

此外,您可以在浏览器的开发工具中查看和编辑 SVG 的结构。正如你在图 3-2 中看到的,内嵌 SVG 直接嵌入到 HTML DOM 中。它有一个你可以在运行时观察和改变的结构。您可以深入研究 SVG 并查看其来源,不像图像只是一个像素网格。

Image

图 3-2 。查看 ChromeWeb Inspector 中的 SVG 元素

在图 3-2 中,高亮显示的文本元素包含以下代码:

< text y="60" x="200" font-family="impact" font-size="60px"   fill="#996600" text-anchor="middle">     Happy Trails </text>

在开发环境中,您可以添加、删除和编辑 SVG 元素。这些更改会在活动页面中立即生效。这对于调试和实验来说极其方便。

保留模式图形

Frank 说:“在图形 API 设计中有两个学派。canvas 等即时模式图形提供了一个绘图界面。API 调用导致一个绘制动作立即发生*,因此得名。与即时模式图形相对应的样式称为保留模式。在保留模式图形中,场景中有一个随时间保留的视觉对象模型。有一个 API 操纵场景图形,图形引擎在场景发生变化时重绘场景。SVG 是保留模式图形,其场景图是文档。操纵 SVG 的 API 是 W3C DOM API。*

*有一些 JavaScript 库在 canvas 之上构建保留模式 API。有些还提供精灵、输入处理和层。您可以选择使用这样的库,但是请记住这些特性以及更多特性都是 SVG 固有的!"

可扩展图形

当您放大、旋转或以其他方式变换 SVG 内容时,组成图像的所有线条都会清晰地重新绘制。SVG 在不损失质量的情况下扩展。构成 SVG 文档的矢量信息在呈现时会保留下来。与像素图形形成对比。如果您像放大画布或图像一样放大像素图形,它会变得模糊。这是因为图像是由只能以更高分辨率重新采样的像素组成的。潜在信息——制作图像的路径和形状——在绘制后会丢失(见图 3-3 )。

Image

***图 3-3。*放大 500%的 SVG 和画布特写

用 SVG 创建 2D 图形

让我们再来看看*快乐小径!*图片来自图 3-1 。这个 SVG 图形的每个可见部分都有一些相应的标记。完整的 SVG 语言非常广泛,它的所有细节和细微差别都不适合在本章讲述。然而,为了对 SVG 词汇的广度有所了解,这里有一些用于绘制愉快轨迹的特性:

  • 形状
  • 小路
  • 转换
  • 图案和渐变
  • 可重用内容
  • 文本

在我们将它们组合成一个完整的场景之前,让我们依次看看每一个。但是,在我们这样做之前,我们需要了解如何将 SVG 添加到页面中。

给页面添加 SVG

将内联 SVG 添加到 HTML 页面就像添加任何其他元素一样简单。

在 Web 上使用 SVG 有几种方式,包括作为元素。我们将在 HTML 中使用内联 SVG,因为它将集成到 HTML 文档中。这将让我们以后编写一个无缝结合 HTML、JavaScript 和 SVG 的交互式应用(参见清单 3-1 )。

***清单 3-1。*包含红色矩形的 SVG

<!doctype html> <svg width="200" height="200"> </svg>

就这样!不需要 XML 名称空间。现在,在开始和结束 svg 标记之间,我们可以添加形状和其他可视对象。如果您想将 SVG 内容拆分成一个单独的。svg 文件,您需要像这样更改它:

<svg width="400" height="600"     xmlns:xlink="http://www.w3.org/1999/xlink"> </svg>

现在它是一个有效的 XML 文档,具有适当的名称空间属性。您将能够使用各种图像查看器和编辑器打开该文档。你也可以从 HTML 中引用一个 SVG 文件,作为带有代码的静态图像,比如<img src="example.svg">。这种方法的一个缺点是,SVG 文档没有像内联 SVG 内容那样集成到 DOM 中。您将无法编写与 SVG 元素交互的脚本。

简单的形状

SVG 语言包括基本的形状元素,如矩形、圆形和椭圆形。形状元素的大小和位置由属性定义。对于矩形,这些是widthheight。对于圆,半径有一个r属性。所有这些都使用距离的 CSS 语法,所以它们可以是像素、点、单位等等。清单 3-2 是一个非常短的包含内嵌 SVG 的 HTML 文档。它只是一个带有红色轮廓的灰色矩形,大小为 100 像素乘 80 像素,显示在图 3-4 中。

***清单 3-2。*包含红色矩形的 SVG

<!doctype html> <svg width="200" height="200">   <rect x="10" y="20" width="100" height="80" stroke="red" fill="#ccc" /> </svg> Image

***图 3-4。*HTML 文档中的 SVG 矩形

SVG 按照对象在文档中出现的顺序绘制对象。如果我们在矩形后添加一个圆,它会出现在第一个形状的顶部。我们将给这个圆一个 8 像素宽的蓝色笔触,没有填充样式(见清单 3-3 ,所以它很突出,如图图 3-5 所示。

***清单 3-3。*一个长方形和一个圆形

<!doctype html> <svg width="200" height="200">   <rect x="10" y="20" width="100" height="80" stroke="red" fill="#ccc" />   <circle cx="120" cy="80" r="40" stroke="#00f" fill="none" stroke-width="8" /> </svg> Image

***图 3-5。*一个长方形和一个圆形

注意,x 和 y 属性定义了矩形左上角的位置。另一方面,圆具有 cx 和 cy 属性,它们是圆心的 x 和 y 值。SVG 使用与 canvas API 相同的坐标系。svg 元素的左上角是位置 0,0。画布坐标系详见第二章。

转换 SVG 元素

SVG 中的组织元素旨在组合多个元素,以便它们可以作为单元进行转换或链接。<g>元素代表“组”组可用于组合多个相关元素。作为一个组,它们可以通过一个公共 ID 来引用。一个组也可以作为一个单元进行转换。如果将变换属性添加到组中,该组的所有内容都会被变换。变换属性可以包括旋转(见清单 3-4 和图 3-6 )、平移、缩放和倾斜的命令。您还可以指定一个转换矩阵,就像您使用 canvas API 一样。

***清单 3-4。*旋转组中的矩形和圆形

<svg width="200" height="200">     <g transform="translate(60,0) rotate(30) scale(0.75)" id="ShapeGroup">       <rect x="10" y="20" width="100" height="80" stroke="red" fill="#ccc" />       <circle cx="120" cy="80" r="40" stroke="#00f" fill="none" stroke-width="8" />     </g> </svg> Image

***图 3-6。*一个旋转组

重复使用内容

SVG 有一个<defs>元素,用于定义将来使用的内容。它还有一个名为<use>的元素,可以链接到您的定义。这使您可以多次重用相同的内容,并消除冗余。图 3-7 显示了在不同的变换位置和比例下使用了三次的组。这个组的 id 是ShapeGroup,它包含一个矩形和一个圆形。实际的矩形和圆形只在<defs>元素中定义了一次。定义的组本身是不可见的。相反,有三个<use>元素链接到形状组,所以三个矩形和三个圆形呈现在页面上(见清单 3-5 )。

***清单 3-5。*使用一组三次

`                              

         ` Image

***图 3-7。*三个使用元素引用同一个组

图案和渐变

图 3-7 中的圆形和矩形具有简单的填充和描边样式。物体可以被绘制成更复杂的样式,包括渐变和图案(见清单 3-6 )。渐变可以是线性的,也可以是放射状的。模式可以由像素图形甚至其他 SVG 元素组成。图 3-8 显示了一个带有线性颜色渐变的矩形和一个带有砾石纹理的圆形。纹理来自链接到 SVG 图像元素的 JPEG 图像。

***清单 3-6。*给矩形和圆形添加纹理

`        <pattern id="GravelPattern" patternUnits="userSpaceOnUse"           x="0" y="0" width="100" height="67" viewBox="0 0 100 67">            

                              

  <rect x="10" y="20" width="100" height="80"       stroke="red"       fill="url(#RedBlackGradient)" />   <circle cx="120" cy="80" r="40" stroke="#00f"       stroke-width="8"       fill="url(#GravelPattern)" /> ` Image

***图 3-8。*渐变填充的矩形和图案填充的圆形

SVG 路径

SVG 有自由形式的路径和简单的形状。路径元素有d属性。“d”代表数据。在d属性的值中,您可以指定一系列路径绘制命令。每个命令都可能带有坐标参数。有些命令是 M 代表 moveto,L 代表 lineto,Q 代表二次曲线,Z 代表闭合路径。如果这些让你想起了画布绘制 API,那就不是巧合了。清单 3-7 使用一系列 lineto 命令,使用一个路径元素绘制一个封闭的树冠形状。

**清单 3-7。**定义树冠的 SVG 路径

    <path d="M-25, -50             L-10, -80             L-20, -80             L-5, -110             L-15, -110             L0, -140             L15, -110             L5, -110             L20, -80             L10, -80             L25, -50             Z" id="Canopy"></path>

你可以用 Z 命令关闭一个路径并给它一个填充属性来填充它,就像我们之前画的矩形一样。图 3-9 展示了如何结合描边封闭路径和填充封闭路径来绘制一棵树。

Image

***图 3-9。*描边路径、填充路径和两种路径

同样,我们可以用两条二次曲线创建一条开放路径,形成一条小径。我们甚至可以赋予它质感。注意清单 3-8 中的stroke-linejoin属性。这在两条二次曲线之间形成了圆形连接。图 3-10 显示了一条被绘制成开放路径的山路。

***清单 3-8。*定义扭曲轨迹的 SVG 路径

  <g transform="translate(-10, 350)" stroke-width="20" stroke="url(#GravelPattern)" stroke-linejoin="round">         <path d="M0,0 Q170,-50 260, -190 Q310, -250 410,-250" fill="none"></path>   </g> Image

***图 3-10。*包含两条二次曲线的开放路径

使用 SVG 文本

SVG 也支持文本。SVG 格式的文本可在浏览器中选择(参见图 3-11 )。如果用户愿意,浏览器和搜索引擎也可以允许用户在 SVG 文本元素中搜索文本。这在可用性和可访问性方面有很大的好处。

SVG 文本的属性类似于 HTML 的 CSS 样式规则。清单 3-9 显示了一个具有font-weightfont-family属性的文本元素。和 CSS 一样,font-family 可以是一个单独的字体名称,如“sans-serif ”,也可以是一系列备用名称,如“Droid Sans,sans-serif ”,按照您喜欢的顺序排列。

清单 3-9。 SVG 文本

<svg width="600" height="200">   <text     x="10" y="80"     font-family="Droid Sans"     stroke="#00f"     fill="#0ff"     font-size="40px"     font-weight="bold">     Select this text!   </text> </svg> Image

***图 3-11。*选择 SVG 文本

把场景组合在一起

我们可以把前面所有的元素结合起来,形成一幅快乐小径的图像。文本自然是一个文本元素。树干由两个长方形组成。树冠是两条路。树木投射阴影,使用相同的几何图形给定一个灰色填充颜色和一个向下向右倾斜的变换。穿过图像的弯曲路径是另一个具有纹理图像图案的路径。还有一点 CSS 给场景一个轮廓。

清单 3-10 提供了trails-static.html的完整代码。

***清单 3-10。*完整代码为trails-static.html

`Happy Trails in SVG

  svg {         border: 1px solid black;   }

                                                                          

                 <path d="M-25, -50                 L-10, -80                 L-20, -80                 L-5, -110                 L-15, -110                 L0, -140                 L15, -110                 L5, -110                 L20, -80                 L10, -80                 L25, -50                 Z"         id="Canopy"         />                                                                        <use xlink:href="#Canopy" fill="none" stroke="#663300"         stroke-linejoin="round" stroke-width="4px" />                  

                                      

  <g transform="translate(-10, 350)"         stroke-width="20"         stroke="url(#GravelPattern)"         stroke-linejoin="round">         <path d="M0,0 Q170,-50 260, -190 Q310, -250 410,-250"         fill="none" />   

  <text y=60 x=200         font-family="impact"         font-size="60px"         fill="#996600"         text-anchor="middle" >         Happy Trails!   

  <use xlink:href="#TreeShadow"         transform="translate(130, 250) scale(1, .6) skewX(-18)"         opacity="0.4" />   

  <use xlink:href="#TreeShadow"         transform="translate(260, 500) scale(2, 1.2) skewX(-18)"         opacity="0.4" />

  

`

使用 SVG 构建交互式应用

在这一节中,我们将扩展静态示例。我们将添加 HTML 和 JavaScript 来使文档具有交互性。我们将在一个应用中利用 SVG 的功能,这个应用需要更多的代码来实现 canvas API。

添加树木

在这个交互式应用中,我们只需要一个按钮元素。按钮的 click 处理程序在 600x400 像素的 SVG 区域内的随机位置添加一个新树。新树也随机缩放 50%到 150%之间的量。每个新树实际上是一个引用包含多条路径的“树”组的<use>元素。代码使用命名空间document.createElementNS()调用来创建一个<use>元素。它用xlink:href属性将它链接到先前定义的树组。然后它把新元素添加到 SVG 元素树中(见清单 3-11 )。

***清单 3-11。*添加树功能

`  document.getElementById("AddTreeButton").onclick = function() {     var x = Math.floor(Math.random() * 400);     var y = Math.floor(Math.random() * 600);     var scale = Math.random() + .5;     var translate = "translate(" +x+ "," +y+ ") ";

    var tree = document.createElementNS("www.w3.org/2000/svg", "use");     tree.setAttributeNS("www.w3.org/1999/xlink", "xlink:href", "#Tree");     tree.setAttribute("transform", translate + "scale(" + scale + ")");     document.querySelector("svg").appendChild(tree);     updateTrees();   }`

元素按照它们在 DOM 中出现的顺序呈现。这个函数总是将树作为新的子节点添加到 SVG 元素的子节点列表的末尾。这意味着新的树会出现在老的树的上面。

这个函数以调用updateTrees()结束,我们接下来会看到。

增加更新树功能

updateTrees函数在文档最初加载时以及添加或删除树时运行。它负责更新显示森林中树木数量的文本。它还为每棵树附加了一个点击处理函数(见清单 3-12 )。

清单 3-12 更新树函数

  function updateTrees() {     var list = document.querySelectorAll("use");     var treeCount = 0;     for (var i=0; i<list.length; i++) {       if(list[i].getAttribute("xlink:href")=="#Tree") {         treeCount++;         list[i].onclick = removeTree;       }     }     var counter = document.getElementById("TreeCounter");     counter.textContent = treeCount + " trees in the forest";   }

关于这段代码,需要注意的一件重要事情是,它在 JavaScript 中没有保留关于树计数的状态。每次发生更新时,这段代码都会选择并过滤 live 文档中的所有树,以获得最新的计数。

增加 removeTree 功能

现在,让我们添加当树被点击时移除它们的函数(见清单 3-13 )。

***清单 3-13。*移除树功能

  function removeTree(e) {     var elt = e.target;     if (elt.correspondingUseElement) {       elt = elt.correspondingUseElement;     }     elt.parentNode.removeChild(elt);     updateTrees();   }

我们在这里做的第一件事是检查点击事件的目标。由于 DOM 实现的不同,事件目标可以是树组,也可以是链接到该组的 use 元素。不管怎样,这个函数只是从 DOM 中删除那个元素,并调用updateTrees()函数。

如果您删除了位于另一棵树顶部的树,您不必做任何事情来重新绘制较低的内容。这是使用保留模式 API 进行开发的好处之一。您只需操作元素树(没有双关的意思),浏览器就会负责绘制必要的像素。同样,当文本更新以显示最新的树数时,它会停留在树的下方。如果希望文本出现在树的上方,就必须在文本元素之前将树附加到文档中。

添加 CSS 样式

为了使交互更容易被发现,我们将添加一些 CSS 来改变鼠标光标下的树的外观:

g[id=Tree]:hover  {         opacity: 0.9;         cursor: crosshair;   }

每当您将鼠标悬停在 id 属性等于“Tree”的元素上时,该元素将变为部分透明,并且鼠标光标将变为十字准线。

CSS 中也定义了围绕整个 SVG 元素的一个像素的黑色边框。

  svg {     border: 1px solid black;   }

就这样!现在你有了一个在 HTML5 中使用内嵌 SVG 的交互式应用(见图 3-12 )。

Image

***图 3-12。*最后的文件加上了几棵树

最终代码

为了完整起见,清单 3-14 提供了完整的trails-dynamic.html文件。它包含了静态版本的所有 SVG 以及使其具有交互性的脚本。

***清单 3-14。*整个trails-dynamic.html代码

`

Happy Trails in SVG   svg {     border: 1px solid black;   }   g[id=Tree]:hover  {     opacity: 0.9;     cursor: crosshair;   }
  Add Tree` `

                                                        

         <path d="M-25, -50             L-10, -80             L-20, -80             L-5, -110             L-15, -110             L0, -140             L15, -110             L5, -110             L20, -80             L10, -80             L25, -50             Z"         id="Canopy"       />                                                      <use xlink:href="#Canopy" fill="none" stroke="#663300"          stroke-linejoin="round" stroke-width="4px" />               

  <g transform="translate(-10, 350)"       stroke-width="20"       stroke="url(#GravelPattern)"       stroke-linejoin="round">         <path d="M0,0 Q170,-50 260, -190 Q310, -250 410,-250"           fill="none" />    <text y=60 x=200     font-family="impact"     font-size="60px"     fill="#996600"     text-anchor="middle" >     Happy Trails!      <text y=90 x=200     font-family="impact"     font-size="20px"     fill="#996600"     text-anchor="middle" id="TreeCounter">   

  <text y=420 x=20     font-family="impact"     font-size="20px"     fill="#996600"     text-anchor="left">     You can remove a     tree by clicking on it.   

     

`

【SVG 工具】??㎡

Frank 说:“由于 SVG 作为矢量图形的标准格式有着悠久的历史,因此有许多有用的工具可以用来处理 SVG 图像。甚至还有一个运行在浏览器中的开源编辑器 SVG-edit。你可以把它嵌入到你自己的应用中!在桌面上,Adobe Illustrator 和 Inkscape 是两个强大的矢量图形应用,可以导入和导出 SVG。我发现 Inkscape 对于创建新图形非常有用(见图 3-13 )。

SVG 工具倾向于独立工作。svg 文件,而不是嵌入在 HTML 中的 SVG,所以您可能需要在这两种格式之间进行转换。"

Image

***图 3-13。*在 Inkscape 中修改文本元素的笔画

总结

在这一章中,你已经看到了 HTML5 中的 SVG 是如何提供一种强大的方法来创建具有交互式二维图形的应用的。

首先,我们看一个使用嵌入在 HTML5 文档中的 SVG 绘制的场景。我们检查了构成绘图的元素和属性。我们看到了如何定义和重用内容定义、分组和转换元素,以及使用形状、路径和文本进行绘图。

最后,我们将 JavaScript 添加到一个 SVG 文档中,以制作一个交互式应用。我们使用 CSS、DOM 操作和事件来利用 SVG 作为动态文档的特性。

现在我们已经看到了 SVG 如何将矢量图形引入 HTML5,我们将把注意力转向为应用带来更复杂媒体的视听元素。*