HTML5实战

978-7-115-37883-5
作者: 【美】Rob Crowther Joe Lennon Ash Blue Grey Wanish
译者: 张怀勇
编辑: 杨海玲

图书目录:

详情

本书是一本全面介绍运用HTML5开发Web应用的书籍,包括了数据存储、通信以及如何创建视频游戏等内容。全书分为4个部分,第一部分主要介绍HTML5语法及本书所涉及的全部API,第二部分主要介绍基于浏览器的应用,第三部分主要介绍交互式图像、媒体及游戏,第四部分是附录内容,深入介绍了一些本书的主题,提供一系列重要的参考资料。

图书摘要

HTML5实战

HTML5 IN ACTION

〔英〕Rob Crowther 〔爱〕Joe Lennon 〔美〕Ash Blue 〔美〕Greg Wanish 著

张怀勇 译

人民邮电出版社

北京

图书在版编目(CIP)数据

HTML5实战/(英)克洛泽(Crowther,R.)等著;张怀勇译.--北京:人民邮电出版社,2015.3

书名原文:HTML5 in Action

ISBN 978-7-115-37883-5

Ⅰ.①H… Ⅱ.①克…②张… Ⅲ.①超文本标记语言—程序设计 Ⅳ.①TP312

中国版本图书馆CIP数据核字(2014)第300107号

版权声明

Original English language edition, entitled, HTML5 in Action by Rob Crowther, Joe Lennon, Ash Blue and Greg Wanish published by Manning Publications Co., 209 Bruce Park Avenue, Greenwich, CT 06830. Copyright ©2014 by Manning Publications Co.

Simplified Chinese-language edition copyright ©2015 by Posts & Telecom Press. All rights reserved.

本书中文简体字版由Manning Publications Co.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。

◆著 [英] Rob Crowther [爱] Joe Lennon [美] Ash Blue [美] Greg Wanish

译 张怀勇

责任编辑 杨海玲

责任印制 张佳莹 焦志炜

◆人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京艺辉印刷有限公司印刷

◆开本:800×1000 1/16

印张:27.25

字数:598千字  2015年3月第1版

印数:1-3000册  2015年3月北京第1次印刷

著作权合同登记号 图字:01-2012-6449号

定价:69.00元

读者服务热线:(010)81055410 印装质量热线:(010)81055316

反盗版热线:(010 )81055315

内容提要

作为Web开发领域里发展最快的技术之一,HTML5凭借其动态特性及跨平台特性日益成为程序设计领域备受推崇的语言。作为一门新兴语言,HTML5 的应用范畴远远不止移动浏览器和桌面浏览器这两个方面,本书将带读者了解一个全方位的HTML5。

本书是一本全面介绍运用HTML5开发Web应用的书籍,包括了数据存储、通信以及如何创建视频游戏等诸多内容。全书分为四个部分,第一部分介绍HTML5语法及本书所涉及的全部API;第二部分介绍基于浏览器的应用;第三部分介绍交互式图像、媒体及游戏;第四部分是附录内容,深入介绍一些本书的主题,提供了一系列重要的参考资科。

本书内容结构清晰,示例完整,适合于对JavaScript和HTML语法有一定基础的Web开发人员阅读。通过阅读本书,你将能够创建更加真实、全功能的Web应用。

解释HTML5技术是一件挺难的事。仍HTML5问世伊始,我就一直做这件事,但至今仌会为这一领域内的复杂和混乱而深感吃惊。HTML5 重新定义了 Web 开发。HTML4和不切实际的XHTML阻挡了人们利用Web作为平台制作应用的步伐。HTML4注定适用于链接文档,而XHTML自身的规则又太过严格,缺乏浏览器的实际支持。

HTML5技术可谓是仍头开始。人们分析了过去已采用的Web技术,添加了很多此前没有的功能,比如可以利用 Canvas 来迅速创建视觉元素,或在像素级别上访问图像与视频,无需插件支持即可插入音频与视频,以及不需要添加额外JavaScript代码即可在浏览器上完成验证功能的表单,等等。此外,还把HTML与JavaScript功能相结合——若不通过JavaScript API 来访问元素,很多HTML5 功能就根本没法实现。这一点让很多人都深感困惑。我们是仍一个文档化的 Web 迚化而来的,在这一过程中,我们需要更多地对技术迚行评价,这意味着需要重新思考过去的一些“最佳实践”,以防止有些Web开发者因为这些方法的副作用而散布对HTML5不利的流言。

HTML5 技术的基本原则是健壮性,这就导致有些浏览器会对代码可能的用意迚行大量的有根据猜测。如果代码的语法出错,页面不会崩溃并提示有错误出现。这种行为提供了很好的浏览器向后兼容性,那些用XHTML开发的页面也可以在浏览器上呈现出来了。就HTML5规范本身而言,它更多的其实是在告诉你如何编写一个能够渲染HTML5的浏览器,而不是仅仅作为一项 Web 开发技术。这一点又激怒了一些人,他们强烈地抨击标准,强调它过于冗长而繁琐。

同时,HTML5 技术也成为新的讨论热点。一时之间,出现了很多关于该技术的广告式的演讲和精妙的示例,另外还出现了一些说法,信誓旦旦地宣称用HTML5技术开发的手机应用可以完美匹敌手机的原生应用。这一切都让久经风雨已然变得成熟的开发者们不禁回想起Java、Flash以及Silverlight:这些技术当初也承诺得很好,到头来还不是各种毛病?针对HTML5有很多传闻,但很多都不是源自规范本身,而是来自它的声明。

当开始扩展语言并为它添加新的功能时,事情就更乱套了。每个浏览器厂商和 Web 开发公司几乎每周都会提出一些非常好的新理念,但开发者只想实现开发目标,所以对他们来说,这种状态真是太糟糕了。怎么能依赖那些正在开发的功能呢?它们将来还会不会变?浏览器越来越跟操作系统挂钩,能直接访问硬件,这就带来了安全性与健壮性的问题,这些问题都需要反复地尝试来解决。你甘愿冒此风险来交付产品吗?

目前的HTML5发展状况非常令人振奋,若你参与其中,则有可能为将来的Web开发环境贡献自己的一份力。假如你没有时间参加邮件列表里的讨论,可以用预览版技术迚行大量的浏览器测试,提出你自己的观点。在此过程中,你可能会对有些现象非常困惑。

这就是本书所面对的技术环境。书中没有介绍据说很快就能用的各种功能,而是基于过去的、久经验证的技术,提供了大量的可用实例。不提供试验性的示例,而是直接给出生产级代码,所使用的功能都是现代浏览器上所支持的,仍而更方便开发者,也提升了终端用户的用户体验。书中所有的范例旁边都会带有相关的浏览器支持情况的说明图例,仍而避免了在旧浏览器上应用这些功能。

现在就使用安全且智能的解决方案(如 Modernizr 和 HTML5 Boilerplate)学习HTML5吧。最终你会理解如何编写当前适用的HTML5应用,仍而成为改变HTML5现状行动中的一员,让HTML5成为真正的生产级应用技术。

对于那些意图定义下一代浏览器和语言功能的开发者们而言,需要的是现在能够出现大量实现的应用。“展示并说明”的表演秀已经是昨天的事了,现在需要的是“交付并增强”。借助本书中的建议和范例,你完全可以成为一名合格的HTML5开发者。努力向前,积极开发吧!

Christian Heilmann

Mozilla HTML5 首席布道者

前言

写一本能够较为全面介绍HTML5内容的书进比想象更为困难。首先,浏览器和规范本身一直在改变,似乎无论半年内写了多少东西,浏览器总会对一个实现加以调整,从而让几章的内容都失效。这就会让整个写作过程反复,很难再为章节确定最终版本。另外,我们还看到许多关于HTML5的书仅仅上市几个月后,内容就完全过时了。这种情况更让我们感到,与其继续追逐这种更新与前沿,我们最终还是决定专注讨论较为坚实一些的Web应用技术,这些内容不会随着HTML5的发展有太大的改动。

本书起初时只有Robert Crowther(当时已经在写另一本书了)和Joe Lennon这两位作者,这增加了本书写作初期的混乱度。不得不说的是,Rob 旺盛的精力让我感到吃惊,他当时还打算同时写Hello! HTML5 and CSS3(Manning,2012)。另外,似乎还嫌这些工作不够多,他还复审合作者完成的章节并给出有益的反馈意见,所以我一直很奇怪:他哪有时间睡觉呢?

Joe Lennon 撰写的内容是表单、文件存储以及附录,还对HTML5 规范做了一个精彩的概述。Greg Wanish(起初是我们的编辑)辅助Joe 完成了这些章节。这两位作者解决了一些非常困难且不稳定的规范,这些规范仍然在很多浏览器中还未完全实现。于是 Ash Blue加入迚来,以解决HTML5的交互性可视化数据相关的庞大规范。

Greg和Ash一样都在美国,Joe在爱尔兰,Rob在伦敦。由于这种地理差别,我们很难在一起开会,几乎每次都聚不齐。如果你曾经参与过团队项目,那么肯定能理解对于本书而言,会议的重要性。另外,即使我们四个人都能把大部分的空闲时间用于写书上,它花费的时间仍进超过我们的预期。之所以造成这种耽搁,一部分原因在于我们想让本书囊括时下最新的技术和规范。另外,MEAP的读者为我们提供了很多有关本书的改迚意见,根据这些反馈,我们对本书作了迚一步调整。无疑,这也花去了很多时间。

撰写本书的经历让我们明白了一个道理,那就是永进都不要碰试验性的主题,想都不要想!但说实话,HTML5规范发展的不稳定性,既给工作带来了困难,又确实令人有所斩获。最后,希望我们的辛苦不会白费,希望读者能借助本书更为轻松地掌握HTML5技术。

致谢

我们要感谢Manning 出版社的编辑Renae Gregoire,她一直在容忍我们。她不仅复审了大量的文稿,还每周报告状态,把我们召集起来开会,如果没有她的这些付出,这本书不可能问世。在本书写作过程中,她是半路接手的,但却通过努力,很快就追上了写作团队的迚度。话说到此,另外还要感谢我们的第一位编辑Maria Townsley启动了这个项目。

感谢Manning出版社的所有人。在本书的写作过程中,他们的耐心无与伦比。他们完全可以出版一本内容不太成熟的书,但秉承着真正为读者考虑的理念,他们最终还是让这本书的品质变得非常出色。TroyMott非常擅于寻找技术作者,如果没有他的努力,这本书也写不完。

感谢MEAP的读者和审稿人,正是他们的注释和修订才使本书至臵纯善。在此,我要感谢所有本书出版过程各个阶段中付出辛劳的审稿人,他们是:“Anil”Radhakrishna、Alexander Esser、Arun Noronha、Chris Zimmerman、Dave Pawson、Dmitry Skylut、Donald Matheson、Federico Tomassetti、James Hatheway、Jeff Kirkell、John Ryan III、Jonas Bandi、Joseph Morgan、Julio Guijarro、Leonel Waisblatt、Lester Lobo、Lloyd S. Derbyshire、Michael Caro、OsamaA. Morad PhD、Robert Williams、Sebastian Rogers、Stan Bice、Timothy Hanna、Tyson S. Maxwell。

最后,尤其要感谢Mozilla的Chris Heilman为本书撰写前言,以及Adam London在本书即将付梓前对手稿迚行的细致的技术校对。

Rob Crowther

这是我一年之内第二次写致谢语,所以那些在我第一次致谢语中出现的人,对于我来说依然非常重要,他们是:我的家人、朋友、引导我走上 Web 开发之路的同行,以及我在第一本书中所感谢的人。对于本书而言,我还要特别感谢我的同事。两年内,我在同时写两本书,他们为我提供给了很多无私的帮助(有时或许是无意之中)。这些可爱的人是:Ade、Adriana、Alexandru、Amy、Angelique、Annie、Anusha、Boris、Carlos、Chani、Dan、Danielle、Darren、Dave、David、Delia、Denis、Don、Dorin、Dragos、Eric、Gary、Gemma、Gifty、Hazel、Indrani、Ioan、Ionel、Jack、Jhumi、Jo、Katie、Liam、Liming、Lindsay、Lisa、Louise、Marc、Marinela、Mark K.、Mark R.、Mark W.、Martin H.、Martin J.、Mihai、Nancy、Natalie、Nia、Patricia、Paul、Paula、Phil、Razvan、Rhavy、Rob、Sally、Scott、Sean、Simon、Stella、Sudini、Tal、Tom H.、and Tom W.。如果我忘了某些人,那真是太不好意思了,但那或许是因为你两年来一直都没有给我发过邮件。

Joe Lennon

感谢我的妻子Jill,有了她的爱与支持,我才不会迷失。还要感谢我的父母Jim和Maria,我的姐妹Laura 和Kelly以及Mac Sweeney一家,还有Core International 的全体员工。最后,还要特别感谢科克大学的Ciaran Murphy教授和Patricia Lynch教授以及Troy Mott,感谢他们首先找到我来写这本书。

Ash Blue

感谢我美丽的妻子为本书贡献了精彩的美术资源,她非常体谅我,幵努力不打扰我的写作。还要感谢我的家人,支持我在假期继续写作。还要感谢我的朋友,我闭关写作将近一年,他们竟然会非常容忍这种行为。我努力试图平衡写书与日常生活的关系,有时会变得疯狂,还好,我没被送去精神病院。

Greg Wanish

我要感谢我的父母。几年来,他们一直都在不懈地支持我的梦想和抱负。追逐梦想所带来的冒险和经验让我变得越发聪明,这一点进进超出了我的预期。

关于本书

HTML5是Web开发领域中发展最快的技术之一。业界之所以能够如此快速地采用这项技术,全有赖于它的跨平台可用性,既能在台式机上使用,也能在移动设备上使用。理论上,只要编写一次应用,就能在任何环境下使用它。通过简单的API调用,它还能提供强大的本地功能。

由于HTML5的动态特性,其应用范畴进不止移动浏览器与桌面浏览器这两方面。通过PhoneGap和appMobi这种平台,我们可以把HTML代码编译成本地移动应用,这就为开发者和公司减少了大量的跨平台开发费用,使他们不必再为iOS与安卓系统编写两套独立的代码基。

多数HTML5的API都仌处于起步阶段,所以在初次开发HTML5应用时,可能会碰到一些陷阱,我们会帮你绕开它们。另外,本书还将介绍最新的回退方案、面向应用的JavaScript 技术,以及HTML5 API 的真正特点。

目标读者

如果想创建真实、全功能的Web应用,那你算是挑对书了。本书所涉内容非常广泛,包括数据存储、通信,甚至还介绍了如何创建视频游戏这样的交互式内容。

本书要求读者能够基本掌握JavaScript和HTML语法。如果不理解“循环”、“数组”以及“JSON”这种术语,那么你最好能先学习一下相兲知识。

本书结构

第一部分 简介

第1章大致介绍了HTML5标记语法和本书所涉及到的全部API。

第二部分 基于浏览器的应用

第2章仍HTML5标记开始,逐步介绍了如何制作电子商务网站的表单,幵深入探讨了计算和输入验证功能。

第3章逐步介绍了文件系统的创建与数据管理,还介绍了拖放功能与Geolocation API。

第4章要更为复杂一些,主要介绍了WebSockets通信以及创建聊天系统的其他技术。

第5章深入探讨了用于存储的各种HTML5 API,包括IndexDB 和本地存储。还介绍了如何构建一个任务列表移动应用。

第三部分 交互式图像、媒体及游戏

第6章通过Canvas API 创建了一个叫做Canvas Ricochet 的HTML5 游戏。该游戏具有简单的兲卡难度系统。

第7章介绍了与Canvas类似的可缩放矢量图形(SVG)技术,幵用它构建了一个2D的太空射击游戏。

第8章深入介绍了HTML5中有兲视频与音频的API,展示了其中一些强大的技术。另外,它还介绍了格式问题、输入,以及如何创建视频播放器等内容。

第9章最为复杂,涉及用于3D编程的WebGL技术。本章将逐步介绍如何创建一个带有复杂形状的3D太空射击游戏。

附录

本书一共提供了10个附录,用来深入讲解本书的一些主题,提供软件设置或安装方面的建议,幵附带了重要的资源链接和参考文档。

■附录A:HTML5 技术及其相兲规范。

■附录B:HTML5 API 参考。

■附录C:安装PHP 和MySQL。

■附录D:计算机网络知识基础。

■附录E:设置Node.js。

■附录F:通道通信。

■附录G:开发工具及库。

■附录H:利用FFmpeg 编码。

■附录I:HTML技术未来展望。

■附录J:链接及参考。

每章特性

在每一章之前,都会列出一种叫做“本章主要内容”的列表,介绍本章所涉及的主题,以及每一主题对应的页码。

本书还在页边空白处附上“Core API”的图标,用此来强调兲键主题,方便读者轻松查找所需内容。

代码约定与源代码下载

本书中的代码用monospaced体标注。注意,虽然本书力图使代码尽量简短,但还是有些代码出现了跨页现象。太长的代码行也会排到下一行中,超过一页的代码段也会在下一页继续。为了帮助理解,添加了代码旁注,这些旁注通常用JavaScript注释的形式(//或/* */)来标注。

全书各章的源代码可仍Manning出版社的网站处下载(www.manning.com/crowther2/或者 www.manning.com/HTML5inAction),或者可以仍 GitHub 库中下载(https://github. com/html5-ia/html5-ia)。

所需软件

为了完成本书的应用范例,需要最新版的Chrome浏览器(Mac或Windows操作系统均可)。源代码中的readme.txt文件内还会列出所需的其他配置。

作者简介

Rob Crowther 是一位英国伦敦的Web 开发者与博主,他还是Manning出版社Hello! HTML5 and CSS3 一书的作者。Joe Lennon 是一位爱尔兰的企业移动应用开发者。Ash Blue是来自Clever Crow Games 游戏开发工作室的一位开发者。作为一名独立开发者,他使用HTML5 来制作游戏,幵将它们发布到各个平台上。他之前还为一些企业开发过性能非常健壮的前端架构和应用解决方案,这些企业包括Hasbro、Tastemaker以及Wikia。他的博客地址是blueashes.com。Greg Wanish 也是一名独立开发者,主要工作方向是客户端应用及电子商务应用。他还设计幵销售一些文化衫。

作者在线

购买了本书,读者就能免费访问Manning出版社运营的私有网络论坛。读者可以在此就本书内容提供反馈意见,询问技术方面的问题,迚而获得作者及其他用户的帮助。用浏览器打开www.manning.com/HTML5inaction即可访问幵订阅该论坛。

关于封面插图

本书封面插图摘自19 世纪法国出版的沙利文·马雷夏尔(Sylvain Maréchal)四卷本的地域服饰风俗纲要。在原书中,这个人物的标题为“Le touriste”,意指旅行者。该套书中的每幅插图都是手工精心绘制幵上色的。书中所展示的丰富服饰,令我们强烈感受到 200年前城镇与地区间的巨大文化差异。虽然彼此居住环境和语言有别,但无论是在街上还是乡间,通过人们的服饰,一眼就能看出他们的居住环境、职业或生活状况。

如今的着装风格早已不同于往昔,区域性的服饰差异也消失了。全球的衣着风格逐渐统一起来,更不要说各个城镇和地区了。或许,正是由于这种文化多样性,才衍生出如今更为多元化的个人生活方式,一种跟技术更替有兲的快节奏生活方式。

当今时代,计算机图书品种丰富,令人目不暇接。在新的时代,马雷夏尔的作品在Manning旗下图书上焕发新生。Manning以200年前各个地区生活的丰富多样性为喻,用这些珍贵精美的插图来赞美计算机行业所具有的创造性与积极性。

第一部分 简介

首先,一定要明白HTML的语义标记基本知识以及HTML5API的多样性。本简介将概述这些内容(但仍会涉及不少细节)来帮助你上手。

如果你已经开始利用 HTML5 的新标签结构来制作网站,那么你完全可以忽略这一部分。但那样做兴许就会错失了解一些先进标记概念的机会,比如ARIA和微数据。当然如果你已经非常熟悉它们,则又另当别论了。

第1章 HTML5:从文档到应用的转变

本章主要内容

■HTML5 的基本知识

■新语义标记及媒体特性

■新的JavaScript API

■紧密相关的Web 规范

HTML5是现在Web开发的最热门主题之一,这是有充分理由的。这不仅因为它是最新的Web标记语言,还因为它制定了Web应用开发的一整套新标准。上一个版本的HTML语言(以及它那以严格著称的基于XML的兄弟:XHTML)主要把HTML限定为一种用于页面文档的标记性语言。HTML5则是第一个将Web作为应用开发平台的HTML语言。

HTML5 定义了一系列新元素,用以帮助开发者创建富互联网应用,另外还提供了一些标准 JavaScript API,用来在浏览器内实现类原生应用。<video>元素就是 HTML5 的新元素中的一员,有了它,我们就可以在浏览器中播放视频,而无需安装任何额外插件。另外,HTML5 还提供了Media Element Interface,能让我们借助JavaScript来控制视频播放。它还具有开发游戏、构建移动应用等诸多功能。

本章主要学习内容

■HTML5 新引入的杰出特性,如何将它们立即应用到Web 应用中。

■对于还在使用旧版或不兼容浏览器的用户,如何提供回退或替代方案。

■使用可访问的富互联网应用(Accessible Rich Internet Applications,ARIA)角色和微数据来进一步增强HTML页面的语义。

■HTML5 自身所用到的大量JavaScript API,以及一些紧密相关的API 规范。

通过本章的学习,你会对HTML5所能提供的功能范围有更为清晰的认识,并将其用于自己的应用中。

文档(HTML4)与应用(HTML5)

Web起初只有文档。1993年,Mosaic浏览器添加了表单,但只有数据输入,所有的应用逻辑还只能位于服务器端。1995 年出现了 JavaScript 技术,从而使得实现浏览器应用在理论上成为可能,但其可行性仍不高,直到1999年出现了XMLHTTPRequest对象。HTML的一个重要版本是 4.01,但也只是一个推荐标准。所以并不奇怪,4.01 规范基本上还着重于如何使用标记来描述文档,也就是我们现在通常所说的语义标记。

HTML5姗姗来迟,这期间互联网已经发生了巨变。如下文所示,在语义标记方面,HTML5有很多改进。然而,相比起HTML4来说,HTML5最大的改进和差异在于利用JavaScript构建浏览器应用的能力。基于此,而且也由于本书内容的重点在于介绍HTML5的新特性,我们更主要介绍的是JavaScript,而非标记语言。当然,我们会涉及到标记语言,但如你所知,JavaScript才是HTML5真正的主角。

下面,我们先从如何创建HTML5文档开始。

1.1 探索标记语言:HTML5速览

要想学习HTML5的新技术,最好直接上手进行实践的探索。本节不仅会概要介绍各种新特性,还会教你顺利地将现有的应用利用HTML5约定进行更新,即使用户没有最新最强大的浏览器,他们的用户体验也不会变差。

本节主要学习内容

■如何创建基本的HTML5 文档结构。

■如何使用新的语义元素来布局页面。

■如何处理无法识别新元素的旧版本IE 浏览器。

■能立刻用HTML5 实现的新表单功能。

■如何使用新的UI元素,例如进度条与可折叠部分。

我们先从HTML5文档的基本结构出发,假如你不喜欢这些基础知识,可以快速阅读它们。到了1.2节,就已经略过了语义标记,从而进入到HTML5的生态系统中。

1.1.1 HTML5 文档的基本结构

与采用旧版本的HTML语言相比,HTML5在构建文档的方式上别无二致:文档都以<!DOCTYPE>声明开头,整个HTML文档都要包含在<html>和</html>这两个相互匹配的标签内。在这些标签间,还有一个<head>部分,用于存放<meta>信息及其他一些非内容的项目(如样式表),以及一个存放页面内容的<body>部分。如果你之前写过HTML文档或应用,就会很熟悉这些概念,但是你应该留意HTML5的一些细微差别,本节就将介绍这些差别:

■HTML5 的DOCTYPE声明的格式;

■如何使用开放的<html>元素;

■在<head>部分中,如何使用各种元素的精简版本。

下面,我们通过hello.html文档来详细讨论一下具体区别。代码清单1-1是用HTML5写的一个“Hello, World!”程序。

代码清单1-1 hello.html——HTML5 文档的基本结构

基本的页面结构就是这样,接着,让我们来看看如何利用新的语义元素来构建一个简单的博客文章页面。

HTML与XML

对于之前的HTML规范,我们既可以认为它们是HTML,也可以认为是XHTML。HTML标记设计得非常宽松,而XHTML则是基于XML的,并具有一种很严格的解析模型。XHTML要求所有元素都具有结束标签(如不能只有开始标签<br>而没有结束标签<br/>),另外它还要求所有的标签与属性都必须小写。一个小错误就能导致整个页面的加载失败。正是由于这种苛刻的错误处理机制,多数网站根本无法正确实现 XHTML。它们更倾向于使用 XHTML 的语法的表单,但却利用text/html内容类型来发送页面,将XML标记用HTML来解析。

HTML5将HTML与XML的各种规范融合为一种规范,也就是说,规范提供了一种词汇表,既可以用 HTML,也可以用 XHTML 来表达。XHTML 序列化必须要带上的 XML 内容类型(如aplication/xml+xhtml)一齐发送,另外,它还需要遵从XML的解析原则,带上xmlns声明、闭合标签等内容。在本章的下载代码中,还有着代码清单1-1的另外两个版本,展示了用正确及不正确的XHTML标记书写的同样的标记:hello-invalid.xhtml,在XML文档中使用HTML语法;hello-valid.xhtml,将标记都改成了正确的XML。

1.1.2 使用新的语义元素

如果此前你了解过HTML5内容,那么有可能听说过大量关于新语义元素。它们具有十分重要的作用,尤其是能让搜索引擎或辅助技术(如屏幕阅读器)更好地理解页面,使用起来也比你所熟悉并喜爱的HTML4元素要更容易。

但不要因此而特别兴奋。如果你希望这些新元素能够让页面变得绚丽,那你可能会有些失望了。就功能而言,这些新的元素相当于一系列<div>元素。默认情况下,它们的作用就像是块元素一样,并且可以使用 CSS 进行样式化。它们的重要性来自于它们所有的标准语义。

考虑一个常见的博客文章页面,Web 页面包含一系列区块(section)。首先,你需要有站点的标题和导航链接,或者是一些侧边栏导航链接,一个主要的内容区域,带有更深一层导航链接的页脚,或是一些版权声明和法律声明链接。代码清单 1-2 展示了如何用HTML4或XHTML来书写该博客文章的标记。

代码清单1-2 html4-blog.html——博客文章中的HTML4 标记

之前的代码没有错,在HTML5里仍然有效,你完全可以继续使用<div>元素再配上你喜欢的语义类名。但从语义的角度来看,这种方法却暴露出几个问题。

■使用旧式标准,使用已命名的类来区分博客文章页面内的各个区域。这貌似没有问题,但类的命名规范完全取决于作者。一个人可能取“header”,另一个人可能取“heading”,主要内容部分或许叫做“main”,而另一个人或许把它取作“body”或“article”。

■有些人可能更喜欢使用ID来取代类,他们可能会把id定义为“header”,而另一些人可能会把类名定义为“header”。

总之,搜索引擎或其他由计算机控制的应用程序无法可靠地确认每一区块所呈现的内容。

这个问题需要利用新的语义元素来解决。现在,我们与其使用类和ID来定义各种区块(标题、导航链接、页脚),不如采用几种不同的HTML元素来定义,如代码清单1-3所示。把这个代码插入到hello.html文件的<body>标签之中。

代码清单1-3 html5-blog.html——博文的HTML5 标记

另外两个重要的HTML5元素:<aside>与<mark>

有必要再介绍一下另两个以后会常用的 HTML5 元素:<aside>与<mark>。你可以用<aside>元素来定义一个在页面中独立于主要内容区域的部分。在传统的书籍或杂志中,这部分内容通常表现为边栏,包含同一主题的信息,但跟页面上的主要文章又不尽相同。如在一个博客内,可能会在文章旁边放上广告,这些广告内容就可以放在<aside>元素中。在 Web 应用主要部分的上方,还可以用<aside>来实现弹出式广告或浮动窗口。

<mark>元素可以用来展示文档中应被标记或者说突出显示的文本部分,通常用来高亮显示文档中的搜索词。

有了新的语义元素,页面标记结构不仅清晰易读,搜索引擎机器人和辅助技术也能轻易理解页面。说到辅助技术,就涉及到了下面要谈到的一个主要主题:ARIA角色。

1.1.3 使用 ARIA角色来增强可访问性

在构建 Web 应用时,你必须确保所有用户都能访问,这其中就包括那些需要辅助技术(如屏幕阅读器)才能访问的用户。要想确保页面被访问,就应该慎重考虑标记语义的问题。单纯使用HTML标记就能让这项任务变得简单起来,而且现在HTML5的新元素还进一步增强了语义。但是,在创建 Web 应用时,迎合辅助技术却变得更加困难。这是因为,在现今的Web应用中,用于动态修改页面的JavaScript代码量不断增加,从而使得单纯通过良好的标记来实现优秀可访问性变得愈发困难。为了解决这种问题,人们制定了WAI(Web Accessibility Initiative,Web 可访问性倡议)和ARIA标准及规范。

WAI-ARIA规范旨在通过对HTML文档作者提供的可访问性信息加以扩展来改善Web应用。ARIA角色、关系、状态及属性,可为Web应用定义出能够被屏幕阅读器这样的辅助技术所理解的工作方式。例如,构建了一个用于文本输入的下拉列表和一个无序列表,可以将ARIA角色combobox添加到<input>元素中,从而使其可以正确地在用户设备上显示出下拉列表框(combobox)。代码清单1-4展示了直接取自WAI-ARIA1.0规范文档的一个范例。

代码清单1-4 www.w3.org/TR/wai-aria/roles#combobox中的ARIA combobox范例

HTML5规范现已明确声明,可以按照ARIA规范所描述的那样,在HTML元素上使用ARIA的role和aria-*属性,而在HTML4中,是不可以这样做的。HTML5还为特定HTML元素定义了ARIA默认角色。如暗示了复选框的<input>元素的角色是checkbox,所以不需要像其他元素一样显式地使用role或aria-*属性了。

有些 HTML 元素能够通过改变原生语义而实现不同的行为,例如可以创建一个行为像按钮的<a>元素,在经过一些验证后,用它来提交表单。HTML5 规范为这些元素定义了一些有效语义。当你使用<a>元素来创建超链接时,则它的角色默认为 link,而经过修改后,它的角色则可以表现为以下角色其中之一:button、checkbox、menuitem、menuitemcheckbox、menuitemradio、tab或treeitem。

关于默认、暗示的 ARIA 语义的完整列表以及具体元素语义的修改限制,可以参看HTML5规范的WAI-ARIA部分:http://mng.bz/6hb2。

1.1.4 IE6~IE8的支持情况

HTML5 元素与旧浏览器的兼容性可能是你比较关心的问题,这也理所当然。HTML的每一版都会引入新的元素,HTML5 也不例外。多数现代的浏览器都能呈现这些元素,即使对于那些并不特别支持这些元素的版本也是如此。多数浏览器在处理未能识别的元素时,一般会将其渲染为通常的内联<span>元素。然后,只需将其设置为block with CSS,让它们按照CSS来呈现即可。遗憾的是,IE例外。在IE9之前,未能识别的元素虽然能够渲染,但却不能按照 CSS 来呈现。可想而知,这就导致在生产应用的开发中很难利用新的HTML5元素,因为用户有可能还在使用IE6、IE7或IE8。

在IE中渲染新的元素属性

幸好,这个问题补救起来也很简单。如果想在页面上应用<header>元素,并使用CSS 样式,可将下列代码段放入页面的<head>部分内。这将强制 IE 在标签中使用 CSS样式,即使所使用的IE本身并不支持某个元素。

需要为每个希望在页面中使用的HTML5元素都执行一个作用相等的JavaScript语句。这样做会导致从IE6到IE8都能正确地渲染格式,但在试图打印页面时,问题依然存在。

如何在IE打印页面上正确地渲染新元素

一种叫做IE Print Protector的解决方案可以修补这个打印问题。但与其重复劳动,还是推荐使用 HTML shiv 脚本来解决这个问题。Remy Sharp 最先开发出的这个最流行的HTML shiv脚本,随后很多人又对其加以不断地改进。欲详细了解该脚本及其最新版本,请参考http://mng.bz/50dt。

警告 HTML5 shiv解决方案需要使用JavaScript。如果不想使用JavaScript,可以用基于XML的HTML5同胞兄弟——XHTML5。请参看Eric Klingen对该方案的讨论:http://mng.bz/QBIw。

HTML5 还有哪些功能可以对现存应用加以大幅改善的呢?利用一些整合的简单功能,让表单变得有趣起来,怎么样?虽然表单在页面上很常见,但有了 HTML5,它们就变得不再那么苍白乏味了。

1.1.5 HTML5 所引入的新表单特性

虽然Web表单极其简陋,几乎没人喜欢,但Web之所以开始成为应用开发平台,关键因素之一即是Web表单。正是由于关注Web应用,所以HTML5在Web表单上做出了很大的改进,所有功能现在就可以用到旧浏览器上,无需为兼容性操心。

使用新的表单输入类型,改善数据输入语义

文本字段的使用范畴和频率早已远远超出了它的基本功能。就像在HTML4中<div>元素也被用来包含所有的块元素一样,文本字段也被用来处理所有的文本输入。HTML5提供了一些新的、向后兼容的类型,比简单的文本字段的功能更为强大,表 1-1 列出了HTML5中所提供的新输入类型。

现在,即使旧版本的浏览器并不理解某些输入类型,这些新型 Web 输入类型都能够立刻使用,但需要将其回退到标准输入类型再加以使用。一些新输入类型还允许浏览器为某些指定类型的表单字段提供标准的小部件。图1-1展示了一些新的小部件范例。

第2章介绍如何通过Modernizr(HTML5的一个特性侦测脚本)来判断浏览器是否支持指定的输入类型,并在需要时提供能够回退使用的JavaScript部件。

改变字段行为的新属性

除了引入新的表单字段类型,HTML5还引入了10种通用属性,如表1-2所示。利用它们,就可以改变指定字段的行为。拿其中的placeholder属性来说,它可以让文本字段在没有输入之前,显示一些预设文本,如图1-2所示。

表1-2展示了HTML5新引入的一些输入属性。到了第2章,再来介绍这些属性与相关输入类型的应用方式。

能够执行客户端验证的新属性

无需使用JavaScript,有些属性就可以使浏览器执行客户端验证。例如,required属性明确要求字段必须被填充,否则浏览器就会报错。pattern属性定义验证输入数值的正则表达式。max和min属性则限制了 number和date字段类型的最大值及最小值。

另外,浏览器对一些新的输入类型会执行一些验证,当用户输入的数值格式不正确时,浏览器就会警告用户。例如用户在email 输入字段中输入了错误的电子邮件地址,浏览器就会给出一个出错提示,从而防止表单被提交至服务器端。

警告 不能单纯依靠客户端验证。无论是HTML5 新增的浏览器原生验证,还是JavaScript 验证代码,它们都不可靠。由于可以很容易绕过客户端验证,服务器端验证输入是不可或缺的。客户端验证主要应该用在改善用户体验上,并不能用作保障应用绝对安全的手段。

第2章将更为详细地介绍新增输入类型及属性。先来介绍一些HTML5新增的其他元素,它们可以轻松快速地与现有应用进行完美整合。

1.1.6 进度条、度量器和可折叠内容

HTML5定义了很多新颖的用于向用户传递信息的元素。这其中就包括很多开发者此前只能靠第三方的JavaScript库来实现的小部件,如进度条、度量器和可折叠内容等。

使用进度条来显示完成进度

利用<progress>属性,可以向用户呈现确定(或不确定)的进度条。确定的进度条有指定数值,进度条的进度由该值确定,所以很适用于显示文件上传进度——随着文件上传量,动态地改变进度条数值。不确定进度条则没有明确的数值,进度条会完全充满,在不确定操作进度的情况下,应用这种进度条,用户就能知道应用正在加载。这两种进度条的具体情况如图1-3所示。

要想创建图1-3所示的进度条,只需下面这两行代码:

使用度量器来显示已知范围内的用户数据

<meter>元素与<progress>元素较为类似。<progress>主要用于展示任务的完成度(百分比),而<meter>则能展示在已知的标量范围内的数据变化。除了使用填充条图形来展示数值之外,我们还可以定义low、high和optimum这些范围关键点参数,还可以赋予这些参数更广泛的含义。当取值在低(low)范围时,度量器的填充条显示为红色;取值在中等范围时,显示为黄色;取值为高(high)和最高(optimum)范围时,显示为绿色。图 1-4 展示<meter>元素在取值不同情况下的显示效果。

图1-4所示的<meter>元素范例的代码如下:

无需JavaScript,使用<details>和<summary>来创建可折叠内容

以前要想创建可折叠内容,需要利用JavaScript切换该内容部分的CSS属性,才能实现内容的隐藏与实现。而HTML5利用<details>和<summary>元素,无需脚本代码,就能提供这样的功能。图1-5显示了这些新元素的实际应用效果。

该<details>和<summary>范例代码如下:

遗憾的是,浏览器对这些新元素的支持相当缓慢。不过,可以使用JavaScript轻松地创建一些回退方案,参见http://mng.bz/cJhc。

利用本节所学的这些技术,你现在应该可以将已有应用升级到HTML5规范中去,不会因为用户没有使用最新、最强大的浏览器而导致任何副作用。下一节将介绍标记之外的一些特性及规范,并利用CSS3与JavaScript改善文档样式与交互性。

1.2 标记:附加的Web规范

正如我们所提到的,Web不再是文档的天下,它已然成为一个应用开发平台。因此, HTML5 也不再只包含那些描述文档结构的标记,还包含了更多的特性及其相关规范,用以确保Web应用的视觉效果出类拔萃,尽可能使用户体验臻于完美。微数据(Microdata)及其API就是其中的一个典型代表。通过使用微数据,可以为文档添加额外语义,以后还可以取回并修改它们。另外一个例子就是CSS3,应用这一改进后的样式表,可以为Web页面添加最新的样式及效果,而不必再依赖外部图像或JavaScript技巧来实现。

本节主要学习内容

■如何使用微数据和微数据词汇向搜索引擎提供更好的页面信息。

■微数据DOM API通过使用JavaScript,能够动态取回并修改微数据项。

■了解CSS3 的一些新特性。它们在增强应用的视觉效果的同时,也能实现更好的用户交互及反馈。

■通过详细的规范及更加完善的API来了解JavaScript在HTML5 中的重要地位。

下面先来了解一下什么是微数据。

1.2.1 微数据

使用HTML5中的微数据,可以为Web页面添加语义信息。另一方面,搜索引擎和浏览器也可以通过微数据,向用户提供额外的功能。如图1-6所示,Google利用微数据提供了更为智能的搜索结果。

要想使用微数据,需要一个词汇表,它要定义出你所用到的语义。词汇表可以自定义,但你可能更需要已发布的词汇表,如 Google 发布的词汇表(www.data-vocabulary.org),其中包含了事件(Event)、组织(Organization)、人(Person)、产品(Product)、评论(Review)、评论集合(Review-aggregate)、面包屑(Breadcrumb)、报价(offer)、集合报价(Aggregateoffer)。通过已发布的词汇表,能够保证搜索引擎及其他应用能够始终如一地理解微数据。

代码清单1-5 展示了微数据的实际应用。它使用了一个事件(event)条目,并引用了谷歌的事件微数据词汇表:www.data-vocabulary.org/Event。该段代码为一个事件创建了一段 HTML 标记,并定义了相应的微数据属性,从而能够让搜索引擎清晰无误地解析事件信息,用它来增强搜索结果,也可能会将事件的日期显示在日历上,或将该事件标记到地图上。

代码清单1-5 html5-microdata.html——微数据实例

HTML5 规范还定义了一个DOM API,用于利用JavaScript进行动态取回和修改微数据项。表1-3提供了该API的具体描述。

微数据的使用,可以极大地改善应用(或 Web 页面)在搜索结果中的表现。下一节将介绍,如何借助CSS3所提供的新样式与效果,使应用展现令人惊艳的视觉效果。

1.2.2 CSS3

如果长期从事Web开发,可能会记得过去在对HTML页面的样式进行编排时,一定会用到<font>元素以及一些<table>元素的使用技巧。幸好,引入了级联样式表(Cascading Style Sheets,CSS)后,就可以不必再用以上这些方法了。

随着Web的发展,呈现内容的方式也在不断的创新中发展,开发者使用阴影、圆角、渐变来改善应用的视觉效果。更令人惊叹的是,他们甚至会利用过渡和动画来实现更好的用户反馈及交互效果。但是,要想实现这些出色的增强功能,一般都需要利用图片或JavaScript(或者,至少需要使用某个 JavaScript 库)的某种欺骗性技巧。CSS3 的出现使这一情况有所改观。现在,根本无需使用JavaScript或图片技巧,就能实现一些样式效果。表1-4列出了CSS3所能实现的一些新样式效果。

本书范例主要利用HTML和JavaScript来构建应用。虽然会使用CSS3进行样式处理,但并不会在书中加以介绍。你可以下载本书范例文件源代码来了解其中用到的CSS代码。如果想详细了解CSS3,请看 Hello! HTML5 and CSS3(Manning,2012),这本书的作者就是本书的合作者之一——Rob Crowther。

1.2.3 JavaScript 和 DOM

在现在的Web应用中,JavaScript以及文档对象模型(DOM)起着极为关键的作用。通过与页面元素动态交互,开发者可以实现以前只在桌面应用上才可见到的丰富功能及交互性。异步JavaScript与XML(AJAX)的诞生,使得页面刷新的烦劳一扫而空,客户端行为可以内联更新,从而使用户体验获得很大的提升。JavaScript对象表示法(JavaScript Object Notation,JSON)成为Web 应用的实际交换格式,多数服务器端语言及架构现在都对它有着原生的支持。另外,现在还出现了很多功能强大的JavaScript架构和库,它们对JavaScript 进行了抽象,从而将开发者从以前备受困扰的跨浏览器不一致性麻烦中解脱出来,更多地去考虑如何打造功能强大的应用。

警告 虽然本书每一章都介绍了如何使用 HTML5 及 JavaScript 构建 Web 应用,但并不适合JavaScript初学者。至少,你应该熟悉一下JavaScript语法以及一些基本概念,比如变量声明、条件语句、函数、闭包、回调、作用域。另外还应了解其他一些概念,比如AJAX、JSON,以及如何与DOM交互等知识。如果你用过jQuery这样的JavaScript库,那么你肯定能跟得上。如果你想更加详细地了解JavaScript,或者感到有所生疏,需要复习一下,可以参考这两本书:Ajax in Action(David Crane、Eric Pascarello、Darren James,Manning, 2005)和Secrets of the JavaScript Ninja(John Resig、Bear Bibeault,Manning,2012)。

在上一版的 HTML 及 XHTML 规范中, JavaScript 的使用范围很小,仅局限在<script>元素,以及与一些能提供事件处理功能的HTML元素属性中。而在HTML5中, JavaScript 的作用大大增强,因而备受重视,规范的每一部分都详细规定了任何指定元素所适用的DOM API 方法及属性。另外,HTML5 还定义了一些高级API,使得Web 应用可以利用视频与音频,离线工作,在本地客户端存储数据,等等。本书将详尽介绍这些API,本章先予以简要介绍。

对 Web开发人员而言 HTML5、HTML Living Standard与 HTML5 的区别

HTML5规范由来已久。简而言之,这一规范最终有两个版本,都由Google的Ian Hickson编写完成。HTML5规范由W3C发布,而HTML Living Standard规范则由Web超文本应用技术工作组(Web Hypertext Application Technology Working Group,WHATWG)发布。更为复杂的是,WHATWG还发布了一个文档:“HTML5: A technical specification for Web developers”,更简洁易读。

这些规范在很多方面都有相似之处,但差异也很明显。比如,HTML Living Standard规范包含了一些W3C发布的完全彼此独立的规范,如微数据、Web存储、以及Web Workers。要想了解这些规范间的差异,请参看HTML Living Standard规范中的“Is this HTML5?”,http://mng.bz/PraC。

本章将把HTML Living Standard 规范中的API看成HTML5的原生部分,这一规范外的API则认同为独立的API。关于WHATWG与W3C各自所发布规范之间的差异,以及这两个组织的不同处理方法,请参看附录A。

下面,我们来介绍一下当前包含在HTML5 规范本身中的DOM API。

1.3 HTML5的DOMAPI

在 HTML5 中,DOM API 的身影无处不在。实际上,尽管已经存在了很长时间,但它们却从没有在HTML规范中得到过定义。这其中就包括一些通过ID属性来获取DOM元素以及操作表单元素值的特性。现在,所有这些特性都包括在了HTML5规范中,规范还定义了一些用于开发高级应用的新DOM API,其中很多都跟HTML元素毫无关系。

本节将要概述的 HTML5 新增 DOM API

■2D Canvas。

■音频与视频。

■拖放。

■跨文档通信。

■服务器发送事件。

■WebSockets。

■文档编辑。

■Web Storage。

■离线Web 应用。

本书将对以上所有主题加以详述,其中在创建有些全功能实例时,经常会用到多个API。本章将大致介绍一下这些API。下面,先来看看<canvas>元素及其相关API。

1.3.1 Canvas

HTML5提供了很多用于在Web页面上呈观信息的元素。可以用许多不同的方式来调整它们的样式,也可以用JavaScript来制作动画并添加动态效果。如果你喜欢使用复杂的JavaScript代码(并且希望用户能用一些高性能的浏览器),那么用HTML和JavaScript可以做出非常惊艳的效果。

但问题在于,设计师和开发者可能需要实现的很多东西都是HTML无法给予原生支持的。比如,怎样插入圆形、正方形或其他形状?如何显示一副图像并能基于用户选择而立刻动态改变它?当然,可以使用一些静态图像或利用服务器端技术来解决,但这些都不是最理想的方案。过去唯一可行的方法就是用第三方浏览器插件来解决,比如Adobe Flash。

HTML5 引入了<canvas>元素以及一些相关的图形绘制 API,从而使用户无需安装插件,即能观看到开发者所创建的令人惊叹的效果。canvas的名字也就很好地诠释了自身的特点:它就相当于Web页面的一张画布。图1-7展示的是完全用HTML5和JavaScript创建的一个游戏“Canvas Break”,利用<canvas>元素来输出游戏的可视化部分。不错吧?第6章将介绍如何使用Canvas API 来创建这个游戏。

Canvas API 定义了一个2D 内容,为在画布上进行绘制提供了一些方法。这些方法能够创建形状、定义路径、使用颜色与渐变、使用文本,等等。另外,开发者还可以通过数据URL或Blob对象,将当前画布内容导出为PNG或JPG格式的图像。

1.3.2 音频与视频

近年来,多媒体内容(视频与音频)占据着互联网大部分的带宽。现在的 Web 视频多数都是Flash video(FLV)格式,这种格式是Adobe Flash 的一种包含多种视频解码器的容器。如果用户安装了Flash浏览器插件,那么就能观看这种视频文件了。一些开发者越来越质疑Flash这一视频平台的安全性和性能,积极寻找替代方案。另外,移动设备对Flash的支持不够,假如想在iPad上观看多媒体内容,那就没辙了。

现在,终于有了替代方案,那就是使用HTML5新引入的<video>和<audio>这两个元素。浏览器能够原生地播放多媒体内容,不再需要第三方插件的支持,如图1-8所示。

<video>和<audio>这两个元素都支持<track>元素,从而可以添加如字幕这样的附加文本。通过使用<source>元素,还可以提供多种文件格式,从而能够以多种系统和浏览器访问同一内容。

HTML5还提供了一种可以控制音频及视频回放的API。利用该API所包含的各种方法,播放、暂停、快进、快退、调整音量等功能都可以实现。第8章将详细介绍如何利用该API的各种功能来创建一种带有视频标注功能的视频播放器。

1.3.3 拖放功能

很长一段时间内,实现拖放功能曾经是令很多 Web 应用开发者深感头疼的事情。自从有了图形用户界面之后,这种功能在台式机软件上已经变得司空见惯了,所以用户逐渐也希望能在Web应用上使用拖放操作,但有时他们会吃惊地发现,自己最喜欢的Web应用却做不到这一点。

20 世纪 90 年代末,开发者开始尝试在浏览器上实现拖放功能,先是由 Netscape 4.0提出了一个简单的实现,而后微软又在IE 5.0 上给出了一个完整而复杂的实现方式。那时,这一功能还只是一个非标准的、IE 独有的浏览器扩展,但后来其他浏览器厂商逐渐采用了这一API,最终使其被HTML5所采纳。

好消息是,HTML5 的拖放功能可以被现今所有浏览器支持,这其中当然也包括IE 5.0 及 5.0 之后的版本。坏消息是,坦白讲,早期微软所用的实现方案实在是太糟糕了。HTML5 规范的编辑Ian Hickson 有一次发推文称“拖放功能API 实在太糟糕了,但这至少意味着IE6和Safari与Firefox一样,实现了这一效果。”

在HTML5中,使用某一元素的draggable属性,即可使该元素(如图像,默认)具备可拖放功能。当用户拖动元素进入或离开其他元素时,甚至当用户放下元素时,你可以利用一系列的事件来侦听这些改变。借助拖放API,你还可以设定与拖动操作的相关数据,当放下时再进行读取。

HTML5还引入一种新的拖放功能,使我们可以把电脑中的文件拖动到Web应用中。如图1-9所示,Gmail就是这样做的。

第3章讲述如何使用拖放功能来导入导出文件。

1.3.4 跨文档通信、服务器端发送的事件以及 WebSockets

Web 应用工作在“请求-响应”模式下,客户端向服务器端发送一个请求,服务器端转而发送给客户端一个响应。如果客户端此后还希望从服务器端获得更多信息,那么就需要发起新的请求。如果客户端不经常发送请求来查看消息更新情况,那么服务器端就很难将消息更新反馈给客户端。本节将介绍HTML5中引入的一些新的通信特性,它们能让服务器端主动和客户端进行通信。在此之前,先让我们介绍一下如何在文档间利用通信来传递消息变更。

利用跨文档通信与通道通信实现客户端通信

在使用有多个浏览器窗口的 Web 应用时,我们经常需要在页面间传递消息。过去,这种功能一般是采用直接操控DOM元素来完成的。例如,老式的航班订票系统可能会用一个新 Web 窗口打开一个日历小部件,当用户选中一个日期时,系统就会直接在父窗口的日期表单字段上修改日期值。

直接操控DOM元素的问题在于,两个文档直接连接在一起,为了共享信息,每一个文档都知道对方的结构。相比之下,HTML5所引入的跨文档通信与通道通信就先进多了,如图1-10所示。

跨文档通信可以让各文档通过消息来进行沟通——一个文档发布消息,而另一个文档利用注册的事件处理器来侦听这些消息。

直接操控DOM还容易引起安全性问题。当应用使用了远端脚本文件时,比如在应用内实现类似谷歌分析(Google Analytics)或Facebook的“喜欢”按钮这样的功能时,这些脚本文件容易降低 Web 应用的安全性。这些脚本都能够完全访问你的页面上所有的DOM元素——当以这种方式加载它们时,你无法限制它们的访问权限。而跨文档通信就比较安全,由于能够跨域运作,所以无需暴露每一个Web应用的DOM即能实现消息在各应用间的传播。

利用服务器发送事件实现从服务器端到客户端的单向通信

对于两个客户端之间的通信来说,跨文档通信是非常好的一种方式。但是,如果想让Web服务器发送的消息能被浏览器接收,则需要用到服务器端发送事件了。利用EventSource接口,你的Web应用能够订阅一个服务器端事件流,它只接收服务器发送的更新消息。与过去使用的难以实现的AJAX长轮询方式相比,这无疑是一种非常大的改进。它非常适合聊天应用,你可以利用AJAX发布新的聊天消息,然后通过事件流接收来自其他用户的聊天消息。稍后将介绍如何创建这种聊天应用,这种应用的截图如图1-11所示。

使用WebSockets实现服务器端与客户端的双向通信

服务器发送事件的最明显的缺点在于只能单向通信,消息只能从服务器端发送到客户端,反之则不行。如果需要双向通信,HTML5 还提供了 WebSockets。这是客户端与服务器端之间的一种最基本的网络技术,没有与 HTTP 相关的开销。WebSockets很适合快速传送小规模数据。对在线游戏和时间敏感性极高的财务系统来说,这种特性是非常关键的。

第4章将通过一个简单的聊天应用和多用户计划板应用来介绍以上三个新通信技术的应用,其间将使用服务器发送事件,并通过Node.js来实现WebSockets,还将通过跨文档通信技术来实现这两个应用间的通信。

1.3.5 文档编辑

如前所述,我们曾经谈过HTML5中新增的表单功能。的确,Web表单能够很好地获取用户输入的纯文本,但并不允许用户编辑 HTML 内容。当然,你可以用<textarea>元素来加载HTML源代码,从而让用户编辑HTML内容,但若通过一些富文本编辑控件来编辑内容,用户体验岂不更好?

HTML5 提供了两种新属性,可以实现在 HTML 页面内进行富文本编辑。一个是contenteditable属性,它可以在页面的任何一个HTML元素中设置,从而使该元素具备可编辑性。另一个是designMode,在HTML文档自身设置,使整个文档都变成可编辑状态。

现在所有浏览器都支持这些属性,而最先引入它们的浏览器是IE5.5。

当元素或文档可编辑时,可以用 HTML5 的 Editing DOM API 的方法 document. execCommand来操纵其内容。该方法能应用于当前选择或区块,可以实现粗体、斜体、插入链接等特性。图1-12 展示了利用这种功能创建的编辑器。第3章还将进一步讲解如何使用这些新特性来创建富文本编辑器。

1.3.6 Web 存储

一直以来,Web应用都在用cookie将数据存储在客户端,它们能在一次会话或多个会话中维持。这就是 Web 认证系统的底层技术:在客户端存储的 cookie 含有一些标识符,Web应用借此可以识别登录的用户。Cookie有很多问题,导致它们只能存储极少量的数据。

第一个问题在于,大多数浏览器都将cookie限制在4096字节内,每个域最多只能存储20个cookie。限额一旦达到,浏览器就会将旧cookie删除,以便为新cookie腾出空间,这就意味着浏览器无法保存所有cookie。另一个问题在于,当Web应用使用cookie时,每一个针对该会话的HTTP请求都会带有cookie,从而加重了每一事务的负担。如果只用一两个cookie,那么根本不碍事,但如果有很多呢?每个HTTP请求都将带着所有cookie发送出去,这就减慢了页面加载速度与应用作出的AJAX请求。

Web Storage DOM API为Web应用提供了一个能够替代cookie的JavaScript解决方案。这一API定义了以下两个接口。

■sessionStorage—— 客户端数据存储,只能维持在当前会话范围内。

■localStorage——客户端数据存储,能维持在多个会话范围内。

该 API 公开的方法能把简单的键/值对数据存储在客户端中。使用这些接口的已存储数据项只能被同一域内的页面所访问。第5章介绍了一个将用户设置和偏好参数保存在本地存储的 Web 应用,如图1-13所示。

虽然该API能够存储兆级数据(多数浏览器最多能支持 5M),但复杂的数据结构却并不适用于此,而通常是存储到数据库中。本章稍后将介绍到IndexDB,这是一种完全索引化的数据库 API,通过它可以将数据存储在客户端本地。

1.3.7 离线Web应用

时至今日,似乎人们永远都处于在线状态——网络一直保持着连接状态,签订了蜂窝数据网络的数据合约,移动设备也可以上网了,现在甚至乘飞机旅行时都可以上网了。即便如此,有时仍会碰上离线的情况:网络连接中断,国外的数据漫游费太高,或者使用的设备没有无线数据连接功能,等等。为离线使用而保存HTML文档的方法很早就有了。但这些技术适用于阅读静态内容(如新闻),碰到Web应用就没辙了。HTML5不再仅限于保存页面这一功能,它可以定义一种缓存清单文件,规定了应用文件的离线缓存方式。

缓存清单文件也可以定义不用于离线使用的文件。在这种情况下,将提供一种回退方案(fallback),当用户处于离线状态时,浏览器就会加载这种回退。这就可以为离线与在线这两种状态分别提供不同的文件。

关于这方面有一个典型的用例,即应用将数据保存到服务器端的数据库中。当处于在线状态时,应用会执行AJAX请求来取回并更新数据库上的数据。在后台,应用会将数据存储在本地的IndexDB数据库中,下一节将予以详述。当处于离线状态时,应用就会加载一个专为离线而设计的JavaScript文件。它并不会向服务器端发送AJAX请求,它会从本地的IndexedDB中取回并修改数据。当下次用户再次联网时,应用就会把本地数据库里的数据提交给服务器端数据库。

第5章将介绍如何开发离线Web应用,如图1-14所示。

1.4 其他API和规范

如前所述,HTML5技术并不仅限于HTML5规范本身,还有大量的其他技术和规范,定义了很多新的功能。现在,很多浏览器厂商都在他们最新版的产品中稳步实现着这些功能,这其中包括地理定位API、IndexDB API、文件阅读器、文件编辑器、File System API,以及SVG和WebGL。

本节主要学习内容

■地理定位API及其使用方法。

■IndexDB API,以及如何利用它在客户端构建数据库。

■面向文件的规范,以及如何利用它将文件保存在用户的本地文件系统中。

■SVG和WebGL,以及如何利用它们在Web 中创建高质量的矢量图形和3D动画。

下面我们就来介绍这些规范,它们虽然是辅助性质的,但作用同样也很重要。先从地理定位API开始。

1.4.1 地理定位 API(Geolocation API)

随着近年来移动设备的大量涌现,地理定位应用也多了起来。现代的智能手机上携带的全球定位系统(GPS)传感器能够非常精确地定位用户的地理位置。即使 GPS 不可用(如移动设备并不具有 GPS传感器,或者用户不在卫星的扫描范围内),移动设备还可以转而使用其他的位置跟踪方法,如利用蜂窝网络信号、Wi-Fi 信号或IP地址。

地理定位API中定义的方法能使Web应用确定用户的地理位置。当调用这些方法时,浏览器就会通知用户,应用正在请求访问他们的位置。用户可以选择接受或拒绝这种请求,从而确保应用不会未经许可就跟踪他们的位置。如果用户接受了请求,地理定位API就会向应用提交一系列有关用户位置的数据,其中包括了用户的地理坐标位置(经纬度)、海拔高度、航向、速度,以及结果的精确度等信息。

第3章会介绍有关地理定位 API 的内容,届时你将学会如何获取用户地理位置并在HTML文档中嵌入地图,如图1-15所示。

1.4.2 索引数据库 API(IndexedDB API)

IndexedDB API 可以让开发者把复杂数据结构存放在完整的客户端数据库中。与Web Storage API 相比,IndexedDB API 的重大改进在于:在Web Storage API 中,唯一的索引是键/值对的键,而在 IndexedDB API 中,值也完全可以被索引了。从而为需要搜索或过滤数据的应用提供了一种比较可行的解决方案。不过,IndexedDB API 更为复杂,一开始很难掌握。

IndexedDB是HTML5家族中相对较新的成员。除了它之外,还有另外一种被人们推荐的解决方案, Web SQL(结构化查询语言)。这种规范定义了一种客户端关系型数据库,使用 SQL 声明来查询并操控数据。可最后这个规范还是被遗弃了,因为所有采用这种规范的浏览器都用的是同一种实现(一种SQLite数据库),而这种情况就违反了WHATWG及W3C的标准化策略:每种特性必须有两种独立的、可互操作的实现。尽管该规范被遗弃,但一些浏览器还是保留了对它的支持,这其中就有移动Safari和Android上的一些移动浏览器。目前,浏览器对IndexedDB的支持迟缓不前,所以多数使用IndexedDB 的Web 应用同时也把Web SQL当做一个回退方案。

在第5章,你将学会如何在任务管理应用中使用IndexedDB(以及Web SQL 回退方案)存储任务数据,如图1-16所示。

1.4.3 文件 API、文件阅读器 API、文件编辑器 API 与文件系统 API

在 Web 应用中处理文件向来是个难题。唯一能让用户选择本地计算机文件的原生方法是使用文件输入类型,而这也非常麻烦,特别当遇到调整Web小部件的UI样式时,情况尤其如此。当用户选择文件时,应用不得不把整个文件都上传到服务器端,以便进行处理。虽然可以利用基于Flash和Java的方案来实现更好的功能,但由于需要安装第三方插件,所以也不算是理想的解决方案。

HTML5家族包括了一些与文件有关的规范,试图在Web应用中将文件操作变得简单。File API 可以让开发者用JavaScript 获取一个文件对象的引用,读取一些属性,如名称、大小以及MIME 类型等。可以用文件阅读器(File Reader)API 来读取文件对象,既能够整体读取也能部分读取。同样,也能用文件编辑器(File Writer)API 将数据输出为文件。文件系统(File System)API 能让开发者在客户端的沙盒封装化的本地文件系统中操纵文件对象。这就能把很多与文件相关的互动都放在客户端执行,极大地节省了服务器端的加载时间。过去,把整个文件都上传到服务器端,仅仅因为发现错误的 MIME 类型,就不得不通知用户文件类型不正确。可想而知,当用户上传完一个大文件后,这样处理是多么糟糕,而我们现在再也不需要这样做了。

你可以使用所有的API来提供一个完整的本地文件系统。第3章将介绍HTML文件到底存在何处。图1-17展示出应该如何使用一些功能。

1.4.4 可伸缩矢量图形

可伸缩矢量图形(Scalable Vector Graphics,SVG)是一种XML 语言,它可以让你使用HTML标记来创建效果惊人的矢量图形,不仅可以利用CSS进行样式编排,并且还能使用JavaScript借助DOM元素进行交互。位图图形的一个主要问题在于,按比例放大其尺寸后,图像质量会降级,产生“像素化”效果。矢量图形则是基于数学公式构建而成,而非由一个个像素点构成,所以即使放大后也能看起来十分美观。

在第7章中,我们将创建一个叫做SVGAliens的游戏(如图1-18所示),学习如何使用SVG来创建一些形状及复杂对象,实现碰撞侦测,了解SVG相比于<canvas>元素的优缺点。

1.4.5 Web Graphics Library

最后再介绍一下Web Graphics Library(WebGL),这是一个利用<canvas>元素来创建3D图形的JavaScriptAPI。它基于Open Graphics Library for Embedded Systems(OpenGLES)标准,这项标准针对包括手机在内的嵌入式设备而设计了一套3D现实方案。借由它所提供的API,开发者可以使用着色器(shader)、缓冲以及绘图方法来实现图形硬件底层控制。

在第9章,我们将学习WebGLAPI 与3D 图形编程,通过创建一个简单的全3D 游戏Geometry Destroyer,来了解创建shader,利用缓冲来处理数据,以及通过矩阵运算将3D数据转变为屏幕上的图形等内容。该游戏画面如图1-19所示。

1.5 小结

HTML5是HTML自1991问世至今最重要的版本。尽管HTML开始只是一种比较简单的标记型语言,却现在却已发展成为一种复杂的网页设计及Web应用开发的重要平台。另外,它和CSS及JavaScript有着非常紧密的关系。HTML5是最先认识到这一关系重要性的语言,并且其规范中囊括了许多的面向Web 应用开发的JavaScript API。

本书接下来的8章将介绍8种不同的应用,从能够离线的移动应用到3D游戏,类型多种多样。第2章将深入介绍HTML5在Web表单上的大量改进,其中包括多种数据类型,以及autofocus和placeholder等新属性,还有一些立即可用的能够简化客户端验证操作的特性。

第二部分 基于浏览器的应用

很长一段时间以来,开发者都在服务器端处理所有事务——表单验证、文件管理、存储、通信,以及其他一些重要的应用功能。服务器端处理可以保证安全性,但却缺少了用户处理能力,而且还会带来很多其他的问题。虽然有很多变通的方法,如Flash或Java这样的技术,但是随着移动设备市场的迅速成长,这些技术却出现了此前不曾预料到的局限性,HTML5的目标正是要弥补这些局限。

多亏了JavaScript处理能力的巨大增长以及W3C的新标准,我们现在才可以通过用户浏览器(即客户端)来执行服务器端任务。在客户端上执行复杂的任务可以节省大量的原本用于服务器端的资金,使得初创企业能够轻松搭建复杂应用,创建看似十分迅速的应用响应。同时,针对Web应用的开发及其在移动设备与桌面上的部署,HTML5技术也开启了一种完全不同的全新的处理思路。如果处理得当,这些任务都可以一并完成。

许多流行的Web应用都用到了HTML5的各种应用特性。例如,Google Drive使用了一种新的存储技术Indexed Database API。你可能使用过HTML5的WebSockets、表单以及这一部分中将介绍的很多其他特性。在读完第二部分后(第2章到第5章),你将完全有能力构建一个服务器端使用率最低的小应用。

*只列出了本章用过或讨论过的输入类型和属性,要想了解更复杂的内容,请访问http://mng.bz/wj56。

查看各节中的标志可迅速定位上表中的各个主题。

第2章 创建表单:输入小部件、数据绑定以及数据验证

本章主要内容

■新的HTML5 输入类型与属性

■data-*属性,valueAsNumber 元素以及<output>元素

■约束验证API

■绕过数据验证的方法

■CSS3 伪类

■利用Modernizr 进行HTML5 特性侦测,利用polyfill进行回退兼容

随着 Web 的日益成熟,用户开始需求更为丰富的表单字段类型及小部件,他们希望能有一种在各种Web应用中都通用的统一标准,特别是当涉及到数据验证时。HTML5充分迎合了这种需求,提供了13种新的表单输入类型,其中包括了数值微调器、滑动条、日期选择器,以及颜色选择器等类型。

为了增强表单的功能,HTML5 标准还定义了一些可应用于<input>元素的新属性,这其中有像placeholder和autofocus这样控制HTML文档呈现方式的属性,也包括required和pattern这样用于验证的属性。现在甚至无需借助JavaScript,就可以用一组新的CSS伪类来定义有效或无效的表单元素。假如无法提供高级验证需求,那么通过新引入的约束验证 API 这一标准化 JavaScript API 的帮助,也能够测试表单字段的有效性。另外,通过一个新的事件,还可以侦测到无效的数据输入。

本站将通过构建一个计算机产品的订单来学习所有新特性的用法。在提交之前,订单将使用HTML5的数据验证来为客户端的数据输入进行“消毒”。

构建本章订单应用的目的

通过学习本章这个简单的实例应用,你将学会以下几项内容。

■如何利用新的输入类型,用更少的代码实现更多的小部件。

■如何利用新的输入类型,用更少的代码来进行数据验证。

■如何利用data-*属性将数据绑定到HTML 元素上。

■如何通过约束验证API特性创建自定义的验证测试。

我们先来看一下这个表单的功能概述,了解一下所需知识。

2.1 表单的功能概述与所需知识

本章所创建的订单如图2-1所示,允许用户输入个人数据、登录详细信息、订购及付款信息。

该表单使用了以下这些HTML5的新特性。

■在适当的时候,利用表单的<input>元素类型(email、tel、number和month)及属性(required、pattern、autofocus、placeholder、max与min),为用户提供更好的部件及数据验证功能。

■保存每一产品价格的data-*属性、valueAsNumber属性并以数字格式读取输入值,以及用<output>元素来呈现金额小计与总计。

■通过 formnovalidate 与 formaction 属性来绕过数据验证并保存未完成的表单。

■通过约束验证API来执行自定义验证,并及时发现用户所提交的表单中所包含的无效元素,利用CSS3伪类选择器处理无效元素的样式。

■利用一些 polyfill 和 JavaScript 的 Modernizr.js 库,让表单更好地呈现在那些不支持HTML5 特性的浏览器上。虽然从严格意义上来说,Modernizr.js 和 polyfill 并不是HTML5的特性,但对于HTML5应用的实际开发来说,还是强烈建议使用它们。

编码完成后,该订单在所有最新版本的主流浏览器上都能正常运行,但在对某些特性的支持上,不同的浏览器可能会存在不同程度的差异,如用于小部件的新输入类型,以及用于约束验证 API 的内联错误消息提示等特性。随着各大浏览器对于新特性支持度的提高,这些问题也会变得无足轻重。

注意 本章只介绍订单应用的客户端部分。因为提交表单会产生一个 URL 请求,所以为了实现更多的行为,需要在服务器端实现表单,这就要用到服务器端语言及架构(如PHP或Ruby on Rails),关于这方面本书暂不做介绍。

构建本章范例应用所需知识点

本章需要跟以下5个文件打交道:

■一个HTML文档;

■一个JavaScript 文件;

■一个CSS 样式表;

■Modernizr 库;

■月份选择器的polyfill 脚本。

CSS样式表和polyfill在本章源代码中,Modernizr库则需要到http://modernizr.com下载。将.js文件重命名为modernizr.js,然后把它和CSS样式表以及monthpicker.js一起放入应用的目录中。

提示 Modernizr 网站提供了两种下载版本:开发版和产品版。开发版包含了整个测试套件,并且没有经过压缩或缩减。如果着急用,而且并不在意文件大小,那么用开发版就很好。产品版则允许选择测试套件,并且经过了压缩,使文件尽量缩小。选用产品版时,一定要把InputAttributes、Input Types以及Modernizr.load测试包括进去,因为本章就要用到它们。本章稍后将对Modernizr库予以详述。

通过概述,并分析所要用到的知识点,下面就先来构建表单的用户界面。

2.2 构建表单用户界面

本节要完成的构建用户界面的任务有:定义HTML文档结构,构建表单的各个部分,以及让用户来决定究竟是保存还是提交表单的细节信息。

本节主要学习内容

■利用HTML5 表单<input>元素类型及属性来提供小部件及数据验证。

■利用data-*属性来保存每种产品的价格。

■使用<output>元素来呈现单种产品金额小计与订单金额总计。

■使用表单属性formnovalidate和formaction绕过验证,保存未完成的表单。

整个UI的构建工作按照以下7步进行。

■第1步:创建index.html 并加载外部文件。

■第2步:创建联系方式明细部分。

■第3步:创建表单的登录明细部分。

■第4步:创建表单的订单明细部分。

■第5步:创建表单的付款明细部分。

■第6步:绕过数据验证,保存表单数据。

■第7步:改变表单在旧浏览器中的行为。

接下来,让我们先从HTML文档开始。

2.2.1 定义表单的基本 HTML 文档结构

先在系统中创建一个新目录,最好能在Web服务器中创建,但对本章这个例子来说,倒不一定非得这么做。

第1步:创建index.html并加载外部文件

在新目录中创建一个index.html文件,然后把代码清单2-1中的代码粘贴进去,这些代码加载了外部依赖文件(CSS和JavaScript文件),定义了<form>元素、顶部的页眉以及底部的按钮。

代码清单2-1 index.html——HTML 文档结构

订单分 4 个部分:本节介绍联系方式细节、登录细节与支付细节,下一节介绍订单细节。

2.2.2 使用表单输入类型 email 和 tel,以及输入属性 autofocus、required和placeholder

在构建订单之前,先来更详细地介绍一下新输入类型及属性,以及如何使用这些类型及属性尽量快速地构建表单。我们将利用 email 和 tel 输入类型以及 autofocus、required和placeholder属性来逐步改善表单。

email和tel输入类型看上去和标准的文本输入元素相同,但如果用户在支持这两种输入类型的移动设备浏览器上浏览页面时,虚拟键盘布局就会根据用户所键入的数据类型而发生改变,如图2-2所示。

对于email输入类型,浏览器还要检查用户输入的内容是否为有效的电子邮件地址。如果是无效数据,当用户提交表单时,就会引发错误。错误类型随不同的浏览器而定,具体呈现方式会有所不同,如图2-3所示。

接下来介绍一下3个属性:autofocus、required和placeholder。

autofocus、required和placeholder属性

autofocus属性的用处很明显:用于定义在页面加载时应该获取焦点的输入元素。对于 required 属性也不难理解,当某个字段必须包含用户输入才能有效时,将用到这个属性。本章稍后将介绍更多的HTML5表单验证方面的知识。placeholder属性则用于定义在某个为空的或者非活跃字段内出现的一小段文本。用户一旦在该字段内输入内容,占位符(placeholder)文本就被清除,代之以用户输入内容,如图2-4所示。

第2步:创建联系方式明细部分

让我们将上述新特性都应用到联系方式明细部分中,具体代码如下面的代码清单 2-2所示,照例应将其添加到index.html中。这些代码应紧跟着代码清单2-1中的<h1>Order Form</h1>。

代码清单2-2 index.html——表单的联系方式明细部分

2.2.3 使用表单输入属性required

登录明细部分是该订单最普通的部分,它要求用户输入两次账户密码以确认输入无误。这里的代码没有用到任何HTML5的新特性,本章稍后将介绍如何使用约束验证API来实现密码确认功能。

第3步:创建表单的登录明细部分

此处,只需将代码清单2-3所示的代码粘贴到上一节完成的index.html代码末尾即可,然后我们会继续介绍一个更有趣的部分。

代码清单2-3 index.html——表单的登录明细部分

2.2.4 使用 number 输入类型与 min、max、data-*输入属性,以及<output>元素来构建具备计算功能的表单

如图2-5中的表单,你可能觉得在订单明细部分中没有用到太多HTML5表单功能吧?未必如此。

订单明细部分中用到了如下几个HTML5特性。

■用于实现数量输入字段的<number>输入类型;

■用于验证quantity输入的min和max属性;

■存储价格数据的data-*属性;

■显示总金额的<output>元素。

number输入类型

在支持number输入类型的浏览器中,该输入类型会显示为一个新的UI小部件:一种数值微调框控件,用户可以通过按动控件旁边的向上箭头按钮来增加数值,按动向下箭头按钮来减少数值,如图2-6所示。

number输入类型还有两个密切相关的新属性:max和min属性。

max和min属性

这两个属性定义了用户在 number 输入字段中所能输入的最大值与最小值,另外,它们还可以定义用于滑块选择器(由range输入类型实现)的最大值与最小值。data-*则是另外一个新的属性,它能自动随着用户输入,轻松地计算出相应的总值。

data-*属性

在本例所要创建的订单中,用户应该能输入每种产品的购买数值,订单应能自动计算出单种产品的总金额以及整个订单的总金额。为了实现这两项功能,首先需要知道每个产品的单价。过去,你可能会在每行中插入一个隐藏字段来保存每种产品的单价,或者通过 JavaScript 数组来保存单价数据,然后再通过查找,获取相应产品的单价。这两种解决方法都不是很简洁。来看看HTML5的data-*属性是怎样解决这个问题的吧!

通过HTML5的data-*属性,任意的键/值对数据都能绑定到某一元素上。JavaScript随后会读取这一数据,继而进行计算并执行客户端操作。

data-*属性的用法很简单:在键前加上data-前缀构成属性名,然后再给它赋值。比如,可以将一个单价数值绑定到数值输入字段上,代码如下所示:

然后,通过侦听字段的改变,将data-price属性值乘以用户的输入值,就得到了单种产品的总金额。稍后再学习如何取回data-price属性值,现在先了解一下本节要介绍的最后一个HTML5新特性:<output>元素。

<output>元素

它的作用正如其名:将输出结果显示给用户。根据某些数据(如用户在<input>元素中所输入的数值)来显示计算结果就是它的一个典型用例。随着我们这个订单项目的不断完善,稍后就会向你介绍如何更新<output>元素中的数值,接下来,先将这些新特性应用到代码上。

第4步:创建表单的订单明细部分

紧接着的代码清单包含了订单明细部分的实现代码。让我们实际应用一下以上介绍的新特性吧。特别要注意的是,对于兼容HTML5的浏览器而言,这些新特性是如何简化编码任务的。只需将代码清单2-4所示的代码直接粘贴在上一步完成的index.html源代码的下面即可。

代码清单2-4 index.html——表单的订单明细部分

2.2.5 使用表单输入类型month 和pattern 输入属性

表单的付款明细部分要求用户输入信用卡详细信息:持卡人姓名、信用卡号、有效期、CVV2 安全码(大多数信用卡的背后都有这个号码)。这些字段将要用到在联系方式明细部分介绍过的HTML5表单特性:required和placeholder输入属性。付款明细部分还要另外使用两个新特性:month输入类型与pattern输入属性。

month输入属性

month类型能让用户从日期选择器部件中选择年月。HTML5 定义了一些日期相关的类型:date、datetime、datetime- local、month、week以及time。目前,在对于这些部件和验证的支持上,大多数浏览器发展得都很缓慢,然而Opera却是个例外,它很早就能非常好地支持这些类型了——当然,它的日期选择器小部件实在是很丑,如图2-7所示。

本章稍后将介绍针对不兼容month类型的浏览器的回退方法,让用户更直观地输入月份,而不是通过一个简单的文本框来输入。

pattern输入属性

pattern 属性能指定一个正则表达式模式来测试字段内输入的数据。在订单中的card_number及card_cvv2字段中,将用这个属性来确保输入的数据是数值类型,并且数值长度符合要求。

使用pattern属性时,可以用title属性来提示用户,让他们了解到该字段所需输入数据的格式。当鼠标指针移动到该字段的上方时,就会显示出一个提示框。如果用户输入了无效值,还需要加上错误信息,如图2-8所示。

第5步:表单的付款明细部分

下面就让我们来添加上述介绍的新特性:利用月份选择器部件来轻松地输入日期,并用pattern属性来定义有效数据模式。将代码清单2-5所示的代码直接添加到上一步完成的index.html的源码中,要紧跟着上一步添加的代码。

代码清单2-5 index.html——表单的付款明细部分

试试看

现在,这个表单应该可以用了。在那些对HTML5新特性支持较好的浏览器中,你能看到这些新的输入类型、属性以及验证功能都生效了。在下一节中,我们将实现暂时保存表单数据(以备后续完善)与立刻提交表单这两种功能,以供用户选择使用。

2.2.6 提交还是保存?由用户来选择——formnovalidate和formaction输入类型

用户有时不可能用一次会话就填完表单,所以需要提供保存当前进程的方法,以便他们后续进行修改。由于用户可能会需要快速跳离表单页面,所以在保存表单时,我们不能强制用户去纠正那些验证提示出错的项目,那样做是不利于用户体验的。只有当表单最终完成并提交时,才应该要求用户去修改验证出错的项目。因此,还需要让用户绕开验证操作。

对表单使用新的 novalidate 属性,我们可以强制整个表单绕开验证操作。只有当我们想使用新的HTML5表单部件,而又不想使用任何新的验证特性的时候,这才是一个不错的方法。绕开验证还有另外一个方法,那就是利用一个单独的按钮来保存当前进程,这就用到了formnovalidate属性,也只有在用到这一属性的时候,才能绕开验证操作。另外,因为要保存而非提交数据,所以还需要改变表单的formaction属性,调用一个不同的URL。在HTML5中,利用formaction属性即可。

第6步:绕过表单验证,保存表单数据

下面就用上述这些新特性来改动一下订单的Save Order(保存订单)按钮。

■在index.html 中找到下面这行代码:

■将它替换成:

■在IE10(或更高版本的IE)中打开订单页面,不填写任何字段。

■单击Submit Order(提交订单)按钮,这时Name(名称)字段就会弹出一个错误消息提示,提示该字段必须填写。

■单击Save Order 按钮,会发现没有进行验证,表单所提交到的URL 是/save,而不是/submit。

很简单,对吧?遗憾的是,不可能这么简单,在不支持这些新特性的浏览器中,这种功能根本无法实现。不过,我们可以用一些JavaScript代码来修复这个问题。

第7步:针对旧浏览器,修改表单行为

在旧浏览器中,这个应用应该也能变换表单行为,当用户提交表单时,应能调用不同于保存数据时的URL。

在index.html所在目录中创建一个叫做app.js的新文件,将代码清单2-6所示的代码添加到这个新文件中。

代码清单2-6 app.js——针对旧浏览器,修改表单行为

如果在不支持formaction 属性的浏览器(如IE9)中打开页面,单击Submit Order按钮,表单会被提交到/submit这个URL 上;单击Save Order 按钮,表单将被提交到/save。你可能会注意到,这中间并没有什么验证,不要着急,稍后将介绍一种回退方案。

进度查看

到目前为止,我们已经创建了表单的几个主要部分:联系方式明细、登录明细、订单明细以及付款明细。对于有些浏览器而言,使用 HTML5 新引入的输入类型 email 或 tel、required输入属性,以及通用的data-*属性和<output>元素,可以减少应用所需的代码。另外,对于另外一项任务,即如何在保存未完成表单时绕过数据验证,利用formnovalidate和formaction这两个新的输入属性,也能在支持它们的浏览器上取得代码简化的效果。

下一节将实现订单明细部分内部所用的计算逻辑,获取用户所输入的数值,计算并显示单件物品以及整个订单的总金额。

2.3 计算金额总计,显示表单输出结果

上一节用data-*属性将键/值对数据和数量字段联系起来,将<output>元素添加到每个产品的购买金额总计和整个订单金额总计上。然而,在现在的状态下,订单似乎并不关心用户在数量字段输入的值,总金额依旧是$0.00。

本节主要学习内容

■如何使用valueAsNumber属性,以数字形式读取输入值。

■如何从data-*属性中获取数据。

■如何更新<output>元素。

本节将使用data-*属性与<output>元素来计算金额总计,然后把结果显示在用户的浏览器上。主要有以下4步内容。

■第1步:添加函数来计算总数。

■第2步:获取输入字段的数量值。

■第3步:获取价格值并分别算出各个产品与整个订单的金额总计。

■第4步:在订单中显示更新后的金额总计。

2.3.1 构建计算函数

下面,我们就来创建在订单中执行计算的函数。

第1步:添加函数,计算总金额

如代码清单2-7所示,从DOM中获取相关字段(quantity、item_total、order_total),在每一个数量字段中建立事件侦听器,只要用户修改了数量值,就能计算出总金额。计算代码并没有放在该代码清单中,本章稍后部分再来添加它。

打开 app.js,将代码清单 2-7 中的代码放到 init 函数中,紧挨着放在代码行“saveBtn. addEventListener('click', saveForm, false);”后面。

代码清单2-7 app.js——计算总金额的函数

接下来我们看一下HTML5新引入的valueAsNumber属性,它能获取input字段元素值的数字形式。

valueAsNumber属性

qtyFields[i]输入元素的value属性,用JavaScript可以读取该元素的当前值,但返回值经常是字符串。虽然用 parseFloat 可以将这种值转变为浮点数,但 HTML5有一个新的解决方法,那就是利用valueAsNumber属性。

比如,当你读取number输入类型的valueAsNumber属性,该属性就能返回一个浮点数。如果将一个浮点数赋予number输入类型的valueAsNumber属性,该属性就会将这个浮点数转换为字符串。

在date和time字段中使用valueAsDate属性

无独有偶,在date与time字段中,同样也存在一个valueAsDate属性,它的作用类似于valueAsNumber属性。当用它获取一个日期相关字段的值时,就会返回一个Date对象。而且与valueAsNumber类似,也可以用该属性将该字段的值设为一个Date对象。

valueAsNumber属性只能用于那些支持number这种新输入类型的浏览器。如果浏览器不支持这种输入类型,要回退到一个常规的文本输入,那就只好用 JavaScript 的parseFloat函数来解决了。所以,对于HTML5方案和回退方案来说,下列语句都可以读取字段的浮点数:

同样,对于修改字段的浮点数值而言,下列语句也是等同的:

那为什么要使用valueAsNumber,而不继续使用parseFloat呢?

现在,你可能要问:“既然 parseFloat 能在所有浏览器上用,那为什么还非得要用valueAsNumber呢?”这是因为,valueAsNumber能够更简洁地实现浮点数与字符串的转换。不过,虽然使用valueAsNumber要比使用parseFloat更有效率一些,但在大多数Web应用中,这种性能的微小提升基本上显现不出来。在 W3C 的邮件列表上,就曾有人质疑过valueAsNumber的有效性。为此,HTML5 编辑lan Hickson 拿出了一个递增字段值的用例,以证明valueAsNumber的确要比parseFloat简洁,代码如下:

第2步:取回quantity输入字段的值

在订单的订单明细部分中,可以使用 valueAsNumber 属性来获取每种产品的quantity字段值。还记得代码清单2-7中那个空的for循环吗?现在就将代码清单2-8所示的代码加进去。

代码清单2-8 app.js——获取quantity输入字段的值

为了获取每个产品的价钱,就需要读取HTML5的data-*属性值,下面我们就来实现它。

2.3.2 获取data-*属性的值

前文介绍了在HTML5中,如何利用data-*属性将键/值对数据绑定到元素上。当你想在元素上添加额外的数据,以便轻松获取或用在JavaScript代码上时,这种数据就显得非常重要了。读取data-*属性的方法很简单:每个元素都有dataset属性,内含该元素所有的 data-*属性。dataset 属性中的每一项都有一个键名,它和该元素标签中的键名一一对应,不过没有data-前缀。代码清单2-4中用data-price属性定义出产品价格,要想取回这个价格值,就需要用下面这行代码:

警告 假如用连字符连接data-*属性名,那么dataset属性就会将它以驼峰命名法表现出来。例如,如果属性名为data-person-name,那么读取时就需要这么写:element.dataset. personName,而不能用element.dataset-name。

dataset 属性是 HTML5 新引入的,还未能被所有浏览器所支持。幸好,有一种适用于所有浏览器的回退方案(对的,你没看错,是所有的浏览器,甚至是 IE6),这就是getAttribute方法。利用它获取data-price属性值的代码为:

第3步:取回各产品价格值,计算单种产品的总金额以及订单总金额

下面,我们就获取每种产品的价格值,将它乘以产品的购买数量,来计算每种产品的总金额,继而得出订单的总金额。将代码清单2-9所示的代码添加到上一步完成的代码中,让它位于for循环的闭合花括号之前。

代码清单2-9 app.js 利用data-*属性获取产品价格

现在,已经计算出了每种产品的总金额以及订单的总金额,只需利用<output>元素将这些数值显示到表单中即可。对于那些支持<output>元素的浏览器来说,向该元素内写入数值,就能从表单中访问它了,比如:

为了更新<output>元素的值,可以如下设定value属性:

对于那些不支持<output>的浏览器该怎么办?

要想在不支持<output>元素的浏览器中使用该元素,需要给该元素分配一个ID,并且使用document.getElementById:

要想更新元素值,可以设定innerHTML属性:

接下来,让我们来更新订单总金额。

第4步:在订单中显示更新的订单总金额

将代码清单2-10所示的代码添加到app.js,让它紧跟步骤3完成后的代码后面,但仍在for循环的结束花括号之前。

代码清单2-10 app.js——利用<output>元素来显示更新后的总金额

试试看

截至目前,每种产品的总金额以及整个订单的总金额应该已经可以正常显示出来了。在现代的浏览器中加载页面,改变每种商品数量字段中的数值,你会注意到两种总金额都会相应地改变,如图2-9所示。

现在,表单可以计算总金额,并且能够验证输入的数据,但如果要添加其他能够显示自定义错误消息提示的验证功能,该怎么办呢?在下一节,我们就使用约束验证API来实现自定义验证,还要利用CSS3来格式化无效字段。

2.4 利用约束验证API来检查表单输入数据

本章开始时曾介绍过一些HTML5的新验证特性——required、pattern、min和max这些属性。通过使用这些属性,无需借助任何额外的JavaScript脚本,浏览器自身就可以验证表单输入字段的数据。然而,这些属性仅仅才是HTML5验证功能中的小角色而已,约束验证API才是主角。

本节主要内容

■如何使用validation属性和方法来设置自定义的验证测试。

■如何使用invalid事件来侦测已提交表单中的无效字段。

■如何使用 CSS3 中新增的伪类选择器为无效字段应用样式,而无需在输入元素中假如过多的类名。

约束验证API所提供的方法与属性可以对指定元素的有效性进行侦测与修改。借助它可以实现其他的验证功能,并且还可以使用自定义的错误消息提示。可以侦测到字段是否存在错误,如果有错误,还可以侦测出错误类型以及要显示的错误消息提示。它还可以让我们自己来设置自定义验证消息,能够被浏览器原生显示出来。

本节将继续开发我们的订单应用,接下来的内容分为两步。

■第1步:为输入元素添加自定义验证与自定义错误消息提示。

■第2步:侦测表单验证失败事件。

尽管本书已经为该例提供了一个完整的 CSS 文件,你完全可以不必自己动手编码,但在本节最后,我们将介绍如何利用CSS3进行无效元素的样式,以便你将来能把这一点也用到自己开发的应用中。首先讨论一下约束验证API的属性与方法。

2.4.1 利用setCustomValidity 方法和ValidationMessage 属性创建自定义验证测试和错误消息

当浏览器或HTML5不支持验证功能时,应用就只好采用自定义验证测试。这种情况下,需要借助JavaScript来验证输入数据的有效性,并在验证失败时提供自定义错误消息提示。约束验证API所提供的setCustomValidity方法和validationMessage属性简化了自定义错误消息提示的实现。两种构建都允许应用将错误消息提示赋予<input>元素的 validationMessage 属性。具体采用哪种构建,则要跟据浏览器对setCustomValidity的支持来判断。

第1步:为输入字段加入自定义验证与错误消息提示

订单应用将利用setCustomValidity方法来执行自定义验证。

■用户全名长度必须至少4个字符。

■密码长度必须至少8个字符。

■“设置密码”与“密码确认”内填入的数据必须一致。

■信用卡号的长度必须至少4个字符。

接下来就为app.js加入这种自定义验证,将代码清单2-11所示的代码加到init函数的末尾,紧跟在对qtyListeners的调用的后面。

代码清单2-11 app.js——执行自定义验证

试试看

如果你用兼容浏览器来打开订单页面,试着打破此前所说的自定义验证规则,那么自定义错误消息提示就会出现,如图2-10所示。

接下来将使用invalid事件,当用户提交的页面中带有一个或更多的字段被标为无效时,该事件就会被触发。

2.4.2 利用invalid 事件来侦测失败的表单验证

当用户试图提交一个使用了HTML5验证特性的表单时,只有当整个表单完全通过验证测试,submit 事件才会触发。如果你需要侦测表单验证何时出问题,可以侦听新的invalid事件,该事件触发的条件只有以下两种。

■用户试图提交表单,但验证失败。

■checkValidity 方法已经被应用调用过,且返回false。

第2步:侦测订单验证失败

为表单的invalid事件添加一个侦听器,将代码清单2-12所示的代码加入上一节所完成的代码后面。

代码清单2-12 app.js——侦听invalid事件

如果想为已提交表单中的错误表单字段添加一种样式,那么invalid事件就很有用了。下面就来学习它。

2.4.3 利用 CSS3伪类来设置无效元素的样式

要想设置无效元素的样式,可以在迭代验证各个字段。一旦发现无效字段,就在该字段上应用CSS类。但这种方法太笨了,稍微用一下CSS3的一些技巧,你完全可以更简洁地实现它。

CSS3 引入了一些新的伪类,可以根据验证结果来设置表单字段的样式。只有当伪类为真时,这些样式才会被应用。下面这些伪类作用显而易见:

■:valid

■:invalid

■:in-range

■:out-of-range

■:required

■:optional

你可能会猜到,伪类简化了无效字段的样式设置工作。例如,对于由约束验证 API宣布为无效的元素,下面的代码会用淡红色的背景和栗色边界框来设置它们的样式。

但这种声明也存在一个问题:订单页面刚加载时,各字段是空的。所以对于那些使用required或pattern验证属性的字段来说,一开始就是无效的,所以那些添加了验证属性的字段,就会都显示出亮红色背景和栗色边框,这太难看了。

幸好,这个问题解决起来并不难,当 invalid 事件已经触发,给父级表单添加一个类,并且为应用到表单中的输入元素及选择器元素的CSS规则中添加伪类选择器:invalid。

注意 不要改变在本章刚开始时加入应用目录的 CSS 文件。本节将逐步介绍理论上你可能会采取的改变,而不是让你真的去修改它们。

上一节中,你为父级表单添加了一个类,现在,在 CSS 中添加一个伪类选择器:invalid。

另外,订单还用:required伪类选择器来为带有required属性的字段设置一种淡黄色背景的样式:

带有required属性的字段与无效字段的样式如图2-11所示。

截止目前,对于大多数现今的浏览器而言(除了 Safari 之外),表单的功能基本上都可以用了。下一节将介绍如何使用Modernizr库来实现绝对可靠的特性侦测,以及如何使用polyfill来填补特性上的空白。

2.5 为不支持HTML5相关特性的浏览器提供回退方案

使用HTML5新特性的一个主要缺点就在于并不是所有浏览器都支持它们。所以你要照顾两个方面:那些最新、功能最强大的浏览器,以及稍微老旧一些的浏览器。

本节主要学习内容

■对于 HTML5 特性,了解 Modernizr 是如何简化了对浏览器支持的侦测操作并加载回退方案的。

■如何利用polyfill来弥补浏览器支持不足的状况,这是一个JavaScript回退方案,只有当所用浏览器缺乏原生支持时,该方案才起作用。

■如何使用JavaScript对那些尚不能完整支持约束验证API的浏览器实现基本的回退性验证。

在逐步完善表单的同时,通过以下3步,你就能了解到以上内容。

■第1步:构建特性侦测,对月份选择器进行回退性处理。

■第2步:针对Safari 5.1,构建回退性的限制验证。

■第3步:针对IE9,构建回退性的限制验证。

但是,首先让我们大概了解一下如何利用Modernizr进行特性侦测。

2.5.1 利用 Modernizr 侦测特性并加载外部资源

在利用HTML5新特性时,有一个很重要的概念,那就是特性侦测:测试浏览器是否支持指定特性。遗憾的是,特性侦测的方法有很多,它们之间的差异性又很大,很难记住每个特性的侦测方法。另一个麻烦是,有时,你可以会需要在浏览器支持(或不支持)指定特性时,才加载某个外部资源。比如说,如果浏览器不支持 WebGL,那么为什么还要加载一个大型WebGL架构呢?同样道理,当用户所用浏览器本身自带一个颜色选择器部件,那我们为什么还非得加载一个颜色选择器部件库呢?动态加载外部资源完全可以做到,但用JavaScript处理起来可没有那么简单。

Modernizr这个JavaScript库可以完美实现特性侦测与资源的动态加载。在Web页面中包含了 Modernizr,就能用十分简单的语法格式来侦测特性支持,比如说,要想查看浏览器是否支持<Canvas>元素,只需用下面代码即可:

如果不使用Modernizr来侦测浏览器的Canvas支持,则需要用下面这些代码:

基于特性测试,利用 Modernizr 动态地加载外部资源也很方便(.js 与.css 都可以)。Modernizr 判断浏览器是否支持localStorageAPI,如果支持,就加载localstorage.js文件,该文件中的代码可能会与localStorageAPI产生相互作用。如果不支持,就加载localstorage-polyfill.js这个回退性方案。

接下来,就让我们看看 polyfill 概念,以及它又是如何能够弥补那些不支持相关特性的浏览器的。

2.5.2 使用 polyfill和 Modernizr 来弥补缺陷

polyfill这个术语是由Remy Sharp 发明的,用来指代那些能够实现API 规范中所缺失部分的代码。该词来源于polyfilla,建筑工人用的一种能够修补墙壁裂缝或缺口的产品。同样,借助Polyfills,Web开发者可以弥合不同浏览器对HTML5特性支持度不一的“缺口”。

提示 Paul Irish,Modernizr 库的主要代码贡献者之一,编辑并维护着一个综合性列表,内容涉及polyfill、shim以及HTML5多种特性的回退方案,等等。访问Modernizr的GitHub wiki可以看到这个列表:http://mng.bz/cJhc。

第1步:部署特性侦测,有条件地为月份选择器部署回退方案

下面介绍如何使用 Modernizr 为没有内建月份选择器的浏览器加载一个月份选择器polyfill。请将本章源代码(可从http://manning.com/crowther2处下载)中的monthpicker.js拷贝到你在本章创建其他文件所用的目录,将代码清单 2-13 所示的代码添加到 init 函数的末尾,直接紧跟着你在上一步骤中所创建的代码下面。

代码清单2-13 app.js——使用月份选择器polyfill

如果浏览器本身并不支持month输入类型,那么加载订单时,标准文本输入框就会被一个带有月份下拉列表和年份选择器的输入字段所替代,如图2-12所示。

大多数的HTML5表单功能都能运用这项技术。实际上,针对目前HTML5整个表单特性在各个浏览器中受支持度不一致的状况,一些项目正在力图弥补这种差异,主要包括以下这些项目。

■Webshims Lib byAlexander Farkas(http://afarkas.github.com/webshim/ demos/)。

■H5F by Ryan Seddon(https://github.com/ryanseddon/H5F)。

■Webforms2 by Weston Ruter(https://github.com/westonruter/webforms2)。

■html5Widgets by Zoltan“DuLac”Hawryluk(https://github.com/zoltan-dulac/html5Forms.js)。

本章最后内容将要实现一些基本的验证,甚至包括那些并不支持约束验证 API的浏览器。

2.5.3 不借助约束验证 API 实现验证

如果在Safari 5.1 或者其他一些浏览器的旧版本(如IE9)上加载这个订单页面,验证功能并不能生效,表单不必经过任何验证就能提交。本节介绍如何使用JavaScript来执行验证,以及在验证发现错误时,如何阻止表单提交。

第2步:针对Safari 5.1,构建约束验证的回退方案

Safari 5.1 对约束验证 API 的支持并不全面。例如,表单中的<input>元素含有required 属性集合,那么该元素将无法在Safari 5.1 中通过验证。但Safari 并不能实现任何 UI 特性,比如它无法显示无效元素旁边紧挨着的错误消息提示;另外,当表单出错时,Safari 也无法阻止表单的提交。下面,我们先来考虑一下如何为用户显示错误消息提示,将代码清单2-14所示的代码放入app.js文件中,让它们紧跟在上一步完成的代码后面。

代码清单2-14 app.js——在Safari 5.1中阻止无效表单的提交

现在,如果用Safari 5.1 加载并试图提交带有无效字段的表单,那么页面就会显示错误消息提示,如图 2-13 所示。而且,无效字段也会以高亮的红色显示出来。这种提示错误的方法并不是最恰当的,实际上,你也许应该学习其他浏览器的处理方式,在第一个错误出现的地方附近显示一个气泡状的错误消息提示。

第3步:针对IE9,构建约束验证回退方案

让我们来看一下本章的最后一个问题。在IE9中,如果有输入字段未能通过早先编写的自定义验证测试,那么在提交表单时,就会看到错误消息提示。这看起来很棒,但是, IE9 并不支持标准的基于属性的验证参数以及 email 输入类型。为了改进这个缺陷,我们需要创建一个函数,用它来扫描表单中的输入字段属性required和pattern,以及email 输入类型。在应用收集到这些字段后,再对这些字段的有效性进行验证。具体实现代码如代码清单2-15所示,将它们添加到上一步实现的代码后面。

代码清单2-15 app.js——IE9的验证回退方案

选择var模式只是为了简洁,而不是因为可靠。设计一种好的模式基于很多情况,这超出了本书的范围。要想使用这个代码,需要在验证表单时,调用 fallbackValidation函数。在 app.js 中找到 validateForm 函数,将下面这个代码段放到 if(order-Form.name.value.length < 4) {的前面。

上面这个代码段利用了Modernizr来测试浏览器是否支持required和pattern属性,如果不支持,则将调用fallbackValidation函数。如果在IE9中运行这个实例,则会看到不仅会产看required、pattern和email,还能看到自定义验证。

对于快速为HTML5应用提供回退方案来说,Modernizr和月份选取器polyfill只是众多工具中的一种而已。你还可以轻松地扩展这些工具,为那些更老的浏览器(如IE6)提供支持(比如可以通过使用jQuery这样的库来处理事件处理器与DOM遍历等任务)。浏览器在HTML5特性支持上的缺陷并不能阻碍我们去使用这些特性——弥合这些缺陷还是挺容易的。

2.6 小结

HTML5提供了很多改进Web表单的功能。新的输入类型(如email与tel)为开发者提供了更多的部件,且实现方法更为简洁。对于数据验证来说,在不借助 JavaScript的情况下,利用新的输入类型(如 pattern)可以完成许多验证任务。更为令人欣喜的是,约束验证API也大大简化了自定义验证测试与错误消息提示的开发工作。更不用说, data-*属性让数据与HTML元素的绑定也更为高效了。

遗憾的是,现今各家浏览器对于实现这些特性的开发进展相对缓慢,造成了目前HTML5在各种浏览器上支持度参差不齐的情况。虽然就近期而言,HTML5先进的表单特性在具体浏览器实现上的缓慢与局限无法得到迅速缓解,但这并不应该令我们驻足不前,在Web应用上放弃使用HTML5表单特性。一旦拥有了Modernizr这样先进的特性侦测工具以及polyfills列表,开发者完全可以将HTML5的表单特性高效地用于Web应用开发之中。

虽然本章所讲的这个订单应用实现了保存特性,但它却不能将表单保存在客户端,而只能将其保存到服务器端。将表单保存到客户端本地系统无疑是一种较好的方法,能够为用户提供更快的反馈,另外也基本上(或根本)不需要占用服务器端资源。第3章就将介绍这种技术,我们将利用File System API 在客户端本地系统创建并保存文件。

除此之外,第3章还会将到表单的高级编辑功能,这将用到Editing API 与Geolocation API。有些表单不仅仅能让用户填入简单的数字与姓名,还能实现更为高级的功能。比如,在博文编辑器中,有时需要为输入的文字提供特殊的格式(如粗体与斜体等)。Editing API的构造非常强大,可以快速地创建出这种富媒体支持。另外,如果网络上的作者需要在博客或其他文章上插入地图,那么我们可以利用 Geolocation API 为他们提供本地化的地图服务。

查看各节中的标志可迅速定位上表中的各个主题。

第三部分 交互式的图像、媒体及游戏

通过HTML5的交互媒体API,如Canvas、SVG、Video以及WebGL,现在可以无需插件的支持来创建图像、媒体播放器和游戏。假如接触过 YouTube 的HTML5 视频播放器和WebGL版本的Google Maps,那么你可能已经用过这些技术了。一些公司已经在利用这些技术来开发游戏引擎了,比如像Ludei的CocoonJS引擎,以及Goo Technologies的Goo引擎。如果学完了这一部分内容,你就可以无需利用插件来开发交互式应用了。

跟Flash、Unity以及Silverlight 这样的RIA插件(Rich Internet Application plugin)相比,HTML5的交互媒体API又有那些优势呢?上述这些RIA系统虽然更为成熟,但在移动开发上却经常受到限制,或者需要开发原生应用,或者需要经过一番转换。但若利用HTML5 技术,你可以创建一个在移动设备和台式机的浏览器上都能运行的游戏(这说的是理想状况,而非目前实际情况)。实际上,HTML5 API 目前在移动应用上仍有很多限制,详情可查看caniuse.com。有些人反驳说RIA插件可以提供密性更好、更安全的Web应用,事实确实如此。然而,越来越多的需求开始青睐无需RIA插件的解决方案。

交互媒体API的重要性何在?对于前端及一些移动开发人员来说,它们是非常重要的工具,许多公司都正开始雇佣 Canvas 专家。笔者之一所就职的电商网站已经把大多数以前用Flash开发的项目转而用Canvas技术来开发。实际上,由于目前仍满足不了顾客的需求,所以他们仍在招聘这方面的人才。我想说的是,这方面的技术能让你在就业市场中更为抢手,并能提高个人的长期价值。

查看各节中的标志可迅速定位上表中的各个主题。

第6章 2D Canvas:底层 2D图像渲染

本章主要内容

■Canvas基础知识

■形状、路径以及文本的创建

■创建动画

■碰撞侦测

■从零开始利用HTML5 创建Canvas游戏

在过去很多年中,开发者们都在使用 Adobe 的 Flash 去创建具有高度互动性的 Web应用。遗憾的是,当智能手机问世以后,面对随之而来呈现爆炸式发展的移动应用市场, Flash 却没有做好应对措施。随后,HTML5 的Canvas API 的出现,彻底终结了Flash独霸天下的地位。Canvas能在一个DOM元素中创建2D形状,无需任何插件的支持。Canvas可实现真正的跨平台应用,并且能结合各种框架(如PhoneGap)进行开发。Canvas不仅操作简单,而且还能实现各种复杂应用,比如模拟医疗培训过程、创建交互式的游说演讲,甚至开发教育应用,等等。

能用Canvas绘制图表和信息图吗?

针对 Canvas,有一个常见的误解,认为它并不擅长创建图表和信息图。虽然可以用它对简单的信息进行可视化,但 Canvas 更擅长构建复杂动画及交互效果。如果想实现简单的可视化或动画,可以用SVG技术来实现(详情参看第7章)。利用SVG,可以创建Logo图标、图表以及信息图,另外它还有很多 Canvas 所缺乏的内建功能,比如动画、可缩放性,以及支持CSS,等等。

本章首先介绍如何实现一个简单的引擎模式来保存并绘制图形,以此范例来介绍Canvas API 的基本知识。随后,创建一些独特的形状,并以它们为素材制作动画。最终,以本章范例为基础进行演绎,你完全可以完成大型动画、交互式数据或绘画应用等项目。不过,我们究竟为何要学这么多技术和原理?当然是为了创作游戏!

本章特色

在网上有很多类似于 Canvas Ricochet的 Canvas 教程,但本章介绍得显然要更为深入,有一些网上免费教程不可能涉及的特色内容:

■Canvas API的高级用法(渐变、路径、弧形,等等);

■游戏能够保存得分,且关卡难度会不断增加;

■利用一种Canvas 设计模式来实现全功能应用。

我们将创建一个名叫 Canvas Ricochet 的反弹球游戏,其中包含了动画元素、碰撞侦测、利用键盘/鼠标/触摸来控制游戏等功能。我们先实现这些功能,然后将它们融合起来,创建一个功能齐备、体验出色的游戏,不仅能记录得分,而且游戏难度也会随着关卡的增加而增加,另外还包括游戏的开始与结束界面。反复优化游戏,不仅能使游戏体验变得出色,而且最终肯定能让开发者获得良好的资金收益。

学完本章后,相信你就能利用这些知识来从头开发自己的 Canvas 应用了。下面首先介绍Canvas的绘图环境(content)。

6.1 Canvas基础知识

无论创建哪种类型的Canvas应用,前两个步骤都跟Canvas的绘图环境有关,即设定绘图环境与生成绘图环境。没有绘图环境,就无法进行绘制。除了这一点之外,你还需要确认当前所用的浏览器是否支持Canvas。

6.1.1 设定 Canvas 绘图环境

在使用Canvas之前,你必须借助JavaScript从API中选取一些绘制工具(这就是所谓的设定绘图环境)。跟大多数HTML5的技术一样,必须要用JavaScript来编写一些调取API命令的代码。最常见的绘图环境是用来绘制二维平面的。如图6-1所示,这是一款叫做Sketchpad的性能健壮的绘画应用,使用了Canvas内建的绘画工具。我们先介绍绘图环境的一些知识,然后就来设定本章游戏范例所用的绘图环境。

除了2D绘图环境,还可以选用3D绘图环境。尽管3D绘图环境能够创建高级应用,但并非所有浏览器都能够支持这一功能。利用3D图形学知识和JavaScript,就能创建出交互式的3D应用,如图6-2所示的那种音乐录影带应用。关于3D图形学知识,我们将在第9章介绍WebGL时再详细探讨。

Canvas:苹果 Mac OS X系统的一个产物

Canvas 不是W3C制定HTML5规范的产物,它最早出现在2004 年,是作为苹果的Mac OS X Webkit 引擎的一部分而诞生的。两年后,Geoko 和 Opera 这两个浏览器都采用了这一 API。随后,各大浏览器都纷纷加以采用,从而使之成为现在HTML5的一种官方API。

2D 环境非常适于开发简单游戏,所以Canvas Ricochet就将架构在2D 绘图环境中,并利用到了JavaScript和HTML。在开发这个游戏的过程中,我们将逐步介绍 Canvas的使用知识。稍后你就会发现,在开发过程中,很多时候都会利用JavaScript来访问各种绘制工具,并将某个对象发送给CanvasRenderingContext2D接口对象。虽然这个接口对象的名字听起来很长,而且也很奇怪,但它确实能访问 Canvas,并进行绘制。每个新绘制的形状都位于先前绘制的形状的上面。

必备条件 在开始学习下面内容之前,可以先去下载本书范例的源文件:http://www.manning.com/crowther2/。还可以在http://html5inaction.com/app/ch6/中找到本章所用的源文件,测试一下这个游戏,看看它到底有哪些好玩的功能。

我们绘制的任何东西都位于一个由<canvas>元素包含的简单坐标系中。如图6-3所示,这个坐标系看起来似乎就是一个很平常的笛卡尔坐标系,但还是有一点差别的,该坐标系的原点位于左上角,y 轴方向是向下的,当你向下移动时,y 轴坐标值就会增加,而不像平常我们所熟悉的笛卡尔坐标系那样减少。

6.1.2 生成 Canvas 绘图环境

虽然利用JavaScript获取了绘图环境,但你必须得把它从<canvas>元素的DOM 数据中取出。

为游戏搭建Canvas基本环境

随便用一种文本编辑器创建一个 index.html 文档。在<body>元素中放置一个<canvas>元素,带有id、width、height等属性,如代码清单6-1所示。如果不通过HTML、CSS或JavaScript来声明尺寸信息,Canvas就会接受浏览器默认指定的宽高尺寸。可以把任何内容都放进<canvas>标签内,在最终渲染时,这些内容就会被清除。创建一个空的game.js文件,把它放在index.html文件的旁边。

代码清单6-1 index.html——默认的Canvas HTML 文档

验证浏览器是否支持

刷新一下浏览器,<canvas>元素内嵌的所有文本都消失了。<canvas>元素一经渲染,其中所有内容都将被清除掉,所以可以把一些内容和消息都放在<canvas>元素内,从而使不支持<canvas>的浏览器能显现这些内容,以便提醒用户。

可以从<canvas>中读取Canvas API 的绘图环境,并将其存入一个变量。渲染Canvas元素的代码如下所示,下一节会详细讲解它的具体实现:

Canvas 的绘图环境元素可以很方便地定义 2D 绘图,还可以用于功能侦测。只需将context变量封装到一个if语句中,它就会检查canvas变量是否有getcontext方法。以下就是基本的Canvas功能侦测语句。

上面的if语句检查了getContext与getContext('2d'),这是因为对于有些移动浏览器,在执行 getContext 检查时会返回 true;而在执行 getContext('2d')检查时,则会返回false。

注意 IE7 和 IE8 在使用 Canvas API 命令时会崩溃,可以换用 explorercanvas 来解决这一问题(http://code.google.com/p/explorercanvas/wiki/Instructions)。打开上述网页,下载并解压缩excanvas_r3.zip,将其中的excanvas.js放到根目录,在一个额外指向IE的注释中添加一个script元素,用来加载excanvas.js。IE9 对这一脚本支持得很好,而目前看起来,IE10 对它的支持也非常好。我们的意见是:利用它,你可以实现简单的动画,但却实现不了更高级的功能(比如本章的Canvas Ricochet这样的游戏)。

现在,我们设置好了 index.html 文件,并探讨了 Canvas 的绘图环境,下面就来开始创建Canvas Ricochet游戏。

6.2 创建一个Canvas游戏

如图6-4 所示,这就是你要创建的第一个 Canvas游戏,在创建它的过程中,将会用到碰撞侦测、动画技术、键盘/鼠标/触摸控制等技术,最后还要对它进行一番优化。

虽然听起来,碰撞侦测和高级动画技术有些吓人,但我们会逐步讲解它们的实现过程,根本不需要你事先了解以上技术的相关知识背景。

本节主要学习内容

■使用Canvas API,动态绘制正方形和圆形,并利用特殊的着色技术对它们进行着色处理(实现纯色与渐变色效果)。

■介绍简单的可视化编程概念。它们也可以用到其他的编程语言中。

■使用Canvas API绘制一个图形。

本节将通过以下7步来创建主游戏引擎及游戏的可视化资源。

■第1步:创建游戏引擎的主要部分。

■第2步:创建专为HTML5而优化的动画。

■第3步:显示背景图像。

■第4步:计算矩形砖块的宽度与高度。

■第5步:对砖块进行着色处理。

■第6步:创建小球。

■第7步:创建球拍。

6.2.1 创建游戏引擎的主要部分

下面将把所有的JavaScript代码都放到一个自执行的函数中。为什么要这样做?因为这能防止变量名出现在全局范围而与其他文件的代码产生冲突。

有关 HTML5 Canvas 的其他一些学习资源

继续学习之前,强烈建议读者参考下面两份资源。首先是nihilogic 的HTML5 Canvas Cheat Sheet(http://blog.nihilogic.dk/2009/02/html5-canvas-cheat-sheet.html)。第二个就是WHATWG 的 Canvas 元素文档( http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html),这份文档提供了详尽的Canvas元素内在工作原理,它不仅有益于浏览器厂商去实现相关标准,也有助于开发者们深入利用Canvas元素去开发更好的应用。

第1步:创建游戏引擎主要部分

将代码清单6-2中的代码添加到game.js中。这些代码创建了一个Canvas游戏引擎对象,这个JavaScript对象并没有声明任何变量和函数,而是使用了方法(作用等同于函数)和属性(作用等同于变量)。例如,可以通过var bricks = {count: 20, row: 3, col: 2 };来规定页面上砖块的数目,然后再通过调用bricks.count来获取砖块数目。关于 JavaScript 对象的用法详解,可参考 https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Working_with_Objects。

代码清单6-2 game.js——基本的JavaScript代码

注意,这里把Game.setup()代码放在window.onload内。后者的作用是,只有当 index.html 完全加载完毕后,才会让浏览器执行 Game.setup()。如果其他必要资源(如库)没能及时加载,运行Canvas代码就会很容易导致游戏崩溃。

第2步:创建专为HTML5优化的动画

绘制之前,先来设置动画。但有一点需要注意:Canvas需要依赖JavaScript计时器,因为动画并不是它的内建功能。要想创建动画,就需要使用计时器来周期性地绘制图形。通常可以使用JavaScript的setInterval()函数,但这样做并不能实现最佳的用户体验。setInterval()适合运行方程式或执行 DOM 操作,并不适用于计算密集型的动画循环。

浏览器厂商提供了一个JavaScript函数requestAnimationFrame(),可以把帧数显示在用户的电脑上(https://developer.mozilla.org/en/DOM/window.requestAnimationFrame)。不过,并非所有主流浏览器都支持这个函数。为此,Paul Irish 开发了一个 polyfill,能实现各浏览器对该函数的统一支持(http://mng.bz/h9v9)。

如何控制帧数的波动

requestAnimationFrame()在显示每秒帧数方面并不稳定,它会根据计算机的处理能力来动态地显示当前每秒帧数,因而会使显示的帧数在1~60fps间波动。如果它返回的帧数小于 60fps,那么像 x+=1 这样的运动逻辑就会导致动画产生撕裂感,时快时慢,这一切都是因为帧数波动所造成的。如果想让动画以固定的帧数进行播放,可以采用两种办法。

一种方法就是在setInterval()中设置逻辑更新,在requestAnimationFrame()中设置绘制逻辑。第二种方法则要更好一些,创建一个增量,然后将所有的移动值都乘以这个增量(比如说:x+=1*delta)。如此一来,动画的帧数就能保持一致了。关于如何自定义设置增量,可以参考一下这个资源:http://creativejs.com/resources/requestanimationframe/。

利用代码清单6-3所示的代码将动画集成进游戏引擎:紧挨着Game对象,直接在其上面添加 window.requestAnimFrame。然后将一个用到 requestAnimFrame()的新方法添加到已有的Game对象上。

代码清单6-3 game.js——制作Canvas Ricochet动画

注意 要注意上面代码中的两个重点。首先,如果一个代码段重复声明一个对象属性,那么就应该替换掉已有的代码。比如说,var Game =中的新方法应该添加到已有的Game对象中。不用担心这样做会修改对象,在可能会修改或替换某个对象时,我们都会及时提醒你的。其次,在制作动画时,有些人不用空白的矩形来清除Canvas内容,他们通过设置一个新的宽度,来清空Canvas绘制区域。虽然这样做显得很高明,但会导致动画播放的不稳定,所以强烈建议使用空白的矩形来清除之前已绘制的动画帧,而不要利用经常改变宽度。

第3步:显示背景图像

利用代码清单6-4所示的代码更换background对象,以便显示出一副背景图像。为了实现这一点,必须将本章源文件中的background.jpg复制到game.js所在目录。

代码清单6-4 game.js——基本JavaScript

这就完成了游戏引擎的主要部分,接下来创建游戏的可视化资源。如果你之前没用编程语言(比如C++)创建过可视化资源,那么可能会觉得下面有些代码理解起来比较困难。不过,一旦你最终理解了这些代码的运作机制,你就会明白有关2D可视化编程的一些基本原理,也就很容易用其他语言来实现相关功能。

6.2.2 创建动态的矩形

砖块不过就是一个个的矩形,它们很容易创建。如图6-5所示,Canvas中的矩形对象能够被清除、填充、添加轮廓。该对象一共有4个参数,前两个参数规定了矩形的绘制起始位置(x与y坐标值)。虽然当前的视图空间只显示x与y的绝对坐标,但可以用负值来定义形状的绘制起始位置。后面的两个参数分别规定了矩形的宽度与高度(以像素计)。

第4步:计算矩形砖块的宽度与高度

要想知道每个砖块的宽度,需要进行一番计算。在游戏中,需要一行摆 5 块砖,每块砖之间要留出 2 像素的缝隙(4 个砖缝 × 2 像素= 8 像素)。这些砖块需要放在宽为 408像素的<canvas>标签中。这个宽度是此前我们在 index.html中用 HTML标记定义好的。从408像素的总宽度中减去砖缝的总长度8像素,就是5块砖实际应该占用的宽度,因此每块砖的宽度为80像素。可能这样用文字描述不太容易让人明白,所以请参看图6-6。

要想将所有砖块布置到位,其实只要反复重写一个基本形状命令就能做到。但那样太笨了,我们先创建一个二维数组来保存每块砖的行号与列号,如代码清单6-5所示。接下来,通过遍历数组,将每块砖按照其相应的行列号数据进行摆放,就能达到完美布局的效果。利用代码清单6-5中的代码来修改Bricks对象。

代码清单6-5 game.js——创建Brick数组

为什么不用盒模型(Box Model)

如果用过 CSS,那么你可能会熟悉盒模型这个概念。它能确定 HTML 元素的布局与定位。Canvas 不使用这种方式,形状不会随着其容器尺寸比例的变化而变化。相反,形状有时会溢出容器,例如,一行很长的文本就并不能自动适应<canvas>标签的宽度与高度。另外,Canvas也不能使用CSS,只能手动用JavaScript来编写所有可视化输出代码。

第5步:给砖块着色

砖块建立好了,但它们现在是不可见的,需要给它们进行着色处理,才能将它们显示出来。我们来创建一个线性渐变(在笛卡尔坐标系下的两个定义好的点之间改变颜色)。代码清单6-6所示的代码将使用switch语句,根据砖块所在的行号来进行着色。将新的gradient与makeGradient方法添加到已有的Bricks对象上。

代码清单6-6 game.js——对砖块进行着色

砖块已经做好了,下面来做小球。

6.2.3 创建弧形与圆形

小球其实就是个圆形,我们使用arc()函数绘制圆形。arc()的具体格式为arc(x, y, radius, startAngle, endAngle),如图6-7 所示。绘制矩形时,起始点在坐标系的左上角,而绘制圆形时,arc()的起始点在所绘圆形的中心。你需要提供这个圆形的半径(以像素计),还有起始角度和结束角度(以π也就是pi计)。起始角度一般为0π,由于我们绘制的是整圆,所以结束角度为2π,如果是半圆,则为1π。

第6步:创建小球

学习了arc()的用法后,利用代码清单6-7所示的代码来创建小球。利用这些代码修改已有的Ball对象。

代码清单6-7 game.js——创建小球

接下来,让我们来创建球拍。

6.2.4 利用路径来创建复杂形状

创建球拍需要结合使用 Canvas 路径与多个弧形及线段来完成。这个过程挺复杂的,因此我们将分步介绍,步骤如下所示。

1.创建一条路径。所用命令:ctx.beginPath()。

2.移动这条路径。所用命令:ctx.moveTo(x, y)(可选方法:重绘出移动后的路径)。

3.绘制所需的一些线段。所用命令:ctx.lineTo(x, y)。

4.关闭当前绘制的路径,所用命令:ctx.closePath()(可防止出现一些未知的绘制行为)。

5.根据需要,反复使用第2步与第3步。

6.设置线段颜色。所用命令:ctx.strokeStyle或ctx.fillStyle。如果不自定义颜色,则游戏使用浏览器默认颜色。

7.利用ctx.stroke()填充路径。

除了使用lineTo 命令外,还要使用arcTo(x1, y1, x2, y2, radius)命令来为球拍创建曲线。

注意 arcTo()在Opera V12.01 中表现得不是很稳定。它倒不会让游戏崩溃,而是会让球拍显得不怎么真实。IE9则要求在arc()之间再声明一个额外的lineTo(),否则,球拍就显得像是随意放置的几根线而已。一般情况下,可以不用声明这个lineTo(),生成的弧形也不会显得散碎。

第7步:创建球拍

球拍是最后一个可视化资源。代码清单6-8所示的代码将会利用4个弧形来创建一个胶囊的形状,并利用渐变色填充该形状。将下面的方法及属性都添加到已有的Paddle对象中。

代码清单6-8 game.js——创建球拍

阶段性测试

现在,所有静态的可视化资源都创建好了。如图6-8所示,这就是现在的游戏画面。如果显示的效果跟这幅图有差别,则先检查一下浏览器是否升级到最新版本;如果根本不显示,则检查一下Game对象是否正确,然后再逐个检查一下那些没有正确输出的对象。

这些东西就是 Canvas Ricochet 游戏所用到的主要可视化资源。但游戏现在还没法运行,下一节讲讲如何让游戏由静至动。

6.3 使Canvas元素动起来

目前,这个游戏还只是个花架子,它没有任何功能。下面介绍如何使游戏元素动起来,侦测是否发生碰撞,以及如何利用键盘/鼠标/触摸控制来移动球拍。

本节主要学习内容

■如何实现在屏幕上移动对象。

■如何创建重叠对象间的响应。

■如何阻止移动的对象脱离<canvas>边界。

■如何从游戏中清除对象(砖块)。

■如何为游戏建立键盘、鼠标及触摸控制。

■如何结束游戏。

本节的任务可分为两组来完成。

6.3.1 使游戏元素动起来

先来完成前几步,先让球拍能够水平移动,再来让小球实现弹跳。

Canvas数据处理

当利用JavaScript来进行Canvas绘图时,浏览器会重建所有的可视化资源,因为它使用的是位图技术。创建位图图形也就是将图像数据保存在有组织的数组中。当计算机处理这些数据时, Canvas就会将一个个像素再组合成一幅图像。这意味着Canvas的记忆容量要比一条金鱼还小得多,所以无论什么东西,它都会重绘。

Canvas为什么总是重新绘制图像呢?同样令人疑惑的是,苹果公司当年为什么放着SVG(可伸缩矢量图形)这种无需不断重绘图形的技术,而选用了这样一种位图技术呢?尽管 SVG 有诸多优点,Canvas现在则更受欢迎。或许,开发者对于SVG的关注和了解度不够也是造成这种局面的原因之一。

第1步:使球拍水平移动

要想使球拍水平移动,其实就是在每次绘制球拍时,调整它在 x 轴上的坐标即可。x坐标值为正数,代表球拍向右运动;若为负数,代表球拍向左运动。上文中,我们在init()中创建了球拍速度属性 Paddle.speed,并定义其值为 4。现在,把下面这个代码段放入Paddle.move()方法中,球拍应该就能移动了吧?

刷新下页面。哦,怎么回事?球拍一下子就飘走了。这是因为它的运动没有限制。要想它不消失,需要加一个碰撞侦测功能,后面我们再说这个问题,先来看看如何让小球动起来。

第2步:使小球运动起来

小球的运动原理和球拍的一样。利用之前声明的两个属性Ball.sx和Ball.sy来改变x与y坐标。用下面的代码段来替代Ball.move():

和上一步球拍的运动效果相同,一刷新页面,小球就慢慢飞出了游戏界面。不要灰心,这都是因为代码中缺少碰撞侦测功能,我们下面就来实现这个功能。

6.3.2 碰撞侦测

对于简单的2D游戏,可以用碰撞点来侦测游戏对象间是否发生碰撞。每隔一段时间进行一次检查,并将状态更新后的对象进行重新绘制。在碰撞侦测机制的作用下,如果对象间发生碰撞,就产生反馈。比如说,小球和球拍出现碰撞,那么其中一个对象就会排斥另一个对象。

真实的游戏物理效果是怎么样的?

遗憾的是,本书只能教你如何侦测形状的碰撞,这并不是真实的物理效果。物理效果的编程实现是一门大学问,用100本书来讲都讲不完。如果你对此十分感兴趣,想让自己的游戏做得更真实,可以看看 Glenn Fiedler 的关于游戏物理效果的文章,他讲得很透彻,而且也不过时,参见:http://gafferongames.com/game-physics/。

下面就为游戏加上碰撞侦测功能,使游戏对象始终位于游戏界面之中。约束好游戏对象的运动边界之后,再对小球的运动做出设定。一旦小球从砖块上弹回,就对小球和球拍进行碰撞侦测。最后,定义一下游戏的结束条件。

第3步:小球和球拍的边界侦测

为防止小球与球拍飞出游戏界面,需要对比它们的实时坐标与<canvas>的宽高,后者存储在Game.width与Game.height中。

用下面的代码段替换掉 Paddle.move()方法中的内容。这个代码段会判断球拍坐标是否为正值,以及球拍是否还在可玩区域中。如果以上两项条件都满足,Paddle.x进行更新,如不满足,就会强制球拍回到可玩区域。

为了防止小球的运动破坏游戏机制,规定如果小球与<canvas>边界发生碰撞,就翻转小球的运动方向。除此之外,还必须让小球回到可玩区域之内,否则的话,小球会“粘”在边界上面,并高速运动。利用代码清单6-9所示的代码替换Ball对象中的edge(),可实现小球排斥游戏边界的效果。

代码清单6-9 game.js——小球碰撞侦测

第4步:碰撞侦测

游戏机制需要让球拍将小球反弹向砖块。小球在与球拍碰撞时会偏转方向,球拍则是相对静止的。因此,代码清单6-10所示的代码在Ball.collide()方法中加入了偏向逻辑。当小球的x与y坐标显示小球与球拍发生重叠时,将小球的y坐标值取负,这样就能让小球以对称但却相反的方向弹起。将Ball对象collide()方法用下列代码替换掉。

代码清单6-10 game.js——小球接触球拍

第5步:清除被小球击中的砖块

当小球击中砖块时,砖块应该消失。因此,我们利用下面的代码来替换Brick.draw()。下面的代码会判断小球是否与当前重绘的砖块发生重叠。如果发生重叠,则对小球的y坐标取负,并将该砖块的在砖块数组中的数据设置为 false,就能在游戏中将其清除。利用代码清单6-11所示的代码添加一个新的Bricks.collide()方法。

代码清单6-11 game.js——去除被小球击中的砖块

现在,球拍能把小球弹回砖块了。不过,现在玩家还无法控制球拍。下一节将介绍如何通过神奇的window事件来实现通过键盘、鼠标及触摸的方式来控制球拍的功能。

6.3.3 实现键盘、鼠标及触摸控制

为实现交互式游戏体验,利用键盘、鼠标及触摸来进行控制是必不可少的。虽然已经在Game对象中创建了控制器侦测,但为了避免混淆,还要另外创建一个独立的Ctrl对象。以下是该节任务的具体实现步骤。

■第2组任务:获取用户输入。

♦ 第1步:创建键盘侦听器。

♦ 第2步:添加鼠标控制。

♦ 第3步:添加触摸控制。

♦ 第4步:通过HTML来添加控制信息。

首先为左右箭头键添加键盘控制器;其次创建鼠标侦听器,以监视鼠标指针运动,将球拍的位置与指针位置实时对应;最后,创建触摸控制功能,使游戏适用于那些支持W3C触摸事件草案 [1]的设备。做好这些功能之后,我们再来讲讲一些输入技术上的最佳实践,以便改善用户体验,

第1步:创建键盘侦听器

要想侦测键盘事件,需要用到一个能够监控按键是否按下与释放的方法,用它来修改已有的 Ctrl 对象,如下面代码所示。这就能使球拍左右移动了。注意,代码清单 6-12中的Ctrl.init()会被Game.setup()调用,从而触发输入监控。

代码清单6-12 game.js——键盘事件侦听器

如果你想试试效果,可刷新一下页面。这时你会看到,球拍并不响应输入命令。由于Ctrl.left和Ctrl.right属性保存着键盘输入,Paddle.move()需要利用下面的代码段来引用这些属性:

更多的键盘代码

如果你想了解更多的键盘状态侦测技术以及完整的键控代码,可以看看 Jan Wolter 的文章“JavaScript Madness: Keyboard Events”(http://unixpapa.com/js/key.html)。

第2步:添加鼠标控制

监控鼠标移动与监控键盘的原理基本上差不多,只不过需要考虑 Canvas 元素在页面上的位置,以及与鼠标指针位置的互相参照。为了获取当前鼠标指针的位置,需要修改Ctrl.init(),并添加一个新的movePaddle()方法,如代码清单6-13所示。

代码清单6-13 game.js——鼠标控制

第3步:添加触摸控制

添加触摸控制只需要再添加6行代码就可以了,而且根本不用修改已有对象。只需要将代码清单6-14所示的代码添加到Ctrl对象中即可。

代码清单6-14 game.js——触摸控制

注意 如果设备不支持触摸事件,那也没关系,不受支持的事件会被忽略,游戏会照常运行。你可以拿各种移动设备来试验,但我们不保证每种设备都能正常运行这个游戏。

6.3.4 控制输入需要考虑的一些问题

在过去几年,针对应用和网站的JavaScript键盘支持技术获得了长足的进步。YouTube、Gmail以及其他一些流行应用都通过键盘快捷键来提高用户的操作效率。尽管加速交互是一件好事,但也容易产生严重的可用性问题。

在用JavaScript声明按键时一定要小心,因为有可能会覆盖浏览器默认的快捷键,使复制、粘贴等基本功能变得无效,甚至可能会意外地关闭浏览器。最好的方法是将范围缩小到箭头键和字母键。一些特殊按键(比如空格键)是可以用的,但最好不要用Shift键、Mac/Windows 徽标键以及Caps Lock键,因为覆盖这些键会引发不可预料的后果。如果必须要使用某种组合键或特殊键,先考虑一下这样做是否会给用户带来麻烦。

用户肯定不喜欢开始时没有操作提示的应用。一定要把游戏的操作说明写得清晰简洁,且要把它放在容易找到的位置上,比如直接放在游戏界面中就是一个很好的法子。

第4步:通过HTML添加控制信息

现在为 Canvas Ricochet 这个游戏添加一个控制说明。直接在<canvas>下添加一个<p>标签,添加“LEFT and RIGHT arrow keys or MOUSE to move.”(利用左右箭头键或鼠标进行控制)这样的说明文字。如果有必要,可以制作一张说明图,那会更易于玩家了解,但为了简便,我们只使用文本。

祝贺你构建了一个完整的HTML5游戏!所有的关卡都可以玩了,没有丝毫问题。我们花了很大的精力把游戏做成现在这个样子,为何不继续对它进行一些优化呢?只需再努力一下,我们就能把游戏关卡的难度逐渐增加,并且添加上好看的游戏界面。

6.4 优化Canvas游戏

从技术上来说,这个游戏已经做完了,但为了吸引玩家,它还缺少一些优化。有必要为它增加一些容易使人着迷的游戏元素:计分板、关卡难度不断增加、令人愉悦的用户体验,等等。这些游戏元素能增加游戏的收益,吸引更多玩家的加入,更重要的是,能让人们一直玩下去。

本节主要学习内容

■如何实现并维持玩家的得分。

■如何集成得分的社交分享功能。

■如何消除安全隐患。

■如何整合关卡系统。

■如何增加一个游戏介绍与游戏结束界面。

■如何选择一款Canvas 游戏引擎。

我们将通过以下4 个步骤来优化Canvas Ricochet游戏。

■第1步:创建一个得分及关卡记数器。

■第2步:在线存储最高得分(可选功能)。

■第3步:创建一个欢迎(Welcome)界面。

■第4步:创建一个游戏结束(Game Over)界面。

首先我们添加了得分系统以及Facebook得分榜(可选功能),然后再来调整一些代码,实现动态的关卡难度系统。随着玩家操作水平的不断提高,他们会更加努力、更加迅速地去玩这款游戏。然后,我们会制作精美的游戏欢迎界面和结束界面。最后,再介绍几种Canvas游戏引擎,它们将有助于编写你自己的游戏。

首先来看看如何实现得分系统和关卡难度系统。

6.4.1 记录得分与关卡

大约10岁时(好吧,或许有些人会更晚),我们整天都在玩Breakout(一种打砖块游戏)。当时,有些人是在早已成为古董的Atari游戏机上玩,另一些人则是在星期五,跑到必胜客去玩。我们一遍又一遍地打这个游戏,目的就是为了提高分数。那时,你只能在当地的一个小圈子里互相比拼得分;现在,有了社交媒体,玩家的游戏得分很容易就能公布到网上,玩家可以在全球范围内进行竞争。为了让用户在网上爆出自己的得分,首先来调整一下我们的游戏,让它记录下一共打碎了多少砖块。

第1步:创建一个得分与关卡计数器

要想实现平视显示界面(HUD)效果,需要利用Canvas API 创建一些文本。就像CSS一样,你可以控制文本对齐、垂直对齐(又叫做文本基线),以及一些@fontface字体。注意不要使用任何调节字间距的属性,否则文本会显得局促。

警告 在用Canvas技术开发应用时,一定要使用矢量字体,不要使用位图。根据W3C的Canvas工作草案的描述:“(如果采用位图,则)对字体变形时,字看起来会非常难看”。如果使用位图字体,在旋转文本时,就会显得很丑。

创建计数器的最简单办法是在 Game 对象下面添加一个 Hud 对象,然后通过Game.init()和 Game.draw()运行它(这正是代码清单 6-15 所做的)。另外要注意的是,要把HUD 的开启逻辑放进init 会自动重置它,在以后整合Game Over 功能时。

代码清单6-15 game.js——得分与关卡输出

每当击中一块砖块时,得分计数器就要进行一次递增,所以,需要修改Bricks.collide()来递增Hud.score,代码如代码清单6-16所示。注意,之前我们已经在Brick.draw()中添加了触发升级的代码,所以不用担心这一点。

代码清单6-16 game.js——调整砖块摧毁逻辑

下面,递增Ball.init()中保存的小球速度,并将Bricks.init()中的砖块数目乘以一个关卡因子。根据玩家所处关卡,关卡因子能按某种比例来调节某些属性。利用代码清单6-17所示的代码中的关卡因子,一旦当玩家升级时,某些对象的属性就会改变。

代码清单6-17 ——小球和砖块的更新

当玩家升级时,除Hud之外的所有参数都应该通过一个新方法Game.levelUp()进行更新。问题在于,玩家过了5关之后,砖块就占满了界面。为了防止这种情况,需要添加一个 Game.levelLimit()方法,并修改 Bricks.init()逻辑。一旦用了代码清单6-18所示的代码,这个游戏就只能玩几关了。

代码清单6-18 game.js——游戏参数更新

第2步:在线存储最高得分(可选功能)

利用实时的得分计数器,玩家能轻松将公布他们取得的最高得分。最简单的方法是访问http://clay.io,查看一下leaderboard文档。

贼不走空,所以要注意安全

由于游戏使用JavaScript,所以黑客很容易就能操控最高得分、生命条数,以及其他一些信息。许多资料都证明JavaScript的安全限制对于高分榜来说是个大麻烦,游戏内部信息容易泄露,从而使居心叵测的人获得不义之财。

如果确实需要安全性,可以采取下面这样几种方案。

最直接的是让服务器来处理所有的游戏数据,在储存任何数据时,先进行检查。缺点在于,需要用户互相参照游戏数据,这会给服务器带来沉重的负担。

还有一个较为少见的方法,在JavaScript文件中隐藏一段安全性代码,AJAX用来和数据库进行握手操作,查看当前游戏是否有效。也可以通过设计模式,在JavaScript中模拟私有属性或变量。虽然这两个方法有效,但它们也只能暂时保证游戏的安全性。

不要认为用Flash或Java来构建游戏就没有安全问题,这些平台一样有着自身的漏洞。

所以,选用实现语言不重要,重要的是怎样制定安全机制。

6.4.2 添加游戏开始界面与结束界面

玩家进入游戏,可能会立刻开始玩,也可能会马上就退出。为了让玩家能够玩我们的游戏,创建一个友好的欢迎界面(如图6-9所示)。我们通过一个侦听鼠标点击事件的事件侦听器来实现这一功能。

第3步:创建一个欢迎界面

首先在Game对象下面添加一个叫做Screen的新对象(如代码清单6-19所示)。界面的背景要足够大,才能容纳其中的内容。只有两个文本:“CANVAS RICOCHET”和“Click To Start”。

代码清单6-19 game.js——创建欢迎界面和事件侦听器

欢迎界面还需要在一个叫做 Game.setup()的新方法中添加一个侦听器来侦听点击事件。同样,为了适应新增的事件侦听器,还需要对Game.init()进行修改。另外,将侦听器逻辑添加到一个新的Game.runGame()方法中,从而能将侦听器实现复用,如代码清单6-20所示。

代码清单6-20 game.js——创建欢迎界面与新的事件侦听器

下一节将设置游戏结束界面,如图6-10所示。

第4步:创建一个游戏结束界面

做好欢迎屏幕,用户可以顺畅地一直玩下去了。如果没接住小球,游戏就结束了,这时就需要弹出一个游戏结束界面。利用下面的代码段添加一个 Screen.gameover()方法。不需要在代码中调用 Screen.gameover(),因为它是放在 Ball.draw()中的。

另外,还需要为之前添加的侦听器Game.restartGame()添加一些代码。该侦听器会侦听一个点击事件,随后利用下面的代码重置游戏为开始状态。还需要将Game.restartGame()作为一个新方法添加到Game中。

这是整个游戏的最后一段代码!试玩一下,然后让你的家人和朋友分享一下你的成果吧。

6.4.3 借助现成的代码库

通过完成 Canvas Ricochet 这个游戏,你也明白了如何利用 Canvas 编写一个完整的游戏。要想完成它,确实需要一定的时间。为了节省时间和资金,在现实项目中,你可能得要借助一些JavaScript库。比如Impact.js这个库,利用它完成这个游戏只需要 100 行代码或者更少(但那样的话,你也没法了解 Canvas的工作原理了)。不过,你还需要考虑到,这些引擎并不会优化你的代码,经常会影响游戏的速度性能。当前,大多数开发者都喜欢用 ImpactJS ,但你当然也可以找找其他的代码库:http://html5gameengine.com/。

ImpactJS

ImpactJS(Impact JavaScript Engine)是一个快速高效的HTML5 代码库。它的文档更新得很快,并且还有视频教程,可以让人轻松上手。唯一的缺点在于每个许可需要花 99美元,如果你只是想用它来测试的话,就有点太贵了。图 6-11 展示了一个用该引擎实现的游戏。

怎样将HTML5游戏移植为移动应用

理论上,HTML5 应用只需编写一次就能在任何设备上运行,但很明显,移动设备现在还做不到这个程度。如果想让HTML5游戏变成能够在安卓、iOS或其他设备上运行的移动游戏,可以考虑appMobi或PhoneGap。它们都能提供强有力的转换工具,能把应用移植到各大主流移动设备上。鉴于本书篇幅有限,我们只好忍痛割爱,不再介绍将游戏移植为移动应用的内容。

6.5 小结

Canvas 的功能并不局限于视频游戏,它用途多样,也适用于网站的设计。可以用它来创建交互式的背景、图像编辑工具,等等。比如,你可以制作一个页脚,显示那些玩过Canvas Ricochet 的玩家,一旦他们开始游戏后,就去除这个页脚。

虽然已经介绍了 Canvas 的很多功能,但还是有些性能没有深入研究。比如说,随着Canvas GUI 工具的发展,有可能做出一个小的动画片。同时,可以让页面响应鼠标位置,或者根据鼠标点击或悬停事件来激活动画序列。

用2D Canvas技术制作游戏的确很好玩,但这些游戏还无法产生收益。另外,绝大多数的HTML5 Canvas游戏厂商盈利情况都不是很好。如果Web应用想要与生桌面应用程序(游戏或其他别的什么应用)进行竞争,开发者最好应该借助一些库,并提高应用的效能。另一方面,Canvas技术做出的2D应用成本低廉、获取方便,唯一的问题在于:如果不经过额外的修改,这些应用无法适用于不同屏幕尺寸——尽管Canvas可以通过WebGL来生成能够较好适配屏幕尺寸的3D内容,但也存在一些问题。一种简单有效的方法是用SVG技术。它有着极为丰富的功能,在创建图像方面能力远在Canvas之上,下一章就来讲讲这个技术。

查看各节中的标志可迅速定位上表中的各个主题。

第7章 SVG:响应式浏览器内图像

本章主要内容

■位图与矢量图的特点对比

■如何创建SVG

■流式布局图形在SVG 中使用JavaScript

■SVG 和Canvas 的对比

可缩放矢量图形(SVG)是一种基于XML语言,用来描述二维矢量图形的一种图形格式,大约诞生自2001年前后。它的定义草案并不属于HTML5规范,但HTML5规范却能让你直接用HTML标记中使用SVG。驾驭好了SVG,那些简单的形状、渐变以及复杂的插图将会自动适配网站及应用的布局结构。另外,在图像调整尺寸的过程中,图像质量根本不用降级,这一点简直太棒了!在HTML5文档中可以直接创建图像,而无需使用任何其他的图形图像编辑软件(如Photoshop和Illustrator)。这就是SVG的威力。

随着本章内容的步步展开,你将更深入地理解位图和矢量图的差异,来理解 SVG的工作原理。然后开始讲解本章的教学范例 SVG Aliens,利用简单的 XML 标记创建构成UFO、飞船以及防御盾的 SVG 资源。设置好所需的资源后,我们会开始介绍如何利用JavaScript 代码使资源运作起来,使玩家能与这些资源展开交互。添加界面转场、得分计数器,以及难度渐进式增长的关卡。最后,还将总结并比较Canvas与SVG的各种特性,使你了解在未来的项目中该如何在这两种技术中抉择。

为什么要创建SVG Aliens 这个项目?

通过学习这个教程,你将发现很多在其他地方无法找到的优秀内容:

■可重用的SVG JavaScript 设计模式;

■通过属性及CSS 来控制动态的、可调整大小的SVG元素;

■对于导入的图像,利用CSS 来优化SVG 动画;

■管理大规模SVG组。

学完本章,你应该能够在HTML文档中使用SVG创建出自己的SVG应用,并且能够利用SVG的CSS支持。下面,首先来看一下矢量图的优缺点。

7.1 位图与矢量图形的对比

像SVG这样可调整大小的图像文件使用的是矢量技术(利用数学公式来创建形状),而没有采用位图技术(由图像数据构成数组),从而可以轻易地调节图像的宽度与高度,同时不会使图像品质有所降级。虽然矢量图形具备很多优点,看似可以成为一种大一统的图像格式,但其实它还存在很多问题。如果你熟悉位图与矢量这两种格式的差异,那么这里的描述就可以看作是一次温习。如果你乐意的话,就再快速浏览一下表 7-1,看看两种格式的具体差别。如果嫌麻烦,则可以直接略过此节,从7.2节开始构建游戏。

以.gif、.jpg、.png 格式为主要格式的位图占据了Web图像的主导地位。用文本编辑器打开一幅位图,就能发现图像中所有的像素信息。由于像素数目都是固定的,所以位图经放大尺寸后,图像品质会降级。所以在谈到可调整大小方面,SVG格式具有明显的优势,因为经过放大后,这种格式的图像不会出现任何品质降级(如图7-1所示)。

SVG的另一大优势在于可以直接将SVG写入HTML文档,无需引用任何文件。创建图像所用的代码量也较少,从而加快了页面加载速度。

你可能会用.ai、.eps、.svg 格式的文件来绘制网页的 Logo。矢量图是通过数学公式计算获得的图形,包含着各种标记点、贝塞尔曲线以及形状。由于是由数学公式创建的,所以这些图像在放大时并没有受到各种限制,如图7-1所示。

Wilson,一张可大可小的笑脸

为了让你了解矢量图形的工作原理,下面我们用SVG的XML标记来创建一个简单的笑脸图形:Wilson(如图7-2所示)。

如代码清单 7-1 所示,Wilson.svg 只包含XML 数据。把这些代码添加到一个叫做wilson.svg 的文件中,然后用任何现代浏览器打开,就能发现这个图形的边界十分光滑,并且能流畅地放大缩小,图像品质似乎不受影响。

代码清单7-1 wilson.svg——SVG代码范例

创建Wilson.svg文件需要在<svg>标记内声明一个带有多种属性的XML信息。如果在浏览器中打开这个文件,放大窗口,你会注意到它仍然符合新尺寸。如果使用简单的<animate>标记,还可以移动Wilson的笑脸;添加一点儿JavaScript代码,还能让它响应鼠标的点击。

所有现代浏览器都支持SVG格式的文件,这就是为什么它非常适合绘制图形并缩放图像的原因了。但如果想用它来制作复杂的动画,或者使用只有特定浏览器才实现了的特性,那情况就比较尴尬了。这种状况似乎是合理的,因为W3C制定的SVG推荐标准是一套非常庞大的文档(http://www.w3.org/TR/SVG);你不可能期望浏览器厂商能够实现全部的标准。不过不用担心,所有的现代浏览器都已经能够支持本章代码中出现的特性,除非另加规定,否则它们将一直可用。

矢量格式并不完美,但在简单的图像与插图应用领域内,它的优势要比位图格式更明显。通过上面这个Wilson.svg的例子,我们可以看到,在流式网站布局中,采用SVG格式的图像可以非常流畅地调整尺寸大小。

下面就来利用SVG 知识来创建SVG Aliens这个游戏吧。

7.2 利用XML构建SVGAliens游戏

在创建SVG游戏(如图7-3所示)之前,可以先在本书网站上玩一下这个游戏(http://html5inaction.com/app/ch7),然后从[http://manning.com/crowther2/](http://manning.com/crowther2/)处下载该游戏的源代码。在压缩文件中,你能找到 ufo.svg、mothership.svg、cursor.png,把这些文件都复制粘贴到你自己创建的应用文件夹中。

上一章中,我们用 Canvas 创建了一个打砖块游戏:Canvas Ricochet。SVG Aliens的游戏机制与此相近,但复杂性大为提高。上一章游戏中的球拍,现在化身成为宇宙战舰。小球现在由激光弹所取代,而且这一武器不仅能杀伤敌人,而且也能破坏自己的防御盾。砖块现在变成了外星人,它们能够逐渐推进,力图消灭你的战舰。复杂性的增加意味着难度的提高,所以我们还增加了生命计数器和防御盾,帮助飞船从来袭的激光火力中存活下来。

本节将主要介绍以下这些有用的SVG技术

■如何将SVG的XML 语言整合进HTML 文档。

■如何创建文本和简单的形状。

■如何利用路径制作简单的插图。

■如何使用XLink 将.svg 文件注入页面。

■如何利用属性对元素进行动画处理。

■如何利用CSS 调整SVG形状。

■如何利用流式布局的viewBox 属性。

注意,使用 SVG,需要用到现代的浏览器。目前,SVG 在 Chrome 似乎有着最流畅的表现,除了Opera,其他浏览器都可以用——Opera缺乏本章范例必须要用到的边界盒。不过SVG是一个庞大的规范,至今还没有一种浏览器能支持它的全部功能。

本节中,我们要在HTML 文档中设置SVG XML标记,同时还用到了一些JavaScript和CSS代码。通过在<svg>标记中配置viewBox属性,下面将创建一个与Wilson一样可伸缩的视窗(viewing window)。接下来,先来进行基本的游戏设置。

7.2.1 在HTML 中设置 SVG

本节将通过以下7个步骤,从基本的框架开始,逐渐打造一款可改变大小的Web 页面游戏。

■第1步:设置基本的SVG标记。

■第2步:创建CSS 文件。

■第3步:为游戏开始界面添加形状。

■第4步:将文本添加到界面,并对它制作动画。

■第5步:通过XLink导入现有的SVG文件。

■第6步:创建游戏结束界面。

■第7步:配置游戏灵活的viewBox 属性。

第1步:设置基本的SVG标记

打开一个文本编辑器,创建三个空文件:index.html、style.css、game.js。把它们放在同一个目录中。本节将实现前2个文件。

把代码清单7-2所示的代码添加到index.html中。在这些代码中,<svg>标记中存在width和height参数,另外还声明了名为viewBox的观察区域。在这里我们还是不打算配置viewBox,因为还需要一些CSS代码,它才能运作。

代码清单7-2 index.html——基本html页面

第2步:创建CSS文件

利用代码清单7-3所示的代码填充style.css。它能够配置游戏颜色与布局。另外,为了让这些代码能够正常运作,还必须把下载源文件中的cursor.png文件和style.css一起放到根目录中。

代码清单7-3 style.css——主要的CSS代码

试试看

刷新浏览器,页面上只显示黑色的背景和一行文本。不要因为鼠标指针消失而惊慌。如上所述,我们已经用一个完全透明的图像文件把鼠标指针替换了。

HTML5的 Pointer Lock API与 CSS颜色填充替代方法

一般来说,当需要采集鼠标移动信息并隐藏鼠标指针时,都需要将鼠标锁定在一个特定位置。虽然浏览器处于安全性的考虑,不允许切换系统的操作控制,但通过Pointer Lock 这个HTML5 API 却能轻松锁定鼠标移动,采集鼠标信息。可参看最新的 W3C 草案获取更多相关信息:http://www.w3.org/TR/ pointerlock/。

CSS 颜色填充的替代方法是直接在 XML 标记内添加属性fill = "#453"。专业的前端开发者认为内联样式是一种糟糕的实践,因为在HTML元素内不断添加属性很快会让文件变得难以维护。

7.2.2 实现简单的形状与文本

喜欢使用CSS3 的人可能会觉得一定是CSS或JavaScript控制着SVG Aliens的动画、渐变效果以及其他一些功能。幸好,SVG有一个<animate>标签,并且支持渐变。利用这些特性,让我们来创建游戏开始及游戏结束界面。

第3步:为游戏开始界面添加形状

图 7-4 所示的开始界面包含的元素有:游戏名称、积分系统的具体信息,以及一个告知玩家需要点击才能开始游戏的消息。先来创建开始界面。

创建简单的形状

要想创建正方形,使用矩形标记<rect x ywidth height>。创建圆形的标记为<circle cx cy r>,创建椭圆用<ellipse cx cy rx ry>,创建线段用<line x1 x2 y1 y2>,创建折线用<polyline points>,多边形用<polygon points>。这些形状一般需要x与y坐标值,有些则需要笛卡尔坐标系中的多个点。每种形状都能接受填色、描边颜色/描边宽度以及渐变。表7-2展示了这些标记的具体用法。

你可以把表7-2中的XML标记合成一个组,就像这样:<g>content</g>。组就像<div>一样,能够存储复杂的组合形状。可以利用JavaScript和CSS选择器轻松定位组,而不用单独地选择组中的某个元素。将代码清单7-4所示的代码添加到<svg>标记中,创建出一个组和一个渐变。

代码清单7-4 index.html——背景设置

刷新浏览器,应该能看到一个黑色的背景,带有微妙的圆形渐变。如果渐变偏离了中心位置,不要怕,设置viewBox时会改正这个问题。如果在显示器上看不到渐变,可以调整<stop offset="0%" stop-color="#333" />,选用诸如#555 这样的亮色。

第4步:将文本添加到界面,并对它制作动画

设置好背景后,就该添加文本了。<text>标记用x与y坐标参数来确定放置位置。当你查看完成的SVG Aliens游戏时,大概会注意到“Click To Play”具有缓慢的渐隐效果。在文本标记中插入<animate>标记可以创建渐隐动画。制作动画所需要定义的内容有:定位CSS(attributeType),声明一个特殊的样式属性(attributeName),开始值(start)和结束值(to),以及以秒计算的持续时间(dur)。将<animate>标记放在大多数 SVG 元素中,创建动画就无需使用 JavaScript 或 CSS3 了。把下面的代码内容用<g id="screen Welcome"></g>封装起来,就能创建文本动画了。

还能针对其他元素制作动画吗?

除了 CSS,还可以对形变和位移方向等方面的内容进行动画制作。可访问 http://www. w3.org/TR/SVG11/animate.html去了解其中详情。注意,该文档内容很长,且在术语和范例上更讨好浏览器厂商而非开发者。

7.2.3 使用 XLink和复杂形状

建立好基本形状、文本和渐变之后,下面将利用更为复杂的 SVG标记来创建一些形状。首先介绍如何利用XLink的快捷方法将图像导入,然后再利用<path>来创建图像。

W3C规范XLink是XML Linking Language(XML链接语言)的缩写。我们主要用它来导入SVG文件,但它还有其他用途,通过<a>元素在SVG内部创建链接。

有关XLink的更多介绍

如果想更详细地学习 XLink,可以参看 Jakob Jenkov 的教程“SVG: a Element”:http://tutorials.jenkov.com/svg/a-element.html。

虽然可以利用 SVG 从零开始创建飞碟形状,但利用 XLink 和<image>将一个 SVG文件导入进来无疑是更轻松的办法。你可以用Adobe Illustrator 或Inkscape 这样的常用矢量绘图软件快速地创建SVG文件,并调整其尺寸大小。唯一需要注意的是,在保存时,需要用“另存为...”将这些文件另存为svg格式。

警告 继续制作之前,先查看一下根目录中是否有mothership.svg和ufo.svg这两个文件。没有这两个文件,XLink图像就不会出现应在的位置处。

第5步:通过XLink导入现有的SVG文件

将代码清单7-5 所示的代码段插入<g id="screenWelcome">,利用<path>标记创建一个玩家要控制的宇宙战舰。注意,路径的d属性中包含着一系列的点,我们正是通过这些点来塑造宇宙战舰的形状的。在已嵌入<g id="screenWelcome"></g>中的下列代码后面添加新的XLink图像和路径。

代码清单7-5 index.html——使用XLink

使用路径来创建复杂形状

你可能会在代码清单 7-5 中看到<path>用到了很多字母和数字来标示特定方向。这些用作移动命令的字母的含义如表7-3所示。

使用大写字母(如 V)声明一个位置,表明该位置是相对 HTML 文档中<svg>标记的位置测量得出的,而使用小写字母(如v)则表明是该位置坐标是相对于之前声明的x与y坐标。

编码及阶段性测试

到现在为止,我们已经将一些代码段组合在了一起,按照代码清单7-6再次检查一下index.html,务必确保SVG代码部分正确无误。

代码清单7-6 index.html——欢迎界面

刷新页面后,你制作的欢迎界面应该如图7-5所示的这样。

一定要在style.css末尾处加上下面代码,以后制作游戏结束界面时,可以把欢迎界面隐藏起来,这样做起来就简单多了。

#screenWelcome { display: none }

第6步:创建游戏结束界面

理想的游戏结束界面应该要显得有趣,并且能鼓舞玩家再次尝试游戏。与制作欢迎界面一样,很快就能做出游戏结束界面(如图7-6所示)。

使用代码清单7-7 所示的代码替换位于<g id="screenWelcome"></g>后面的<g id="screenGameover"></g>。代码中使用的标记和属性与创建欢迎界面时用的完全一样,因而它的代码十分简单。

代码清单7-7 index.html——游戏结束界面

第7步:配置游戏灵活的viewBox属性

下面,不改变游戏的笛卡尔坐标,按照用户窗口的尺寸来配置<svg>上的viewBox参数。利用4个不同的属性来设置viewBox:最小x坐标、最小y坐标,以及视区的宽度与高度(<svg viewBox="min-x min-y width height">)。由于要将游戏居中,所以不需设置最小x 坐标和最小y坐标,将这两个值都设为0。将视区的宽高度都设为500,这也正是SVG应用的尺寸。

修改后的<svg>标记应如下面代码所示:

现在,游戏的布局恩能随着浏览器页面的刷新而更改了,真正实现了弹性布局。然后在style.css中,用#screenGameover { display: none }来替代#screenWelcome { display: none }。于是当你刷新页面时,只能看到欢迎页面。

如果之前并不明白矢量视图的概念,那么在理解viewBox的属性运作机制方面就会比较困难。如果搞不懂改变尺寸的原理,那么建议多用不同的参数值来试试,看看效果。

现在创建了两个游戏界面,在HTML、XML和CSS的协助下,它们能够动态改变尺寸。利用这些语言完成游戏似乎不错,但却根本办不到。我们只能依靠JavaScript来创建游戏逻辑、碰撞侦测以及人工智能(AI)。

7.3 利用JavaScript添加交互性

既然利用 SVG 可以轻松地创建矢量可视化资源,你可能会期望它会集成革命性的JavaScript。实际上,使用并修改 SVG 是非常麻烦的,因为它严重依赖 DOM。可以利用SVG标签属性来完成各种任务,但要想发挥它的全部潜力,还是需要JavaScript的协助才行(就像HTML5各种API的处境一样)。

本节主要学习内容

■如何创建SVG JavaScript引擎。

■如何创建简单的SVG设计模式。

■如何动态生成元素。

■如何利用命名方案正确获取XML 数据。

■如何使用CSS 简化复杂的路径动画。

{Core API!}要想与 XML 完美配合使用,则需要额外配置 JavaScript,所以问题有时会变得非常复杂。要想摆脱这些约束和限制,需要为游戏使用一种灵活巧妙的设计模式。不要怕,能挽救这种不利局面的JavaScript解决方案有很多呢。

XML命名问题

在继续学习之前,有必要了解一下命名空间和JavaScript的关系。命名空间定义了浏览器所需解析的信息(即究竟解析为XML数据还是HTML数据)。当与XML进行交互时,必须声明一个命名空间,否则浏览器并不知道你更换了命名空间。一些命名空间使用不当的症状包括:无法正确返回数据、新DOM元素插入到错误的位置,以及普遍运行不稳定,等等。要想解决命名空间上的问题,需要使用末尾有 NS 的方法,比如 getAttributeNS(NS,element)。要想了解命名空间方法的详细信息,请参看 Mozilla 关于 JavaScript DOM 元素的文档:https://developer.mozilla.org/en-US/docs/DOM/element#Methods。

主要的JavaScript库,比如jQuery和MooTools在绝大多数情况下都不支持命名空间,也就说明它们并不能很好地操控SVG元素。

直到最近,要想在Web 上使用矢量图形,开发者还必须得使用VML(Vector Markup Language,矢量标记语言)、Flash或其他程序。由于IE8和版本更低的IE浏览器并不支持 SVG,所以在开发实际应用时,可能必须要用JavaScript 矢量图形库来生成能够兼容新旧多款浏览器的代码。当前,一款比较流行的库叫 RaphaelJS。如图7-7所示,这就是用它做出的一个虎头。

RaphaelJS利用SVG及其前身VML来创建矢量图形。另外,借助性能优越的插件,它还能将复杂的数据表现为饼图或其他可视化形式。RaphaelJS 的竞争对手svgweb则使用Flash来渲染SVG元素。除了这些库之外,如果不需要为较老的浏览器提供支持,完全可以使用d3.js(http://d3js.org),它的性能也很不错。

由于我们不打算支持老版本的IE,所以可以用没有回调函数库的JavaScript代码来开发我们的游戏,还将逐步讲解基本的SVG设计模式。另外还将介绍如何用JavaScript对象创建可重用资源,接着再来讲讲如何创建防御盾。最后,将设置外星战舰(以下简称UFO)集群,这一内容比较复杂,因为要创建50多行代码。

为了让复杂的任务变得稍微简单些,下面将把任务分解成三组步骤。

首先来实现游戏引擎的主要部分。

7.3.1 游戏引擎基本结构和使用界面

由于这个游戏有着非常复杂的逻辑,所以需要一个高效的设计模式。引擎的核心是一个Game对象,它可以进行游戏初始化、更新对象、处理游戏界面转场、判断游戏结束、清除对象等功能。

第1步:建立基本的游戏工具、元数据,以及XML命名方案

从现在开始,将所有的代码放入一个自执行的函数,以防止 JavaScript 变量泄露到全局空间。引擎利用代码清单7-8所示的代码来设置基本工具、元数据(如游戏界面的宽高)、XML命名方案,以及其他内容,这些内容都不属于其他对象。将下列代码放入game.js中。

代码清单7-8 game.js——游戏引擎基本架构

首先,引擎用run()来侦测浏览器是否支持SVG,然后开始设置所有的游戏对象。update()方法负责去除或改变游戏资源。这里你会注意到,有些init()项目没有放在update()中,因为它们需要独立的计时器去触发。

警告 虽然在这里使用 requestAnimationFrame 似乎是个不错的办法——就像我们在第6章中做的那样——但其实这并不理想,清除动画计时器很麻烦。用polyfill来编写间隔时间或延时很容易出现问题,有些浏览器对基于时间的SVG动画的支持度不高。在这些情况都没改观之前,除非利用 Canvas 来开发应用,否则最好使用 setTimeout()和setInterval()。

第2步:集成界面转场

为了使用早先创建的欢迎界面与结束界面,需要利用代码清单7-9所示的代码来清除SVG元素与鼠标点击监控功能。

代码清单7-9 game.js——界面转场

维持游戏对象的所有工作都已经完成了。下面先让我们从最简单的对象开始创建,最终去实现更复杂的对象。

7.3.2 设计模式、动态对象的创建以及输入方式

创建每个游戏对象,都需要用到一定的设计模式以及一些特殊方法。在创建对象时,应该把一些不变的属性应该放在所有方法之前。这种属性一般包括:路径数据、宽度、高度、速度,等等。每个对象都应该有一个init()方法来处理x/y坐标值、计时器,以及重置属性等必要功能。init()将创建DOM相关的数据,必要时它也应该调用对象的 build()方法;update()用于执行在计时器中触发的逻辑;最后一种需要用到的方法是collide(),用于处理碰撞侦测。表7-4列出了设计模式所用主要方法及其概述。

明白了对象的组织结构后,开始创建大型UFO。

第3步:创建大型UFO

在游戏中,隔段时间之后,大型 UFO(如图 7-8所示)就会从界面的左上角出现。为了在游戏开始时让玩家看不到它,需要把它的起始x坐标值设定为其宽度的负值。比如,如果大型UFO宽45像素,则它的起始坐标的x值为−45。游戏设定:由于罕见性,所以玩家击中一个大型UFO,就可获得30分。

使用前文讨论过的设计模式创建一个大型UFO对象,把代码清单7-10所示的代码复制粘贴到一个自执行函数中,再将该函数放在Game对象声明的下面。

代码清单7-10 game.js——大型UFO(UFO母舰)

创建过程非常简单。下面来打造玩家的战舰,其原理类似,但要添加一个输入监控器和SVG路径。

第4步:创建玩家的太空战舰

由于之前创建过玩家的战舰路径,所以只需重用那段代码就够了。路径的 d 属性内置有x与y坐标,所以需要将x/y坐标和路径数据分成两个不同的参数,这样才能动态生成玩家战舰的 x/y 坐标值。利用代码清单 7-11 所示的代码来创建玩家的太空战舰。

代码清单7-11 game.js——玩家的太空战舰

第5步:用键盘操控玩家战舰

除了上面代码之外,还需要用一个update()方法来添加数值,以便监控键盘输入情况。使用鼠标输入也是可行的一种方法,后面将在Ctrl对象内实现这一功能。首先,利用代码清单7-12所示的代码完成Ship对象。

代码清单7-12 game.js——玩家太空战舰的交互性

现在可以测试一下蓝色的己方战舰了,将Game中没创建的对象都注释掉,以免出现不必要的错误。注意查看浏览器的控制台,确保没有意外错误发生。如果在此之前,你是自己探索着进行开发,那么请在继续下面的内容之前,把代码调整成上面代码的样子。另外,为了保证下面代码段能够正常运行,你还需要把一些没有实现的对象给注释掉。

第6步:捕捉键盘与鼠标控制

许多教程都用 jQuery 和其他的库来创建键盘绑定,但对于现在大多数浏览器来说,不需要库也能支持大多数键盘键位。至少,可以安全地实现箭头键、空格键、字母键以及鼠标移动和点击,这些键位和鼠标行为都在下面代码中实现了。将之前的Ctrl对象用代码清单7-13所示的代码替换。

代码清单7-13 game.js——键盘/鼠标设置

修复好所有错误之后,就能通过键盘或鼠标来移动战舰了。就像上一步骤我们说过的那样,如果之前你自己调整过代码,那么确保把它们调整回上一段代码的样子。

设置好了玩家战舰以及输入绑定,下面我们来进行第2组任务,开始实现更复杂的对象。这些对象需要更复杂的逻辑代码,因为它们更依赖于周边的环境数据。

7.3.3 创建并组织复杂形状

在第2组任务中,需要创建一些逻辑较为复杂对象用于移动或布局。

■第2组任务:复杂对象以及碰撞。

♦ 第1步:创建防御盾。

♦ 第2步:实现激光。

♦ 第3步:添加激光碰撞侦测。

♦ 第4步:创建平视显示器。

首先,创建保护玩家战舰免受来袭激光的蓝色防御盾。接着,创建激光对象,它们需要处理游戏的碰撞逻辑。随后将设置平视显示器(HUD),显示玩家剩余生命条数以及累积分数。

第1步:创建防御盾

防御盾的结构比我们之前创建的任何对象都要复杂,因为它是由多个部分构成的,如图7-9所示。盾的每个分块(盾块)都有命中点数(HP,Hit Point),并且每个命中点数都包含一个不透明值。命中点数能够描述物品能承受的打击次数。

我们将创建4个防御盾,每个盾都由8个盾块组成,利用代码清单7-14所示的代码来配置它们。

代码清单7-14 game.js——防御盾设置

构建防御盾需要用到二维数组。这个二维数组将包含4个防御盾的数据,每个防御盾又将包含8个盾块的数据。将二维数据传入build(),就会将这些数据转化成实际的对象。如代码清单7-15所示,我们还将动态地生成x与y坐标值。

代码清单7-15 game.js——防御盾辅助函数

第2步:实现激光

下面创建通用的激光对象,它能击中任何标记为class ="active"的元素。UFO和玩家在射击时用的是同一种激光对象。利用代码清单 7-16 所示的代码来创建 Laser对象。

代码清单7-16 game.js——构建激光

第3步:添加激光碰撞侦测

在这个游戏,实现碰撞侦测需要以下两个步骤:

1.采集所有的活跃激光对象,存储它们的DOM数据;

2.将这些激光对象的取回信息与当前活跃的 SVG 元素相对比,如果判断碰撞为true,则触发该对象的hit方法。

使用代码清单7-17所示的代码来配置Laser.update(),它将整合碰撞侦测功能。由于DOM访问,可能理解起来稍微有些难度。

代码清单7-17 game.js——移动激光

试试看

检查代码,排除可能出现的错误。通过点击鼠标(或用触摸板或触摸屏上轻点)向防御盾射击来测试碰撞侦测是否正常。

第4步:创建平视显示器(HUD)

玩家需要知道生命条数和当前分数。通过创建一些SVG元素,就能轻松展现这些信息。创建了 HUD,就需要额外的逻辑来维持所展现的游戏数据,如代码清单 7-18所示。

代码清单7-18 game.js——创建HUD

设置HUD时,HUD就会创建好所有的必要文本元素。创建生命计数器,使用已有方法来构建玩家战舰。接下来,用代码清单7-19所示的代码来使HUD具备更新信息的能力。

代码清单7-19 game.js——HUD更新信息

当 HUD 更新玩家的得分时,还会查看是否赢得了额外的生命。每次更新,得分的文本就会在DOM中被完全替换,而额外赢得的生命则会以一个新的玩家战舰的图像显示出来。每消灭一架UFO,Hud.update.level()就会触发,查看是否需要调整UFO的速度。如果需要调整UFO的速度,UFO的计时器就必须停止,然后利用新的计时器重新开始。

7.3.4 维持一个复杂的 SVG 组

在第3组任务中,需要创建UFO集群,一共要创建55架在屏幕上来回移动的UFO (如图7-10所示)。虽然完全可以手动创建每架UFO,但通过一个方法自动完成这些任务岂不是更好?

下面就是本节需要完成的步骤。

■第3组任务:UFO 集群。

♦ 第1步:设置UFO 集群。

♦ 第2步:为UFO 添加运动路径。

♦ 第3步:使UFO 运动起来。

♦ 第4步:使UFO 随机射击。

第1步:设置UFO集群

放置UFO以及设置其AI需要用到很多数学知识。坦诚地说,这不容易理解,但通过代码清单7-20所示的代码,你至少可以理解基本的游戏AI编程知识。下面这些代码主要做的就是确定UFO数量、集群UFO,以及如何设置UFO动画的基本参数。

代码清单7-20 game.js——UFO集群设置

第2步:为UFO添加运动路径

要想为每一个 UFO 生成不同的路径,可以使用 Adobe Illustrator 或 Inkscape (http://inkscape.org/)。这两种软件都可以把矢量图形保存为 SVG 格式。在将图形保存为SVG 格式的文件后,用文本编辑器打开,就能得到用来创建形状的路径信息了(对于本书这个例子来说,你可以用下载源文件中UFO的SVG文件)。

使用CSS使SVG更容易

就像在HTML中把内容放在各个<div>元素中一样,UFO都被放在SVG组中。利用组和CSS的继承性,就可以调整组中所有元素的颜色、显示以及更多特性。总之,SVG组能实现更多的控制,减少所需添加的标记以及维护工作量。下面的代码段展示的是已添加到 style.css中的CSS规则,因而不用再次添加。利用.open与.closed选择器,可以在每个UFO所可能采用的2条路径间进行选择。下面的代码段能够根据.a、.b或.c三个类为UFO指定不同的颜色。

在代码清单 7-21 所示的代码中,我们已经预先把所有的路径都建立好了,只需放心使用即可。

代码清单7-21 game.js——UFO路径

第3步:使UFO运动起来

为了创建简单的动画,我们需要把每个UFO的2个可行路径隐藏掉一个。单用SVG完全可以创建动画,但利用 CSS 却可以更为简化,并能减少计算负担。为了完成动画以及build()的辅助方法,将代码整合进UFO对象中,如代码清单7-22所示。

代码清单7-22 game.js——UFO动画与辅助方法

如果SVG文件路径破损了可怎么办?

如果发现由矢量编辑软件所创建的SVG路径发生偏移或断裂,怎么修复它呢?在有些情况下,可以将图形移动到SVG文件画布的中央或左上角,问题即可解决。另外一种办法是将图形外围的白色空间全部清除,使其外围空间变为透明。如果这些办法都不奏效,可以手动地在SVG的viewBox属性中添加偏移量,就像我们在UFO中做过的那样。

创建动态运动

每当UFO集群移动时,都需要侦测是否与游戏边界发生碰撞。这是因为在写作本书之时,SVG 的碰撞侦测技术在所有的浏览器上都并不稳定。当以后变得更加有效之后,我们就可以利用getIntersectionList、getEnclosureList、checkIntersection和checkEnclosure这些方法了(更多信息可参考W3C官方文档:www.w3.org/TR/SVG/struct.html#__svg SVG SVGElement__getIntersectionList)。

可以用一个想象的盒子把所有的UFO都包起来,姑且我们把它叫做边界盒(bounding box)。与其手动计算这个边界盒,可以在代表 UFO 集群的 SVG 元素<g id="flock">中调用getBBox()。它将计算UFO 集群外的边界盒,返回一个类似这样的对象:{x: 20, y: 20, width: 325, height: 120}。

简单总结一下,整个逻辑流程如下。

1.获取UFO集群的边界盒。

2.查看边界盒是否撞墙(如果撞墙,则向相反方向增加坐标)。

3.适当递增所有UFO的x与y坐标,检查玩家是否已经失败。

4.切换动画。

5.有可能的射击行为。

此行文本用于列表编号,不因该出现在正文中。

好了,下面就来创建 update()方法来控制 UFO集群的运动吧,如代码清单 7-23所示。

代码清单7-23 game.js——UFO运动AI

注意 如果现在使用Opera浏览器,在某个SVG元素使用getBBox()时,它可能并不会如你预想一般运行。我们只好等待Opera的改进。

第4步:使UFO随机射击

每次调用update()方法时,根据一个随机数检查来触发射击行为。如果UFO射击,将使用上一代码清单中生成的一个边界盒,从底部一行的一个UFO进行发射。还可以让发射更为灵活一些,比如说只从每列的最底部一行发射,但那需要更多的逻辑代码,那就必须再次使用SVG边界盒数据来加速。利用代码清单7-24所示的代码实现UFO的射击功能。

代码清单7-24 game.js——UFO射击AI

试试看

现在我们实现了UFO 集群,从而成功地完成了SVG Aliens 这款游戏的开发。游戏运行画面应如图7-11所示。由于了解了一些SVG的基本概念,所以下面让我们把它和Canvas技术做一下对比。

7.3.5 SVG 与Canvas 的对比

当前,在浏览器中生成图形的最理想技术就是Canvas和SVG了。由于Canvas是基于位图的技术,所以你可能会觉得SVG会更好,图像更具适应性,但同样有一些问题也不容我们回避。

缺少相应的社区

凡是具有中等JavaScript技术水平的人都可以迅速理解Canvas的帮助文档。如果觉得它的官方文档过于复杂,还可以在网上找到很多教程。而SVG的官方文档呢,简直可以用浩瀚来形容,它们都很难理解,而且涉及的领域非常多。笔者只好寻找那些能够把问题阐述得简明扼要的资料,但往往是徒劳的,很少有人写过这方面的文章。

各种Canvas的相关库以及社区的发展速度都很快,在未来几年中,Canvas技术很有可能会轻松地成为Flash的最大竞争对手。相比之下,现在还没有出现像这样成熟的SVG社区。

JavaScript集成

在创建复杂应用时,Canvas 与 JavaScript 的集成度显然要比 SVG 优秀,这是因为Canvas不会与DOM产生交互。比如说,更新SVG元素时,需要将它们加载进DOM并处理,然后再注入页面。虽然程序员可能会觉得使用DOM有一些优势,但这同样也带来了一些复杂性。看看代码清单 7-25 所示的代码,对于更新一个正方形这样的任务来说, SVG和Canvas所用的代码量截然不同。

代码清单7-25 example.js——应用Canvas与SVG,相应所需的js代码范例

SVG的利弊

SVG的优势在于配合HTML标记使用内联XML元素,虽然这种作法导致该语言与JavaScript的配合度不高。使用XML,开发者可以不借助任何其他语言来创建动画图像。除此之外,这些形状都是DOM元素,意味着在运行时可以选择并修改它们,可以轻松为它们添加事件侦听器以及CSS代码。Canvas元素则不位于DOM中,因而也就不具有SVG的这些现成的优势。如图7-12 所示,Firebug 能够在页面上高亮显示一个 SVG图像,而对于Canvas元素,你只能看到容器的<canvas>标签。

Canvas 最令人头疼的问题在于其文本渲染质量太差了,很多开发者只好先制作出文本的预渲染图像,并通过自定义脚本来让Canvas解析它们。SVG的文本非常锐利清晰,从而使其成为文本类应用的最佳选择。

SVG的当前发展状况

SVG 1.1有自身的一些缺陷,所以开发小组正在致力于开发SVG 2,以期修补这些问题。用于移动设备方面的SVG Tiny 1.2也正在实践中。虽然在移动设备上,SVG的支持度不高,但情况在慢慢好转。如果想了解SVG发展状态更新,请参看W3C的相关页面:http://www.w3.org/Graphics/SVG/。

当你想用Canvas创建一个圆形时,需要创建一条路径并添加一些声明。SVG则不必那么复杂,只需通过一些HTML标记,比如<circle>,就可以免除用JavaScript创建多个点的困难,从而使其成为创建复杂形状的首选方案。

由于Canvas的很多功能是通过JavaScript脚本来实现的,所以我们认为很难被屏幕阅读器所访问。但另一方面,SVG 使用真正的页面元素(如<text>),这意味着屏幕阅读器有可能解释这些信息。

SVG的当前发展状况

用谷歌搜索“Canvas games”所得结果远远比搜索“Canvas games”所得结果要多得多。开发者社区也不太了解SVG,尤其是如何用它来构建游戏类应用。游戏需要具备非常强大的渲染能力,要能快速生成很多资源,比如粒子、敌人、场景,等等。由于 SVG 内置于 DOM 中,所以大量资源会使游戏性能变慢。除此之外,有关Canvas的宣传力度也不利于SVG的发展,特别是Canvas还能配合WebGL用于3D绘图领域。

开发者到底该选用哪种方案

以笔者的观点来看,简单的图形与动画可以用SVG技术来实现。如果应用使用了非常多的位图,并且对处理能力要求得很高,则选用Canvas技术。

7.4 小结

SVG 的用途并不局限于创建游戏方面。开发者还可以用它来创建图像编辑软件、动画工具、图表,甚至HTML5视频。它能改变背景图像的尺寸,创建能与界面保持固定比例的图像以及交互式的Logo,而且用这种技术创建的图像不会在放大时出现像素化。SVG的使用率在不断增加,前端开发者主要用它来创建可缩放的Web图形,如图7-13所展示的那样。

尽管SVG 的意图雄心勃勃,但由于它与DOM 的集成性以及所涉领域的宽泛,开发者们还没有大规模、深入地研究这门技术。如果SVG要想与Canvas竞争,那么就需要修改它的 API,使开发者能够更方便地利用 JavaScript。但现在就开始研究它也有好处,你会成为弄潮儿,等到SVG2.0面世后,就可以重点研究这项技术了。

在交互式应用中,一个很重要的因素就是音效以及为复杂动画添加视频。下一章将介绍HTML5的音频及视频API,从而为应用添加音频与视频。

查看各节中的标志可迅速定位上表中的各个主题。

第8章 视频与音频:在浏览器内播放媒体

本章主要内容

■解决视频所固有的跨浏览器与跨设备问题

■在不同的视频及音频格式间转换

■控制视频回放

■在浏览器中使用<canvas>元素进行视频后期处理

■将其他内容集成到视频回放功能上

显然,Web不只包含文本,但在HTML5标准问世之前,我们还没有办法利用HTML标准来以内置方式播放音频与视频。相反,浏览器只能利用第三方插件来解决这个问题。

这种情况现在完全改变了,快速成长的Web正在替代传统广播媒体。Netflix、YouTube、Spotify、last.fm以及Google Music 等Web 服务都在用在线播放来取代DVD 和CD。正是有了HTML5技术,视频与音频才真正成为Web的关键内容。不再依赖第三方应用来播放媒体,直接通过浏览器就可以实现:通过Web应用来控制并操控媒体。

本章将学习HTML5 的Media Element 接口,并创建一个视频点播及标注系统。著名的美国橄榄球教练及解说员约翰·麦登就是用的这种系统来进行解说点评的。用户能够通过我们这个应用,在播放中的视频上直接绘制图形。视频标注(telestrate)一词就是来源于电视体育广播界(televison + illustrate = telestrate)。

通过实现这个范例,我们能学到什么?

■学会如何利用<video>将视频插入到页面。

■借助Media Element接口,如何使用JavaScript来控制视频播放。

■如何使用<source>元素支持不同的浏览器所要求的不同格式。

通过本章,你将学会以下内容:

■构建基本的播放器架构;

■利用HTML5将视频嵌入到Web 页面;

■根据用户选择,使用HTMLMediaElement 接口来加载并播放视频;

■添加事件侦听器来提供用户反馈,启用UI 选项,并开始播放;

■使用<source>元素为不同浏览器提供相应可支持的各种格式的视频;

■借助HTMLMediaElement 接口,利用JavaScript 来控制视频;

■将处于播放状态的视频与其他Web内容组合起来。

下面,我们将逐步介绍应用所需的必备条件,然后开始创建这个基本的视频播放器。

8.1 利用HTML5播放视频

现在,对于任何浏览器来说,用HTML5标记在网页中插入视频就跟插入图像一样简单。在本节中,你将充分利用浏览器的内建支持来构建最简单的视频播放器。

首先,展示一下应用的最终效果,并介绍构建这个应用所需要的必备知识。然后,构建该应用的基本框架,使用<video>元素在网页中插入视频。

8.1.1 应用预览及构建引用所需的必备知识

本章所要创建的这个简单的播放器如图8-1所示。

这幅图展示出该播放器的4个关键组成部分。

■视频播放功能。图中展示出的正是美式足球运动中的一个场景。

■视频标注功能。图中标注了“HTML5 in Action”一行字。

■位于右侧的视频选择播放列表。

■位于视频播放器下方的控制视频回放的工具栏。

使用什么浏览器

本节内容适用于Chrome、Safari或Internet Explorer 浏览器。由于跨浏览器的视频文件格式问题,暂时不要考虑Firefox和Opera。稍后在8.1.3节,我们会再讨论这个问题,并通过一些技巧让Firefox和Opera也能正常播放视频。

准备工作

首先,需要从本书网站处下载范例视频,并从http://jQuery.com下载jQuery的最新版本。将视频文件放到工作目录中的同名目录下,将 jQuery 放在该工作目录下。还需要从https://gist.github.com/1579671下载关于requestAnimationFrame的polyfill,以备后续使用。在8.4.1节开始动画时,将会用到上述代码。

有了这些基础之后,就可以开始搭建基本架构了。

8.1.2 构建播放器的基本架构

代码清单8-1显示了应用的架构,它只创建了一个简单的布局,将视频播放器与播放列表用占位符代替,主要的一些部件以后再补足。

在工作目录中创建一个叫做index.html的HTML页面,将代码清单 8-1中的内容放进去。

代码清单8-1 index.html——播放器的基本架构

建立好基础之后,让我们开始添加一个视频,这是构建该应用的过程中比较有趣的一个环节。

8.1.3 使用<video>元素为页面加入视频

HTML5之所以引入<video>元素,在于简化在网页中嵌入视频这一任务,降低其使用难度,使得这一任务变得犹如在网页中嵌入图像一般简单。由于视频文件格式要比图像格式更为复杂,所以肯定会在处理视频格式时遇到更多的困难,但这个设计目标却算是达到了。图8-2 展示了在Google Chrome 上应用<video>元素的情形。

通过代码清单8-2所示的这段代码,就可以显示图8-2中所示视频。如你所见,它并不复杂。用这段代码替换代码清单8-1 中的第一个注释,然后刷新页面,就能看到图8-2所示的画面了。

代码清单8-2 index.html——嵌入视频

在这段代码中,共用到了4个属性:src、controls、width和height。表8-1对这些属性加以概述,附录B提供了更加完整的属性列表及相关说明。

对于本章所要创建的应用来说,仅仅显示一个视频是不够的,还需要能提供多个视频,并且还能根据用户指令进行视频切换,并对播放进行各种控制。为了实现这个目的,需要了解一下HTMLMediaElement接口。这是一个适用于<video>与<audio>元素的多种属性与方法的集合,能够实现播放、暂停及改变音量等一系列功能,下节将予以介绍。

为何不介绍音频相关内容

或许读者可能已经注意到,本章主要介绍了<video>元素,而对于<audio>元素所述甚少。这并非<audio>元素不重要,也不是因为它更复杂,其中原因只跟本书作为纸质媒体的特性有关。虽然书籍并不便于展示视频相关内容,但它更不便于展示音频这种不可见的东西。由于这两种元素共用着一个API(HTMLMediaElement接口),所以本章将重点介绍这个API。这两种元素的唯一差别在于可视化属性方面。<video>元素可以指定媒体的宽度与高度,而<audio>元素则没有这方面的需求。

8.2 利用HTMLMediaElement接口来控制视频

现在已经能够播放视频了,下面来实现一个播放器功能,让用户在视频列表中能够自由地选择视频进行播放。如图8-3所示,视频列表就在元素的旁边。

下面两节将进行以下5个步骤的操作。

■第1步:加载视频列表中的所有视频。

■第2步:选择某个视频开始进行播放。

■第3步:切换视频。

■第4步:使用事件处理器来更详细地处理视频的切换。

■第5步:为支持所有的浏览器,提供多种视频格式。

如前所述,本节将用到HTMLMediaElement 接口这个JavaScript API。在用到 HTML5技术的相关任务中,标记语言的能力到此为止了,接下来就是 Javascript 大显身手的时候了!

第1步:加载视频列表中的所有视频

首先,把所有视频都加载到一个播放列表中,以便当用户点击某个视频时,它就能够播放。代码清单8-3显示了这一播放列表所用的标记,用这些代码替换代码清单8-1中的第二个注释。在真实的视频应用中,播放列表几乎肯定是动态生成的,但本章不打算使用服务器端代码。

代码清单8-3 index.html——视频播放列表所用的标记

第2步:选择某个视频开始进行播放

要想实现用户点击视频列表中的某个视频,该视频就能播放,需要用到HTMLMediaElement接口的以下属性和方法,如表8-2所示。

第3步:切换视频

代码清单8-4所示的代码用于切换视频。如其所示,需要用到change_video函数,另外也用到了表8-2中的src属性和play()方法。将这段代码封装在<script>元素中,再将这个<script>元素插入到index.html的head部分的末尾。

代码清单8-4 index.html——用户点击播放列表切换视频

第4步:使用事件处理器来更详细地处理视频的切换

在上一步骤的代码中,设置了<video>元素的src属性,play()方法立即被调用。之所以执行得这么顺利,是因为所有的视频文件都相对较小,并且所有的内容都是从本地硬盘进行加载。如果视频文件较大,那么很可能当立即调用play()方法时,该视频文件并没有加载多少,从而无法进行播放,从而导致出现错误。更可靠的方法是在播放视频前,先等待视频加载完成。HTMLMediaElement接口包含一系列在媒体加载时触发的事件,如表8-3所示。

在通过网络加载大型媒体文件时,有足够的时间向用户显示所触发的事件的相应通知。本节中,我们将把事件处理器绑定到每一个会触发的事件上,并在发生canplaythrough事件时播放媒体。但下面先来介绍一下如何通过HTMLMediaElement接口获得一些与网络有关的信息。

利用.networkState和.readyState来确定媒体资源的状态

HTMLMediaElement接口含有两个便于确定媒体资源状态的属性:.networkState和.readyState。在构建真实的应用时,可以通过这两个属性获取一些信息,进而为所加载的媒体资源构建一种可视化的用户反馈形式,如进度条或进度表盘等。表8-4介绍了每种属性的取值。.networkState 类似于 XMLHTTPRequest 请求对象中的.readyState属性,而.readyState则与表8-3中所列的事件紧密相关。

当canplaythrough事件发生时,播放视频

代码清单8-5所示的这段代码介绍了如何使用HTMLMediaEvent接口事件,以及如何结合使用 networkState 和 readyState 事件。将该段代码插入到代码清单 8-4 中的$(document).ready处。

代码清单8-5 index.html——捕获HTMLMediaElement接口事件

试试看

除了视频能够自动播放之外,代码清单8-5所示的代码的执行效果跟代码清单8-4所示的代码的执行效果毫无二致,同样能切换视频播放。但如果打开浏览器的控制台,则能看到如代码清单8-6所示的输出结果(具体数值可能随浏览器的不同而有所差异)。

代码清单8-6 执行代码清单8-5后的控制台输出

记住,networkState: 1 代表处于NETWORK_IDLE 状态,readyState: 4 代表处于HAVE_ENOUGH_DATA状态。因为所有的视频文件都在本地硬盘,所以这里不需要考虑太多问题(虽然在IE中可能会看到networkState为2的情况)。如果有一些较大的在线视频,一些事件的取值也会有所不同。

阶段性成果测试

如果一直按照本章之前所建议的那样,使用Chrome、Safari或IE9这些浏览器,那么现在能看到一个简单的界面,可以在上面点击播放列表中的某个视频进行播放,具体界面应如图 8-4 所示。如果你的代码运行后出现了问题,请对比本章源代码中的 index-2.html的源码进行解决。

使用Firefox还是使用Opera?

如果尝试在Firefox或Opera中加载该页面,可能会看到一个灰色的页面,上面显示着这样一条提示:Video format or MIME type is not supported.(“不支持该视频格式或MIME类型。”),如图8-5所示。

虽然Firefox和Opera都支持<video>元素本身 [2],但它们却都不支持MP4视频格式,从而就造成了图 8-5 中这样的问题。但<video>和<audio>元素提供了一种变通方案:通过<source>元素来指定多种媒体文件。

8.3 利用<source>元素指定多种媒体格式

每个<video>元素都有多个作为其子元素存在的<source>元素。每一个<source>元素都指定了一个视频,浏览器会轮流尝试每一个视频,使用首先碰到的可支持格式的视频进行播放。如图8-6所示,在我们之前创建的视频播放器中,采用<source>元素而非<src>属性,视频播放器就能再Firefox中正常运行了。

第5步:为支持所有的浏览器,提供多种视频格式

现在就让我们来实现它。代码清单8-7所示的代码展示了适用于<video>元素的新标记,用到了一些<source>子元素,用下列代码替换之前工作文件中已有的<video>代码。

代码清单8-7 index.html——添加<source>元素

代码检查

现在最好先停下来,在浏览器中试试。本章应用的这一版本的html文件叫index-3.html,可在本章源代码中找到。如果你自己的代码出现任何运行问题,可以和这个文件相比对。

8.3.1 通过.currentSrc 属性获取视频类型

借助上文所述的新代码,Firefox 现已能够加载格式支持的视频了。不过,这给媒体点播器的功能带来了一个新的挑战。之前,可以通过设定.src属性来切换视频,但现在的情况则不同:需要根据浏览器选择播放的视频文件来设定.src属性。遗憾的是,不可能把所有的<source>子元素都用一个新组来代替,为了改变播放的视频,必须设定.src属性。

为了解决这个问题,需要引入另一个HTMLMediaElement接口的属性:.currentSrc。这一属性能提供当前所选媒体的文件名。

由于全部视频文件都是按顺序命名的,所以我们完全可以去掉播放列表中<li>元素内的文件扩展名。不从<li>元素中获取完整的文件名, change_video 方法能从.currentSrc 属性中复制文件扩展名,继而组合出选定视频的完整文件名。代码清单8-8采用的就是这个方法,提供了更新后的change_video方法。用这段代码替换掉之前文件中已有的相应部分。

代码清单8-8 index.html——使用currentSrc确定视频文件类型

针对 IE9 出现的currentSrc bug的解决方案

代码清单8-8非常简明直白,但并不适用于IE9浏览器。这个问题是IE9中的一个bug:一旦加入<source>元素,它就会比<src>属性以及<video>元素的currentSrc属性优先运行。这就意味着,在IE9中运行应用,将不可能通过点击来切换视频,只会看到第一个播放的视频不断地循环播放。

IE9 的另一个问题在于利用 JavaScript 更新<source>元素没有任何效果。如果使用了<source>元素,想在IE9中更新播放中的视频,那么只有一个解决方案:替换掉整个<video>元素,如下所示。

幸好,这个bug在IE10中得到了修复。所以为了避免再涉及这方面的API(更不要说这一方法在其他浏览器中还容易出现问题),本章后续内容将直接忽略这个问题。如果使用IE9,请选择下载文件中已经修补了IE9中问题的版本(文件名有IE9)。

现在我们的视频点播器已经能够正常工作了,但可能还存在以下问题。

■mp4 和webm这些格式的区别表现在哪里?

■为了支持所有浏览器,到底应该提供多少种视频格式?

■如果视频格式都不统一,又该如何切换它们?

下一节再来介绍改变视频格式的问题。现在,我们先来看看前两个问题,即每种浏览器所支持的具体视频与音频格式。表8-5做了总结。

*如果用户在Windows Media Player或QuickTime 中安装了一些编解码器,IE和Safari 相应地就能播放其他格式。当前Windows系统并没有兼容Ogg/Theora格式的编解码器。

+如果在QuickTime 中安装了一些编解码器,则Safari 能播放其他格式的音频。

如你所见,没有一种格式能够适用于所有浏览器。为了获得广泛的桌面支持,至少应提供两种媒体格式:对于视频,至少应提供WebM/VP8和MPEG-4/H.264;对于音频,应该至少提供MP3和Ogg。

对媒体格式的支持度似乎在HTML5中是一个见仁见智的问题,其原因如下面这则补充资料所示。

HTML5为何不强制一种所有浏览器都能支持的格式?

最初,HTML5规范指定把Ogg/Theora作为统一的视频音频格式组合。这主意似乎不错,因为它们都是开源的格式,编解码器也是免费的。但苹果公司和微软公司都拒绝采用这两种格式,它们更推崇 MP4/H.264 这一种组合。MPEG LA 有限责任公司管理着 MP4/H.264,销售编码解码器的专利使用许可证,并且它还是持有H.264编解码器相关专利的公司(苹果与微软就是这样的企业)的利益集团中的一员。

H.264 的支持者认为,就技术而言,Ogg/Theora 画质低劣,缺乏硬件支持(在带有低端处理器的需要电池的设备上,这一点十分重要)并且也很容易招致专利流氓的恶意诉讼,而MPEG LA公司却可以通过经济手段来解决影响H.264发展的一系列“潜水艇专利”问题。

Ogg/Theora 的支持者则认为,网络的开放性需要一个开放式的视频格式。由于下载 H.264解码器需要获得MPEG LA公司的许可,所以如果Firefox 包含H.264 解码器,那么Mozilla 就无法公开它的源码。谷歌采取的规避办法是:将浏览器分成两个部分,一部分是开发源码版(开源Chromium计划),一部分是闭源版。

因为浏览器厂商对标准应采用的视频格式有严重的分歧,另外也由于HTML5的目标之一即是记录当前已实现的方案,所以规范中不再指定使用具体的编解码器。在HTML的发展过程中,也的确存在这样的先例:<img>元素并没有指定浏览器具体应该支持哪些图像格式。虽说如此,但情况也在慢慢好转:谷歌后来发布了开源、带有公开许可证的 WebM 格式。谷歌手握业内一流的视频网站YouTube,又是Android移动操作系统的提供商,相信它已做足准备逐渐来取代H.264的商业地位。

8.3.2 媒体格式的转换

为实战考虑,你只需知道如何将一种媒体格式转换为另一种格式即可。transcoder 这款工具就能实现不同媒体封装格式与编码之间的转换。另外,有一些在线或可下载的工具也能转换个别媒体格式的文件;在附录J的链接与资源中列出了这样一些工具。但要想批量处理大量的文件,还是得使用命令行工具。附录H就介绍了如何通过ffmpeg 将本章所用的视频文件转码。

现在,多亏有了<source>元素,任何浏览器,只要它支持<video>元素,就都能播放视频。另外,我们还了解到各个浏览器所对应的视频格式。下面就来实现视频标注功能,它能让我们直接在播放中的视频上进行各种绘画与标注。

8.4 结合用户输入与视频,实现视频标注功能

如上文所述,借助视频标注(telestrator)功能,用户可以直接在播放中的视频上进行标注或绘制,以便为电视观众进行解说。要想在应用中实现该功能,需要将视频与其他图像数据结合起来,为此就需要用到<canvas>元素。第6章中已经详细介绍了 Canvas 技术,并利用其绘图功能开发了一个交互式游戏。而对于本章而言,则将只利用 Canvas 的图像数据操作功能,将图像和其他内容与视频源结合起来。

本节主要内容

■如何使用<canvas>元素来播放视频。

■如何创建视频播放操作控件(因为<canvas>元素渲染的是视频及图像数据,而非<video>元素)。

■如何将canvas 中的视频和其他内容(如图像)结合起来。

■如何利用<canvas>执行基本的图像处理操作。

■如何获取用户的绘制(视频标注功能),并将它们添加到播放的视频当中。

实现视频标注器需要完成以下三组任务。

先来看看如何通过<canvas>元素实现视频的播放。

8.4.1 使用<canvas>元素来播放视频

要想实现视频标注功能,首先要实现的就是修改正在播放中的视频。要想实现这一点,可以通过在页面上叠加一些元素,并在指定的时间点上,隐藏并显示相应的元素。如果你坚持使用插件来渲染视频,那么唯一办法就是利用HTML来修改视频。通过<video>元素,视频数据可以如图像一般被获取。你可以访问视频的每一帧,将它看成图像数据。然后再使用<canvas>元素抓取图像数据并显示它。

第1步:添加<canvas>元素

代码清单8-9展示了标记的基本设置。<style>元素应该放在文档的头部信息处,当然,也可以把这些规则放在已有的<style>元素中。div会在相应位置替代已有的元素。

代码清单8-9 index.html——使用<canvas>元素播放视频

第2步:抓取并显示图像数据

接着,我们需要侦听<video>元素的play事件,利用它来抓取视频帧,并在画布上进行渲染。代码清单8-10中的$(document).ready将代替之前在代码清单8-8中的已有函数。

代码清单8-10 index.html——调整draw()函数,以便使用<canvas>元素

通过<canvas>元素,我们现在能够播放视频了,但缺了一些东西。<video>元素所带的播放控件不起作用了,因为视频是通过<canvas>播放的。下面就来创建你自己的控件。

8.4.2 创建自定义的视频播放控件

本节将创建一个简单的按钮菜单来控制视频播放,如图8-7所示。显然我们不打算进行过多的设计,实现基本功能就够了。

第3步:添加标记,实现视频播放器控件

我们利用代码清单 8-11 所示的代码添加这些控件:返回起始点、慢速播放、暂停、正常播放、快速播放。将这些代码直接放在<canvas>元素的后面。

代码清单8-11 index.html——创建视频播放器控件

想让这些按钮真正起作用,还需要了解一下HTMLMediaElement接口的属性和方法,如表8-6所示。

这些属性及方法足以实现这5个按钮。在代码清单8-10中添加的$(document).ready函数中,按钮菜单需要绑定一个事件处理器,如代码清单 8-12 所示。它在这个函数中的位置是随意的,但应在v变量声明之后。如果觉得没有把握的话,可以把它放在该函数的末尾处。

代码清单8-12 index.html——控制菜单的事件处理器

代码检查

现在我们恢复了视频播放器的一些基本功能。本章到目前为止的代码都在下载源文件中的index-5.html文件中,你可以把自己写的代码跟这个文件中的代码做一下对比。你还可以考虑一下如何使用.currentTime和.duration,并结合<meter>元素(参看2.3.3节)来重新生成进度条。如果不打算这样做,就可以去学下一节内容,看看如何通过<canvas>元素实现播放时的各种效果。

8.4.3 控制播放中的视频

通过<canvas>元素播放视频不仅仅是复制<video>元素的行为,而且能够处理视频输出。本节将学习基本的视频处理技术,最终效果应如图8-8所示。后续我们还将用这种技术来创建视频标注器(telestrator)。

要想控制播放中的视频,需要完成第2组任务。

■第2组任务:控制播放中的视频。

♦ 第1步:为视频添加帧图像。

♦ 第2步:在canvas 中调整帧与视频的合成方式。

♦ 第3步:调整视频的不透明度。

♦ 第4步:调整播放的视频的灰度。

第1步:为视频添加帧图像

第6章介绍了如何利用Canvas绘制图像,本步骤所用的基本方法也是一样的。首先,需要在页面中插入一副图像,把它放在<#player>元素中的任何位置处都可以(利用display: none 可以将其隐藏):

为了能让用户开启与关闭帧,需要在菜单中添加一个按钮(参见代码清单8-11):

由于它在菜单中,所以可以利用现有的鼠标点击处理事件代码,switch余下的case语句如代码清单8-13所示。在完成之后,把结果代码再添加到代码清单8-11的事件处理器中。

代码清单8-13 index.html——帧按钮的事件处理器

而在接下来的代码清单8-14中,我们会通过调整draw()函数来绘制帧。

代码清单8-14 index.html——调整draw()函数显示帧

现在,点击一个按钮,就能把一个帧放在视频上方。接下来学习如何在 canvas 中调整帧与视频的合成方式。

第2步:在canvas中调整帧与视频的合成方式

默认情况下,Canvas 层中绘制的对象都是层叠的,新绘制的对象覆盖了位于其下的对象。但是,利用绘图环境的 globalCompositeOperation 属性能改变这种层叠机制。

图8-9展示了可用的合成模式。

为了方便试验,我们在代码清单8-15中创建了包含全部合成模式的<select>元素。合成操作的对象共有两种。

■目标,已经绘制的对象。

■源,打算绘制的新对象。

在 index.html 中添加代码清单 8-15 所示的代码(把它放在代码清单 8-11 所添加的<menu>元素后面)。

代码清单8-15 index.html——展示各种合成模式的<select>元素

现在应用就能响应变化了。将<select>绑定到事件处理器上。用代码清单 8-16 所示的代码替换已有的draw()函数。

代码清单8-16 index.html——在draw()函数中改变合成模式

因为视频往往是由完全不透明的图像组成的,所以它并非是试验合成模式最理想的格式。在该例中,它占据了所有的像素。但是这个简单的范例可以让你试验这些功能,以便将来在自己的项目中运用它们。

第3步:调整视频的不透明度

不透明度是用.globalAlpha属性设置的,取值介于0~1之间。CSS规定,1表示完全不透明,0代表完全透明。在我们的应用中,可以让用户利用数值输入来设定该值,把下列代码添加到<menu>元素后面:

如之前一样,可以给这个数值输入添加一个事件处理器,将结果通过一个变量反馈给draw()函数。下列代码包括了获取不透明度的代码以及新的draw()函数。用代码清单8-17所示的新代码替换代码清单8-15中的draw()函数(将合成模式绑定到$('select')上)。

代码清单8-17 index.html——在<draw>函数中改变不透明度

第4步:调整播放中视频的灰度

借助.getImageData和.putImageData方法,<canvas>元素可以对图像进行一般的处理。这两个方法能够直接访问构成画布的像素数组。一旦获得像素,就能用JavaScript 实现标准的图像处理算法。下面我们将用 JavaScript 实现了一个调整图像灰度的算法,代码清单8-18所示的这段代码可以放在<script>中的任何位置处。

代码清单8-18 index.html——改变图像灰度的函数

注意 上面代码的 grayscale 函数改编自 HTML Rocks 的关于图像滤镜的文章,详情可参见:www.html5rocks.com/en/tutorials/canvas/imagefilters/。

既然已将复杂的数学算法都封装在了一个通用函数中,接下来就只需把它应用到画布上就可以了。在代码清单8-19中,从draw()函数中调用grayscale()函数。另外,还需要在代码清单8-14中所创建的framed旁边声明一个grayed变量,并将其初始值设置为false。

代码清单8-19 index.html——利用draw()调用grayscale()

注意 当通过file://URL访问该例时,getImageData()方法会返回一个安全错误。如果遇到问题,可以尝试用本地Web服务器来访问该文件。在Chrome中,当SVG图像绘制到画布上,再调用 getImageData(),就会触发一个安全冲突,这是一个 bug。可以通过[https://code.google.com/p/chromium/issues/detail?id=68568](https://code.google.com/p/chromi um/ issues/ detail?id=68568)来升级Chrome。

我们还需要在按钮菜单中添加一个Grayed按钮,在switch语句中还要再加一个事件处理器。这和代码清单8-13中创建的Framed按钮非常类似,所以就不重复这段代码了。

代码检查

迄今为止,源代码中的 index-6.html 是能够正常运行的代码(如果用的是 IE9,则需要参考8.3.1节)。

注意 图像处理针对的是每个像素而进行的,视频质量越高,实现这项任务所需的计算能力就会越高。除非制作能够预览视频处理结果的应用,否则一般应该把实时视频处理这项任务放在服务器端来完成,用户会因此而感激你的。

8.4.4 实现视频标注功能

上一节介绍了如何用<canvas>元素来渲染视频,并将帧图像叠加在视频之上。这一节可以利用这些技术实现视频标注功能,如图8-10所示。

这就需要完成第3组任务。

■第3组任务:实现视频标注功能。

♦ 第1步:捕捉鼠标移动。

♦ 第2步:在视频上显示捕捉到的路径。

♦ 第3步:添加一个“clear”按钮(“清除”按钮),使用户可以去除视频上原有的标注,重新开始标注。

第1步:捕捉鼠标移动

为了实现鼠标移动,$(document).ready函数就必须包含代码清单8-20所示的代码。添加的位置并不重要,在初始化声明及draw()函数之间。

代码清单8-20 index.html——捕捉鼠标移动

注意 为了简化draw()函数,在这节中我们去掉Grayed和Framed这两个按钮的实现代码。将它们保留在代码中不会造成任何麻烦,但在根据本节的指示替换或包含代码时,要记住这一点。

第2步:在视频上显示捕捉到的路径

下一步就是要显示 draw()函数中的路径,代码清单 8-21 所示的代码又是一个新的draw()函数。

代码清单8-21 index.html——修改draw()函数以显示路径

第3步:添加一个Clear按钮(“清除”按钮),使用户可以去除视频上原有的标注,重新开始标注

最后需要添加一个 Clear 按钮,以便用户可以去除视频上原有的标注,重新开始标注。把这个按钮放在控制菜单中,添加下面这行代码即可:

switch的新case语句如代码清单8-22所示。

代码清单8-22 index.html——清除标记

现在,一个功能完善的视频点播及标注器就做好了,你可以在选择的视频上面添加鲜艳的黄色标注。如图 8-11 所示,笔者自不量力地想模仿一下约翰·麦登的解说风格。点击Clear按钮就能取消这些标注。

代码检查

本节所对应的代码是 index-9.html。另外这里还有一个 index-10.html,这个文件里面含有上一节为了简化而删除的Grayed和Framed按钮。

8.5 小结

借助HTML5技术,在网页中添加音频与视频变得跟添加图像一样简单。不用担心浏览器对于媒体格式的不兼容性,目前完全可以自如地在多种文件格式间进行转换。本章还介绍了如何利用JavaScript来控制媒体元素。利用HTML5来插入视频的额外优势还在于,你可以用它来作为其他内容(特别是<canvas>元素)的输入。随后,本章还探讨了如何将视频与图像相结合,以及如何将视频与实时绘制行为相结合的技术。希望读者不仅能够学会这些技术,还要想想如何在自己的Web应用中添加以及播放媒体。

下一章,我们将学习WebGL技术,用它来制作激动人心的视觉特效。WebGL格式能让你通过JavaScript来直接访问计算机的图形硬件,从而有助于实现真实的3D游戏和数据可视化效果。

查看各节中的标志可迅速定位上表中的各个主题。

第9章 WebGL:3D 应用开发

本章主要内容

■开发WebGL引擎

■与显卡通信

■创建3D 图形

Web开发者多年来一直努力解决Web在3D表现上的局限性,以创造效果更好的交互式游戏、课件以及信息图。过去人们用Unity、Flash 以及Quicksilver 创建了Google Maps以及其他一些在线的3D探索应用。这些插件都很方便实用,但插件的更新却需要受制于浏览器厂商,而且往往缺乏硬件加速支持。为了打破这些局限性,Khronos Group 开发了WebGL(Web Graphics Library)。无需插件,只需利用WebGL,开发者就可以开发精美的3D应用(如图9-1所示的X-Wing)。一些开发者还利用WebGL做出了一种绘画界面,用户可以在其中创建二维图像,然后在3D空间内旋转这些图像。

警告 因为WebGL建立在Canvas API的基础至上,所以本章所介绍的一些概念性知识基于第6章的应用。在学习本章内容之前,读者必须已经非常熟悉Canvas和JavaScript面向对象编程(OOP)技术。如果做不到这点,请重新复习一下第6章的内容。

当然,借助其他教育资源,完全可以学到基本的3D编程知识。但现在不用那么麻烦了,本章将通过介绍3D编程的概念、数学原理、示意图等内容,把所有必需的3D编程方面知识都灌输给你。通过逐步解析一个3D 游戏(名叫Geometry Destroyer)的创建过程,你将学会如何实际运用这些知识。

为何要开发 Geometry Destroyer

很多讲解WebGL基础的在线教程往往只会教你制作简单的demo,而本章这个范例却会教你如何从零开始创建一个非常真实的应用。通过学习这个范例,你将学会以下这些关键知识点。

■创建可重用的WebGL 类。

■生成并维护大量的WebGL实体。

■利用可重用代码,创建不同的形状缓存。

■在2D与3D空间中利用资源。

■在3D空间内利用粒子生成来处理2D碰撞侦测。

本章将着手从零开始创建一个WebGL游戏引擎,通过其构建过程,你将学会管理3D资源的基本知识。

在创建出引擎的实体管理功能来控制可视化对象之后,本章将逐步介绍利用 WebGL发起请求,处理返回数据,以及显示得到的3D形状。在本教程的最后部分,还将介绍如何在3D空间内利用2D形状创建游戏的玩家角色和子弹。另外,通过拓展2D绘制理念,本章将创建3D的旋转立方体。在被子弹击中后,该立方体会碎裂成许多更小的立方体和正方形。

学完本章后,你就会了解如何用WebGL创建并管理3D数据。除此之外,还能顺带学会如何自制一个基本的WebGL引擎,并创建一个有趣的游戏。下面就先来介绍一下用于实体管理的各个引擎部分。

需要现成的WebGL引擎?

如果想赶快开发出来一个 WebGL 应用,建议下载 CopperLicht,下载页面为 http://www. ambiera.com/copperlicht/download.html。下载完后,还可以看看文档和demo来学习这个引擎,这些资源的地址是www.ambiera.com/copperlicht/documentation/。对于其他一些项目(交互式数据展示、建筑展示、动画视频和地图等),可以到GitHub上下载Mr. Doob的three.js:https://github.com/mrdoob/three.js。在http://mng.bz/1iDu上,你能找到先关的学习范例、文档及使用技巧。

9.1 创建一个WebGL游戏引擎

虽然使用现成的引擎能节省很多时间,但它若不支持你所需的功能,就会出现一些问题。如果时间充裕,我们还是建议构建自己的JavaScript应用引擎,这样一来,你不仅能够学会知识,还能将这些可重用的代码用于将来的项目中。

本节所涉及的可重用的WebGL概念

■如何构建一个能够创建可视化输出的引擎。

■如何利用John Resig 的脚本创建简单的JavaScript继承。

■从哪里获取资源,如何使用资源,从而使WebGL 代码更容易编写。

■处理碰撞、删除,以及其他实体管理任务的方法。

例如,本章在创建Geometry Destroyer(图9-2)时所用的技术,完全可以转而应用到其他的可视化API上,比如Canvas和SVG。

警告:开发引擎可不是简单活儿。如果你不想反复通过复制粘贴大量的JavaScript代码来创建3D引擎,那我们建议你读读9.1节和9.2节,然后就从本书源代码中找到整个引擎的现成源文件。你可以研究一下这份源代码,然后直接去看9.3节就可以了。如果你喜欢冒险,并想创建属于自己的应用,那就更好了!和我们一起从头开始创建这个游戏吧!

浏览器使用注意:使用Chrome和Firefox来运行本章范例应用

无论你是否要开发这个游戏引擎,我们建议你使用Google Chrome 或Firefox 的最新版本。其他浏览器并不支持先进的3D功能或必需的图形加速功能。虽然有些浏览器宣称“支持”WebGL,但“支持”并不一定意味着已经实现了所有的功能。

IE能用WebGL吗?

想在旧版的IE中使用WebGL?名叫IEWebGL的插件(http://iewebgl.com)。从IE6到IE10,它都能支持。由于它是一种可下载的执行文件,所以你可以将它提供给那些使用 IE 的用户。注意,它并不适用于本章这个范例,但和Three.js这样的库却能很好地结合,可在上面的链接站点处查看到完整的列表。

引擎的创建工作需要以下7个步骤。

■第1步:创建(或查看已下载的)JavaScript 代码和index.html。

■第2步:创建style.css。

■第3步:实现能够节省时间的脚本。

■第4步:创建基本的引擎逻辑。

■第5步:管理实体存储。

■第6步:创建带有3D 数据的形状实体。

■第7步:增加可重用方法,以便加速编程并使文件易于维护。

下面就让我们来开始吧。

9.1.1 设置引擎布局

创建一个WebGL引擎,需要用到几种不同的开发工具,另外还需要一种文件结构(如图9-3所示)。

目前,你完全可以事先按照图9-3中的层级关系建立起空的文件夹与文件,或者也可以在学到这些内容的时候再来创建。JavaScript 文件夹(名为 js)包含着该引擎的所有文件,里面包含着一个run.js文件和一个名为engine的文件夹。我们将引擎内容与其他内容相隔离,目的是为了让整个文件结构更为清晰。

关于显卡的一个警告 请注意,并不是所有显卡都能支持 WebGL。如果你在自己的计算机上使用的浏览器是Chrome或Firefox的最新版本,但却无法运行本章所创建的应用,那么只好请你重新换台机器再试一下。如果你还是运行不了这个应用,我们对此也深表歉意——很多开发者也一直被这个问题所困扰着。

第1步:创建(或查看已下载的)JavaScript代码和index.html

按照代码清单9-1所示的代码,创建一个名为index.html的文件,作为所有JavaScript代码的运行基础。之所以加入<canvas>标签,是因为WebGL运行在CanvasAPI基础之上。

代码清单9-1 index.html——创建引擎的HTML 文件

能在 WebGL应用中使用 2D Canvas 吗?

不行。2D Canvas 和WebGL API 不能在同一绘图环境中出现。不过,可以使用两个<canvas>元素来创建两个不同的绘图环境,然后通过CSS,让其中一个位于另一个的上方。

第2步:创建style.css

由于在WebGL中创建文本比较困难,所以使用HTML标记中的文本。游戏简介界面与游戏开始界面已经放入了index.html,但它们还需要对样式进行一些处理(如图9-4所示)。

将代码清单 9-2 所示的代码放入一个名叫 style.css 的新文件中,将文件放在含有index.html的文件夹中。

代码清单9-2 style.css——添加样式

第3步:实现能够节省时间的脚本

我们先来创建一个名为js的文件夹,它将包含所有的JavaScript代码。接着,在其中创建一个run.js文件,用来包含所有的运行代码。在run.js旁边创建一个engine文件夹。在该文件夹中,再创建一个名为assets的文件夹。下面将把4个用来节省开发时间的脚本放入这个文件夹中。

引擎的配置及正常运行需要以下4个外部文件。

■Paul Irish位于animation.js 中的requestAnimationFrame()。

■John Resig的类扩展脚本classes.js 的一种轻微修改版本。

■变换矩阵库sylvester.js。

■webgl_util.js 的辅助函数。

在此后的开发过程中,我们将详细介绍每个文件的作用。

Paul Irish的requestAnimationFrame()

本章打算利用最好的动画实践(就像我们在第6章中所采用的那些实践一样)来配置引擎,这些最佳实践包括:

■利用requestAnimationFrame()取代setInterval,来解决移动兼容性,防止在另一个标签页中更新,防止帧速率波动;

■在其他浏览器中测试 requestAnimationFrame(),利用 Paul Irish 提供的polyfill,为像IE8这样的老式浏览器提供支持。

下面就来构建依赖关系,或者说引擎依赖的相关文件。在asset文件夹中,利用Paul Irish 的 requestAnimationFrame()(http://mng.bz/h9v9)创建一个名为 animation.js的文件。代码如代码清单9-3所示。

代码清单9-3 animation.js——请求动画及间隔时间

John Resig提供的一个简单的JavaScript继承

这个引擎需要你能创建一种能够立即修改、调整及继承的对象,所以需要实现一种可扩展的类。问题在于,这种类往往需要更为健壮的库(prototype.js),因为 JavaScript本身并不支持它们。为了限制引擎文件的体积和依赖关系,我们对John Resig所提供的一个简单的JavaScript继承脚本(http://ejohn.org/blog/simple-javascript-inheritance/)稍微修改了一下。将代码清单9-4中的脚本放到assets文件夹中的class.js文件中。

代码清单9-4 classes.js——JavaScript继承

想要更多的JavaScript脚本

如果想学习更多 JavaScript 基于原型的继承方面的知识,可以看看 John Resig 和 Bear Bibeault合著的SecretsofJavaScriptNinja(Manning,2012)。这本书介绍了很多非常好的技术,有助你更好地利用库,解决跨浏览器的相关问题,以及如何维护代码的知识。

sylvester.js

创建3D 对象需要向显卡提供一些打包的矩阵信息,比如[0 1 3 0],但是JavaScript却没有内建处理这种信息的工具。当然,我们可以自己从零开始创建一个矩阵处理库,但那要费很多时间,所以我们利用现成的sylvester.js来处理矩阵。从http://sylvester.jcoglan. com下载该脚本的最新版,解压缩,然后把sylvester.js文件放进assets文件夹中。

webgl_util.js

最后,我们还需要用到 webgl_util.js。它可以帮我们生成透视,处理矩阵,等等。我们衷心希望能向这个杰出脚本的作者致谢,但正像Mozilla声明的那样:“似乎没人知道它的作者。”从http://mng.bz/P7Vi处获取该文件并把它放入assets文件夹中。

且慢!说好的“自定制引擎”呢?

前文曾经说过,要从零开始创建一个WebGL引擎,那为什么还要用到上面这些外部的资源呢?这是因为,事实上,我们没有足够的时间来完全地自制引擎。要想逐步讲解一个完整引擎的开发过程,至少需要100页,因此我们觉得,最好能用一些现成的脚本来简化这个过程。希望你能同意我们的观点。

9.1.2 创建、改变及删除对象的工具

所需的资源已经就位,现在就让我们来开发引擎吧。

第4步:创建基本的引擎逻辑

使用代码清单 9-5 所示的代码来创建第一个引擎文件,在 js/engine 文件夹中创建core.js。这些代码能够判断浏览器是否支持WebGL,对WebGL进行基本配置,创建一个侦测碰撞的辅助方法,并且为后面的代码预先留出位置。

代码清单9-5 core.js——引擎初步架构

第5步:管理实体存储

接下来,我们需要管理实体存储,并创建一个用于清除被删除的实体的墓地。利用代码清单9-6所示的代码来完成core.js的实体管理,在已有的gd.core对象中。稍后,当你编写run.js时,这些方法可以极大地便利实体的。

代码清单9-6 core.js——引擎实体管理

第6步:创建带有3D数据的形状实体

你需要用一种可扩展类来创建含有 3D 数据的实体。这里需要用到之前用过的 John Resig 的简单的 JavaScript 集成脚本,并结合使用一种模板对象。这种模板对象其实是游戏中所有可视化资源(比如玩家、敌人以及粒子)的一种模型。利用代码清单9-7来创建一个template.js文件,把它放到含有core.js的文件夹中。

代码清单9-7 template.js——实体默认模版

第7步:增加可重用方法,以便加速编程并使文件易于维护

前面步骤所介绍的代码并没有直接创建任何3D图形,但它们是3D编程的一个必不可少的组成部分。再忍耐一会儿,待我们介绍完下面这个代码段之后,就开始讲解WebGL的3D编程技术。

这里要创建一个game.js文件,其中含有几个常用方法,可加速我们的开发。这些方法能缩减run.js文件尺寸,并使其易于维护。将代码清单9-8所示的代码放进engine文件夹下的game.js文件中即可。

代码清单9-8 game.js——实体辅助函数

① John Resig的博客“Partial Application in JavaScript”最后更新日期为2008年2 月,http://mng.bz/6SU0。

② “Animating objects with WebGL” Mozilla Developer Network,最后更新日期为2012年8月7日, http://mng.bz/O5Z2。

将一切都设置正确后,打开 index.html,浏览器控制台可能会有两种显示,或者毫无错误,或者run.js并不存在。如果之前创建了run.js,就不会出现如图9-5所示的错误。

现在建立好了引擎机制,下面就来把对象的3D数据发送给用户的显卡,并显示返回信息。

9.2 与显卡通信

当在线标准的发展如火如荼之际,计算机图形技术也正处于不断发展之中。在用于创建3D应用的计算机图形API库领域中,OpenGL和Direct X是相互竞争的两大重要选手。它们之间的主要差异在于,OpenGL 是开源的,而Direct X 则是专有的,受到相关版权的保护。正是由于OpenGL开源这一特点,才使得它在Web领域的分支WebGL,越来越多地受到业界的重视和大力支持。

注意 在此,我们要非常感谢 Mozilla 提供的关于 WebGL 的教程(https://developer. mozilla.org/en/WebGL),以及Learning WebGL提供的教程(http://learningwebgl .com)。本节的代码正是取自于上述资源。感谢Mozilla和WebGL!

OpenGL是一个跨平台的库,适用于Mac OS X、Unix/Linux以及Windows多种平台。它允许图形硬件的底层操作。WebGL源于OpenGL ES(用于嵌入式系统的OpenGL),是OpenGL在移动设备上的一个分支库。不过,虽然WebGL在浏览器上渲染3D数据这一能力貌似强大,但这种做法着实也违反了网页不应直接访问设备硬件的Web安全模型,容易造成一些安全问题。比如,通过Web页面,有人就能让你的显卡过热而起火,或者偷取显卡内存中的资料,再不就是直接发动一场DoS攻击 [3]。为了防止这些情况的发生,很多浏览器都额外添加了一些安全特性,但效果也只是“有望能防止这些损害和入侵”而已。就学习目的而言,本节将在没有攻击的假定条件下进行学习与探讨。

本节主要学习内容

■了解WebGL 对计算机内部数据的处理。

■创建着色器数据和存储。

■创建并存储带有缓冲区的shape 数据。

■利用矩阵,将装配好的3D数据显示到屏幕上。

■通过使用一些脚本,便于矩阵的编写。

下面,先来介绍一下WebGL是如何渲染数据的。

9.2.1 有关显卡的一些基本知识

可以先考虑下这个问题:就我们所要创建的游戏来说,用户的浏览器是如何处理并显示游戏对象的3D数据呢?请看一下图9-6。

如图9-6所示,当把实体的3D数据❶发送到显卡时,这些数据起初是以数组形式❷(计算机数据)存在的,随后就会被GPU(Graphics Processing Unit,图形处理单元)转换成顶点缓冲区❸(更多数据)。在这一渲染阶段,需要更多的信息来装配3D形状(比如说缓冲区变量)。在处理完顶点缓冲区后,这些数据就会通过顶点着色器❹的处理,生成屏幕位置和颜色信息。然后,GPU通过三角形装配器❺将3D数据进一步处理成三角形片段,然后再将其送入光栅化程序❻,清除掉形状当中不必要的视觉数据,生成像素片段,平滑彩色表面。随后,片段着色器❼会继续处理形状数据,为每一像素输出色彩值及深度值。最后,通过帧缓存器❽将3D数据显示到用户屏幕上。

3D图形怎么会跟三角面扯上关系?我不太明白……

当你尝试创建带有二维表面的形状时,通常会先创建一个矩形表面。但矩形表面却并不是最基本的形状,要想用很多表面拼合成人脸或者球体这种复杂的三维形体,矩形表面并不是很好的选择。三角面就能很容易做到这一点,它们可以轻易地拼合成任意形体。要想大体了解一下三角面在3D编程领域中的作用与意义,可以看一下Rene Froeleke写的这篇文章:"Introduction to 3D graphics",该文的链接为http://mng.bz/STHc。

如果想更详细地了解一下WebGL处理数据的方式,建议阅读Opera的相关说明,链接为:http://mng.bz/4Lao。本章对这部分内容的介绍算不上很深入,因为我们不想让读者感到厌烦。

继续介绍引擎

当前,引擎并没有和显卡进行通信。要想实现这一点,需要按下列两组步骤来做。

等到做完这些任务后,就能开始编写游戏了。

9.2.2 创建 3D数据着色器

在开始第1 组任务之前,先介绍一篇由Jacob Seidelin写的关于WebGL的参考资料, http://blog.nihilogic.dk/2009/10/webgl-cheat-sheet.html。文中将 WebGL绘图环境对象的所有方法按照着色器、缓冲器等类别进行了分类。在学习后面内容时,这个备忘录会非常有用。

HTML与HTML

本章中大量用到了“着色器”一词,好像挺时髦的。很早以前,这个词指的是为形状或形体着色,但现在它的含义更为广泛。通过 GPU 编程技术,着色器可以完成视觉元素转换、像素渲染,以及一些视觉特效的制作,比如实现模型或图像当中应有的光照效果。

第1步:通过OpenGL ES 创建并设置颜色、顶点及形状的着色器

为了启动着色器,gd.core.shader.init()需要调用gd.core.shader.get()和 gd.core.shader.store()来获取着色数据。除此之外,你还需要用一种神秘的语言OpenGL ES(参看下文补充资料中对OpenGL ES的介绍)来写一些代码,并把这些代码放到HTML文档中。把代码清单9-9所示的这些代码放在index.html中JavaScript脚本的前面。注意,如果放错了位置,不是紧挨着放在JavaScript脚本的前面,游戏很可能会加载失败。

代码清单9-9 index.html——颜色、顶点、形状的着色器

OpenGL ES着色器语言参考资料

OpenGL ES是OpenGL 的一个子集,主要应用于嵌入式系统,比如手机、游戏主机以及类似设备。The Khronos Group提供了一则用于WebGL 的OpenGL ES 着色器语言的参考备忘录。这份pdf非常有助于编写自己的着色器脚本,它的下载链接是:http://mng.bz/1TA3。

第2步:设置从DOM提取到的着色器

配置好着色器脚本后,需要通过 JavaScript 来处理它们。在 core.js 中,将gd.core.shader.init()替换为代码清单9-10所示的这些代码。

代码清单9-10 core.js——设置着色器

第3步:从DOM中提取着色器数据

在上一个代码清单中,gd.core.shader.init()访问了放置在 index.html 中的shader-vertex和shader-fragment脚本。通过从DOM中提取,gd.core.shader.get()获取并处理着色器,并返回一个经过编译的数据包或者是一个出错信息。gd.core. shader.init()接着处理,把DOM结果连接到一个程序中。该程序在一个store方法中设置了矩阵、片段以及颜色。最后,所有的残留图像数据都被删除了。在core.js中,利用代码清单 9-11 所示的代码替换 gd.core.shader.get()与 gd.core.shader. store(),来完成着色器的加载任务。

代码清单9-11 core.js——提取着色器

9.2.3 为形状、颜色及维度创建缓冲区

准备好了着色器数据,现在为形状、颜色及维度创建缓冲区。有趣的是,每一个对象都有自己的一个独立的缓冲区。

第4步:创建实体的shape、color以及dimension缓冲区

要想缓冲数据,打开template.js,利用代码清单9-12所示的代码将以下3个方法添加到Entity对象中:gd.template.Entity.shape()、gd.template.Entity.color()和gd.template.Entity.indices()。

代码清单9-12 template.js——缓冲区配置

为了使用刚才所创建的这些缓冲区方法,需要在创建新实体时,手动地调用this.shape()、this.color(),可能还需要用到this.indices()。本章稍后会在编写 run.js 时详细介绍如何使用这些新方法。为了输出创建的缓冲区数据,你需要配置gd.core.draw()。

9.2.4 在屏幕上显示形状数据

通过gd.core.draw(),可以遍历gd.core .storage.all 中当前所有的实体。每个实体都要分3个步骤(因此共有3部分代码)来处理,所以对于下面要介绍的这3个代码清单,一定要保证每一个都能接续上前一个,否则就根本不能运行。同时要提醒的是,现在进行的是第2组任务。

■第2组任务:利用矩阵及绘制形状。

♦ 第1步:利用矩阵与缓冲区输出可视化信息。

♦ 第2步:绑定及绘制形状。

♦ 第3步:侦测碰撞,去除相关实体。

♦ 第4步:添加矩阵辅助函数,简化矩阵交互。

♦ 第5步:添加Vlad Vukićević提供的用于旋转的 WebGL辅助函数。

第1步:利用矩阵与缓冲区输出可视化信息

打开core.js,利用代码清单 9-13 中的代码替代gd.core.draw()。该清单中的代码会将canvas中之前所有的绘画数据都予以清除,并设定当前的视角,将所有实体都绘制到存储区中。该代码会对所有实体执行相应的更新和旋转逻辑(如果这些逻辑已存在的话)。注意其中的for循环,在接下来两段代码清单中,还要继续填充这个循环函数(一直到代码清单9-15)。①

代码清单9-13 core.js——绘制形状(1)

① Weisstein and Eric W.,“IdentityMatrix,”MathWorld,Wolfram Web Resource,http://mng.bz/CO1M。

第2步:绑定并绘制形状

设置好矩阵并应用了旋转之后,需要输出当前3D对象的缓冲区信息。绑定3D数据,然后通过 gd.gl.vertexAttribPointer()将其输出,该方法会将绑定的缓冲区数据传送下去。使用代码清单9-14中的内容继续完善gd.core.draw()方法。

代码清单9-14 core.js——绘制形状(2)

注意 这时刷新浏览器的话,还看不到任何3D模型。耐心一点,不要沮丧。下面通过引擎的draw循环,将输出3D模型,到那时你就能看到精彩的成果了。

第3步:侦测碰撞,去除相关实体

以上虽然输出了3D对象,但还需要在cp.core.draw()中添加下面这些代码。如代码清单 9-15 所示,它将添加一种优化过的碰撞侦测方法,正确判断碰撞实体是 a(友方)还是b(敌人),同时要清除实体墓地。

代码清单9-15 core.js——绘制形状(3)

阶段性测试

现在运行一下 index.html,看看浏览器控制台上的显示信息。如果显示出错,而不是缺失run.js,那么继续进行下一步。

第4步:添加矩阵辅助函数,简化矩阵交互

对于gd.core.draw(),通常必须借助一些非常复杂的逻辑来处理颜色和形状矩阵。不过,现在我们采用一些事先写好的辅助函数来处理模型视图( http://3dengine.org/Modelview_matrix)、透视视角(http://mng.bz/VitL)和单位矩阵(http://en.wikipedia.org/wiki/Identity_matrix)。将代码清单9-16中的内容添加到gd.core对象中。跟webgl_util.js一样,下面这些代码的出处已不可考证,但Mozilla 的WebGL教程和Learning WebGL,以及其他一些在线课程都在用它。

代码清单9-16 core.js——矩阵辅助函数

① 有关“矩阵乘法”的概念及详细资料,请参看维基百科的相应页面:http://mng.bz/yo4D(最后修改日期为2013年4月8日)。

② 有关“平移(几何学)”的概念及详细资料,请参考维基百科的相应页面: http://mng.bz/2dbB(最后修改日期为2013年2月21日)。

第5步:为旋转添加Vlad Vukićević的WebGL辅助函数

代码清单9-17中的代码来自于Mozilla的官网http://mng.bz/BU9f。Mozilla声明说道:“这些例程借用自Vlad Vukićević此前所编写的一个范例程序”。你可以去这个人的博客看看:http://blog.vlad1.com。Vlad创建了一些帮助工具来解决旋转问题,已经数据的推送和提取。这段代码添加到gd.core中的旋转逻辑中。

代码清单9-17 core.js——Vlad Vukićević的工具

阶段性测试

现在运行index.html,看看浏览器的控制台,应该如图 9-7 所示。可能没有出现缺失某个文件的错误。如果在刚才的学习中,出现了其他一些不该出现的错误,或者引擎代码出现了问题,那就直接用下载的第9章源代码来覆盖引擎文件好了。调试WebGL非常复杂,因为很多浏览器都没有提供方便的图形监控工具。

配置好了最后一个辅助函数,现在你应该多少能对显卡通信方面,至少能够为一个WebGL 程序编写一个基本的 3D 输出。下面,我们将以此为基础,来构建一个交互式的3D 游戏:Geometry Destroyer。

9.3 创建GeometryDestroyer

虽然创建3D形体非常困难,但之前创建的3D引擎却能大幅简化这一过程。创建新的实体,并通过使用矩阵添加3D数据。引擎不仅负责输出所有的数据,而且还负责在必要时清除内存中的数据。

在本节中,你会通过创建一个有趣的游戏来学习

■编写一个简单的矩阵,在3D空间中输出形状和颜色。

■创建3D旋转数据,并通过一个辅助的控制器,用这一数据指示2D空间中的方向。

■创建并控制敌人实体与粒子实体。

■为了便于创建矩阵,使用indices 将三角面转变为四边面。

■在3D空间中绘制简单的2D形状,以及独特的多边形和立方体。

在下面学习创建实体的过程中,你会学到有关3D建模以及高效的面向对象编程技术。如果对创建3D形状或管理实体没有概念,也不要着急,我们会逐步教你。

准备工作:试玩游戏、下载代码并测试引擎

如果你还没有准备好,可以先到http://html5inaction.com/app/ch9/去玩玩这个游戏,然后再去http://www.manning.com/crowther2/下载本书的源文件。

本节的任务可划分为三组任务。

先来完成第1组任务,制作玩家角色。

9.3.1 创建一个游戏界面及控制对象

先来建立游戏的介绍界面,其实现逻辑不涉及 3D 内容,它的最终结果如图 9-8所示。

第1步:捕捉用户输入

在js文件夹中,用自己喜欢的文本编辑器创建或者打开run.js文件。这个文件目前还没有任何内容。将代码清单 9-18 所示的代码都放到一个自执行的函数中,从而设置出游戏基本的输入监控器和方法。一定要这里所有的代码都放在自执行的函数中,以防止变量泄露到全局空间。

代码清单9-18 run.js——初始化游戏设置

第2步:编写HUD(平视显示)功能

现在已经能侦测控制器输入了,整个引擎也能如预期般运行。但现在还需要用一个平视显示(HUD)来管理得分及初始设置。当然,我们还需要一个玩家角色,不过,先利用代码清单9-19所示的代码,在run.js中的Ctrl变量后面创建一个名叫Hud的变量。

代码清单9-19 run.js——平视显示(HUD)

9.3.2 在3D 空间中创建 2D形状

HUD 和控制器都创建好之后,现在来创建玩家实体。这是一个简单的白色三角形,当玩家按下键盘上的方向键时,它能随之做出相应的移动。按下 X 键,就能让它发射子弹。如图9-9所示,白色三角形代表玩家,小红点代表发射的子弹。

第3步:创建2D的玩家实体

将代码清单9-20所示的代码添加到HUD对象中,创建用于初始化玩家的所有数据。多数初始化信息将保存在位于代码清单前面的变量中,以便于将来调整玩家数据。

代码清单9-20 run.js——创建玩家实体

3D绘图基础知识

在上面创建玩家实体的过程中,最麻烦且难以理解的估计就是 shape()和 color()这两个方法了。shape()方法能够将3个点组成一个三角形,如图9-10所示。color()则将该三角形填充为白色。

vertices(顶点)一词的单数形式是vertex。在数学中,角的顶点是指两条线段的汇聚点。通过声明3个顶点,也就创建了一个三角形,如图9-10所示。当然,为三角形再添加一个点,它就变成了四边形,如图9-11所示(图中是正方形)。

第4步:使玩家实体动起来

回到Player实体,为了通过键盘来控制它的移动、旋转及设计操作,我们需要利用代码清单9-21所示的代码为其添加一个update()方法。通过之前整合进来的Ctrl对象,现在已经能够生成键盘属性了。

代码清单9-21 run.js——玩家实体的状态更新

阶段性测试

现在,在页面上移动代表玩家的太空船就毫无问题了,如图 9-12 所示。但是,如果现在按下X键,应用就会崩溃,因为子弹还没有配置好。下面就来修补这个问题。

第5步:创建玩家发射的子弹

要想发射子弹,需要把代码清单9-22所示的代码添加到Player实体的后面。玩家实体会发射小三角形的子弹,当子弹与敌人实体发生碰撞时,即将其摧毁。当你通过init()方法传入参数时,Bullet会在Player位置处产生。

代码清单9-22 run.js——制作子弹

装好子弹,我们的太空船就能到处巡航了。但是,现在一发射子弹,游戏就崩溃了,又出什么问题了?哦,现在还没有设置敌人的游戏资源。等下,马上就好。

9.3.3 创建 3D形状和粒子

本例中的敌人显得非常复杂,也可以说是非常“顽强”。它们不断变换颜色,并且还会产生出大量的点。如图 9-13 所示,敌人一旦碰上子弹,就会炸裂开来,散布成各种立方体与矩形的粒子,形成一种非常有趣的效果。

现在,我们要着手解决的是第2组任务。

■第2组任务:输出敌人。

♦ 第1步:创建一个3D的多边形敌人。

♦ 第2步:创建一个复杂的3D模型。

♦ 第3步:生成随机的敌人属性。

♦ 第4步:侦测敌人的碰撞问题。

♦ 第5步:有限地生成敌人。

第1步:创建一个3D的多边形敌人

首先,利用代码清单9-23所示的代码,在gd.template.Bullet的下面添加Polygon。现在只需创建它的基本架构,下一个代码清单再来配置3D数据。

代码清单9-23 run.js——Polygon基本架构

第2步:创建一个复杂的3D模型

为了完成上一代码清单中的gd.template.Polygon.init(),需要添加大量的顶点数据。敌人的初始形态是由三段构成的,上下两段是金字塔形,中间是一个立方体。创建3D 模型需要用到的数据非常多,所以我们建议最好还是直接复制本章的源代码好了。将代码清单 9-24 中的 this.shape()调用放到上一代码清单的 gd.template.Polygon. init()已有代码中的最顶部。

代码清单9-24 run.js——多边形形状init()预先准备部分

第3步:生成随机的敌人属性

建立好了多边形的3D数据,为了让敌人行为正常,还需要生成速度、旋转、颜色以及生成点。利用代码清单 9-25 所示的代码,将 randomMeta()和 cube()添加到gd.template.Polygon中。

代码清单9-25 run.js——多边形形状init()预先准备部分(续)

第4步:侦测敌人的碰撞问题

这是创建gd.template.Polygon的最后一步。当多边形敌人被击中时,需要用方法来确定由随机面和立方体粒子所产生的形状数据。还需要更新逻辑与碰撞信息。利用代码清单9-26所示的代码,将其余的方法都添加到gd.template.Polygon中。

代码清单9-26 run.js——多边形的边、状态更新以及碰撞

第5步:有限地生成敌人

虽然已经有了一个多边形实体的类,但还需要一个独立的对象来生成它们。利用代码清单9-27所示的代码,在gd.template.Polygon的下方创建一个名为PolygonGen的新对象。

代码清单9-27 run.js——多边形生成器

现在,终于可以按下 X 键,生成多边形了。不过,如果向它们开火,还是会出现一个错误,因为游戏试图使用并不存在的立方体和粒子的实体模型。所以,我们还需要继续完成第3组任务。

■第3组任务:生成粒子。

♦ 第1步:创建一个3D 的立方体粒子。

♦ 第2步:为立方体添加颜色、旋转角度,以及索引数据。

♦ 第3步:添加尺寸、类型以及其他立方体元数据。

♦ 第4步:生成正方形粒子。

requestAnimationFrame()和其他计时器的一些问题

当用户切换或新建一个标签页时,在原来的标签页中,gd.core.animate()中触发 request-AnimationFrame()的方法就会停止运行,这一点跟 JavaScript 中传统的计时器 setInterval()与setTimeout()不一样,后者在这种情况下仍会继续运行。这意味着,将传统的计时器应用到动画上通常不怎么理想。通常一些polyfill会依靠draw()循环中的帧计数器来解决这个问题,但当用户离开一个标签页时,requestAnimationFrame()的一些实现仍会在几秒钟后对帧进行更新。另外一个办法是构建自定义的计时器脚本,核对经过的时间,在 draw 循环中触发。但这一内容太复杂了,我们没时间来谈论它。为了简化任务,我们限制ploygonGen对象能生成的敌人数目就够了。

第1步:创建一个3D立方体粒子

利用代码清单9-28 所示的代码,在 PolygonGen 下面创建一个新的 gd.template. Cube实体。

代码清单9-28 run.js——立方体形状

第2步:为立方体添加颜色、旋转及索引数据

利用代码清单9-29所示的代码,为gd.template.Cube.init()添加颜色、旋转及索引(indices)数据。indices数据能让你绘制带有4个顶点的四边面。通常,构建一个四边面,需要用6个点构建2个三角面,再将这两个三角面组合成四边面。这里的做法减少了代码,便于维护。

代码清单9-29 run.js——立方体的顶点与颜色

第3步:添加尺寸、类型以及其他一些立方体元数据

在结束gd.template.Cube之前,我们还需要添加一些元数据,比如尺寸、类型以及其他一些具体信息。将代码清单9-30所示的代码添加到已有的Cube对象中。

代码清单9-30 run.js——立方体元数据

第4步:生成正方形粒子

利用代码清单9-31所示的代码,在gd.template.Cube下面添加gd.template. Particle。如果像实现更炫酷的效果,可以加大粒子的数量,关掉Polygon.collide()中的粒子限制。不过要注意的是,生成太多粒子会消耗大量内存并降低帧数。

代码清单9-31 run.js——生成粒子

在浏览器中启动整个应用,所有的功能都能正常运作了(如图 9-14 所示),你成功了!你创建了一个真正的3D游戏,学会了如何构建一个基本的WebGL引擎,同时也掌握了基本的3D编程概念。利用这些工具,你现在就可以用在JavaScript项目中用上WebGL了(尤其是再用上像three.js这样功能强劲的3D库),可以用它来创建logo、插图等。

9.4 小结

3D应用唤醒了人们对于视频游戏与动画的极大兴趣。即使利用 WebGL 创作娱乐内容,那也只不过是这项技术用途的一个很小的方面。有些人已经创作出了不同的3D模拟实例,比如建筑浏览动画以及车辆操作动画,等等。浏览器内的3D表现也能超越Canvas技术的2D限制,比如Bjork网站(bjork.com)就在3D环境下使用了2D形状,实现了令人惊艳的效果,如图9-14所示。

很多网站和企业都在WebGL上投入了大笔的资金。它的强大功能令人难忘,随着支持度的增加,它会极大改变网站和移动设备的编程方式。关于这一点,很大程度上是因为, WebGL最终将使得移动开发者开发出同时适用于所有移动设备的带有图像加速功能的3D应用。所以,我们认为开发者现在应该通过demo和教程多学一些相关知识。

此外,WebGL不是唯一能引领Net的API,我们将在附录I中为你讲解更多API,比如全屏、设备定向及指针锁定等API。

注 释

[1]. http://www.w3.org/TR/2011/CR-touch-events-20111215/

[2]. Windows操作系统下最新版的Firefox现在可以播放MP4视频了。当然,需要系统本身能够支持播放MP4视频。

[3]. 更多细节,请参考以下资料:http://www.contextis.com/resources/blog/webgl2/。

附录 A HTML5 与相关标准

本附录主要内容

■HTML5 规范的开发

■常见的一些已获W3C 承认的HTML5 规范(非草案)

■与HTML5 相关的一些常见规范

像HTML5、CSS3、Node.js这些流行词,你经常能在一些场合遇到。有很多人用HTML5来描述各种新兴涌现的Web技术,HTML5俨然已成为一种“百宝箱”般的存在。比如,本书的一位作者就曾遇到一位市场营销人员,他这么说道:“我能利用 HTML5 创建一个为SEO优化过的视频游戏。”这些人至少也应该了解一下HTML5规范是什么啊,这样才不会出丑嘛!所以,在附录A我们将主要介绍HTML5的官方定义,以及不属于HTML5的一些技术。

A.1 HTML5的起源

或许出人意料的是,万维网联盟(Worldwide Web Consortium,W3C)起初并不提倡HTML5 技术。W3C 认为,在 HTML4 之后 HTML 就该消亡了,于是他们转而去从事XHTML2规范的制定与推广,力图使这种基于XML语法格式的Web标记语言得到发扬光大。如果XHTML1被认为是一种语法严格的语言,那么它的第二版简直就可以用“苛刻”一词来形容了。因此,W3C的很多成员都感觉需要更换解决方案了,WHATW(Web Hypertext Application Technology Working Group,Web超文本技术工作组)随后组建起来,开始研究HTML5。

HTML5 最初只有两个规范:Web Apps1.0 与Web Forms 2.0,然后它们被融合成一个单一规范:HTML5。不久之后,W3C意识到 HTML的优点,开始致力开发HTML第5个版本的规范(应该明确的是,这个规范跟WHATWG所推出的HTML5并不完全一致),他们在 WHATWG 的工作成果的基础上开始制定新标准。在一段时期内,这种情况加剧了标准的复杂性。WHATWG在搞HTML5,而W3C则在搞“HTML的第5个版本”,但同时也在继续制定XHTML2标准。糊涂了吧?我们那时就是这种感觉。

从那时起,XHTML2实际上就已经死掉了,无论是WHATWG还是W3C,大家都在往HTML5这条路上发展。虽然双方的成果都由同一个编辑所监督,但他们各自都有一套独立的标准。这种情况的出现,是权益博弈的结果。基于一些非常复杂的理由,不同的利益集团会选择不同的阵营,从而产生那种可悲可笑的状况。

A.1.1 WHATWG 与 W3C

WHATWG 的目标是按照所有利益集团的反馈意见来持续更新“HTML 活跃标准”,使之成为一种能够稍微领先于当前实现的前沿性标准及规范。WHATWG则放弃了使用版本号来标识标准的做法,转而采用不断演进的文档来记录与发布标准。这份标准旨在能够稍微领先于浏览器厂商所实现的功能,同时通过论坛的形式让所有人都能参与决策,以便确定最终实现中出现的新特性及其相关文档的细节。

W3C则继续沿用传统的版本号迭代的做法,HTML5后面肯定就是HTML6、HTML7,等等——它们都是参考了一些 WHATWG 的快照文档的内容。总之,就目前而言,W3C把WHATWG现存的单一规范分割成了8种不同的规范,从而使得各种特性都能分别发展,并不阻碍标准的发布。有关WHATWG各种独立规范的列表,可以参看WHATWG常见问题页面:http://mng.bz/dWRb。

这两大组织另一个关键区别在于制定决策的方式。在WHATWG中,编辑对HTML5规范的有绝对的决策权。W3C则采用由HTML工作组对议题逐步升级的方式来进行决策。

除了HTML之外,W3C还要制定大量的规范,其目标之一即是力图使所有的规范都能彼此兼容。W3C多年来都一直在致力于发展基于XML的技术,而WHATWG则反对使用纯的XML方案,这一点形成了两大组织分歧的根源。但是除了一些热点问题的争论外,这两大组织的规范间并未有过于明显的冲突。

为了帮助读者直观地了解两大组织的关键区别,我们特别总结了表A-1。

当然,仅用这张简单的表格是无法涵盖成千上万开发者的真实情况的。尤其要注意的是,有许多人还同时参与到这两个组织的工作中。本节的作用只不过想让你了解一下这两大组织的背景,当你想要了解哪一种浏览器能正确地实现某种功能时,在WHATWG邮件列表与他人进行讨论,或是参与到W3C对于某些规范的具体细节所进行的bug追踪时,就不至于出现一些沟通问题。

A.1.2 那么……HTML5 到底是什么东西

在我们看来,如果一项技术属于WHATWG 的Living Standard 标准,或是源于这一标准的W3C规范之一,那么它就应算是HTML5的官方技术。但很多技术,虽然和HTML5沾边,却并不是HTML5 的官方定义,比如CSS3、Geolocation、Storage API 等。下面一节将概述HTML5的官方技术,随后的一节将介绍不属于HTML5官方定义的技术。

是不是HTML5官方技术有那么重要吗?

当然没有那么重要啦!当我们在现代Web平台上开发Web应用时,应该选择那些能够符合你的任务要求的技术,而且浏览器还得支持它们才行,而不要光看它们具体出现在哪条规范上。即使你在一些社交网络上进行热烈的争论,声称自己开发的HTML5应用的关键之处就在于使用了 Geolocation,你也不会被人大加责难。如你所见,我们在写作本书时,也对 HTML5 的定义进行了延伸,也使用了一些非官方的技术。

A.2 常见的HTML5规范

在这一节中,我们将概述一些符合WHATWG 的HTML Living Standard 以及W3C 的HTML5 family of specifications 的技术。虽然,WHATWG 的规范并没有一直被人叫做HTML Living Standard,但我们还是用这个词儿来区分W3C的HTML5 规范。在谈到每一项技术时,都会连带介绍W3C的相应规范以及本书所涉章节。

A.2.1 语义标记和表单元素

HTML5介绍的HTML元素改变了人们构建Web应用与使用表单元素的方式。程序员们能通过 data 这样的属性来控制标记。这些属性能在 HTML 元素中保存重要的元数据。这是HTML最核心的东西,所以也包括在W3C的HTML5规范中。

第1章与第2章介绍了语义标记和表单元素的使用。

A.2.2 视频及音频(多媒体)

过去,Web 开发者主要依靠 Flash 或其他插件来提供音视频的支持,HTML5 的<audio>和<video>元素能让浏览器播放音频和视频,无需配置外在插件。两个元素都使用Media Element API,所以它们切换回放、声音、停止等的事件系统都是相似的。这一方面内容也是W3C核心的HTML5规范。

第8章介绍了视频及音频。在附录I中,还有一些更为前沿的视频技术。

A.2.3 Canvas 和SVG(交互性媒体)

Canvas API和SVG技术能让开发者通过JavaScript来创建交互式的媒体。现今最流行的Canvas API 最初是苹果在Mac OS X中的一个产品。开发者可以利用<canvas>元素创建位图图像。虽然<canvas>元素自身属于HTML5的核心规范,但2D绘图环境(实现绘制的JavaScript API)则在另一个单独的叫作“HTML Canvas 2D Context”的规范中。另外还需注意的是,虽然 WebGL 能使 Canvas 显示 3D 图像,但 3D 绘图环境却并不属于HTML5官方规范(详情参看A.3节内容)。

SVG技术诞生自大约2001 年,是一个基于XML 的语言。HTML5 所做的不过是将SVG元素注入到HTML页面中(也经常能把SVG注入到XHTML页面中)而已。一定要记住的是,SVG是HTML5的一个标准组成部分,而不是由HTML5所衍生出的一个规范。

第6章与第7章介绍了Canvas、2D绘图环境以及SVG。第8章使用Canvas来控制实时视频,第9章则将其与3D绘图环境结合起来使用。

A.2.4 存储

HTML5 还包含一些基于存储的API。其中,属于HTML5 规范的是Web Storage 与离线应用。

在 W3C 体系中,离线应用属于 HTML5 的核心规范。线程和本地存储则属于 Web Storage规范。这些都在第5章有所介绍。

A.2.5 通信

Web通信(跨文档通信与通道通信),服务器发送事件,以及WebSockets都属于HTML5的核心技术。在W3C体系中,它们分属于3个规范:“HTML5 Web Messaging”、“Server-Sent Events”和“WebSockets API”。这里要注意的是描述传输数据的格式的WebSockets协议,它是由IETF(Internet Engineering Task Force,互联网工程任务组)定义的。第4章与附录F介绍了通信方面的内容。

A.2.6 XML HTTP请求对象

在20世纪90年代末,这种API就已经存在于IE中了。在2000至2002年间,它通过Firefox的实现被大量用于Web应用中,从而促成了AJAX(异步JavaScript和XML)的诞生。但是XHR(XML HTTP Request)从来没有记录在任何规范中,直到2004 年, WHATWG才为它制定了专门的规范。目前,W3C制定了关于XHR对象的专门规范,但由于XHR和AJAX用途广泛,知名度高,所以虽然严格来说XHR属于HTML5技术,但本书也不会专门介绍它。

A.3 常见的非HTML5技术

一些流行的规范和技术常常因为它们优秀的功能而被误解为HTML5技术。虽然这些技术差不多和HTML5规范同时出现,而且经常出现在HTML5技术展示网站和HTML5相关书籍(也包括这本书)中,但由于之前给出的定义,它们并不属于HTML5。按照Bruce Lawson的说法,最好将这些Web开发技术称为“HTML5的盟友”。

A.3.1 CSS3

CSS3为Web开发提供了一些非常出色的功能,比如过渡和3D变换。但它却是一个完全独立于 HTML5 的规范。本书并没有专门地介绍 CSS3,但书中的所有应用都用到了这一技术。

要想较好地学习CSS3,可以看一下Hello! HTML5 & CSS3(Rob Crowther,Manning, 2012)以及Sass and Compass in Action(Wynn Netherland、Nathan Weizenbaum、Chris Eppstein和Brandon Mathis 合著,Manning,2013)这两本书。

A.3.2 Geolocation

许多早期的HTML5 demo都展示了Geolocation API,但该API却并不属于WHATWG的HTML Living Standard 或W3C的HTML5 family of specifications。

W3C制定了关于Geolocation API 的规范,第3章将对此给予简要介绍。

A.3.3 存储

上一节已经介绍了存储。有两种并不属于 HTML5 规范的关键存储技术:IndexedDB和File System API。在W3C规范中,这两项技术表现为4 种规范:Indexed Database API、File API、File API: Directories and System和File API: Writer。

第5章介绍了IndexedDB,而第3章则介绍了File API。

A.3.4 WebGL

WebGL技术基于OpenGL。Khronos联盟组织将OpenGL应用于浏览器中,结果就产生了WebGL技术。所有的桌面浏览器都支持WebGL,甚至连微软也一改最初的反对态度,在IE11中实现了WebGL。

A.3.5 Node.js

许多人都误会了 Node.js(经常简称为 Node),错把这个新的软件平台当成 HTML5的一个API。虽然Node 使用了新兴的Web 标准技术,并改善了许多HTML5 API 的使用方式,但它仍然不是一种Web 标准。它运行在谷歌的V8 JavaScript引擎上,主要有Joyent负责开发。本书介绍了一些简单的 Node 用法,更多有关 Node 的知识可参考 Node.js in Action (Mike Cantelon、TJ Holowaychuk与Nathan Rajlich合著,Manning,2013)。第4章与附录E都介绍了有关Node的内容。

A.3.6 jQuery 与其他一些 JavaScript 库

它们最初所要解决的问题是:针对AJAX的基础,XHR对象,如何在其不同的浏览器实现之上提供一种兼容层,每一种库都添加了各自独有的功能。Prototype.js 添加了一些功能,鼓励一种类似Ruby的编程风格。Dojo的做法也差不多,只不过差别在于风格转向了Python。发展了很多年之后,跨浏览器兼容性的终极方案面世了,这就是jQuery。HTML5无法取代像jQuery这样的库,但它能让它们变得更高效。通过制定HTML5的相关规范,使得浏览器行为变得标准化,全方面的努力可以提高浏览器的兼容性,从而削弱这些库的作用和地位。HTML5已经取代的一些JS库功能如表A-2所示。

A.4 紧跟规范的最新进展

要想了解 HTML 核心规范的最新进展,最好的方法就是看看 WHATWG 的博客:http://blog.whatwg.org/。不过,这些规范读起来都很长,很乏味。所幸,我们可以利用一个针对Web开发者的版本来了解这些规范,它的链接地址是:http://developers.whatwg.org/。这个版本去掉了面向浏览器厂商的内容,所以较易于开发者阅读。

对于剩余的一些规范来说,没有太集中的资源。每一个独立的W3C工作组都有自己的博客或邮件列表。另外,还可以抽空看看各大浏览器的开发博客,了解一下他们都在实验什么新功能。

■MozillaHacks:https://hacks.mozilla.org/

■Google Chrome Blog:http://chrome.blogspot.co.uk/

■IEBlog:http://blogs.msdn.com/b/ie/

■Surfin’Safari:https://www.webkit.org/blog/

■Opera Desktop Team:http://my.opera.com/desktopteam/blog/

■Opera Mobile:http://my.opera.com/mobile/blog/

相关图书

HTML+CSS+JavaScript完全自学教程
HTML+CSS+JavaScript完全自学教程
零基础入门学习Web开发(HTML5 & CSS3)
零基础入门学习Web开发(HTML5 & CSS3)
HTML CSS JavaScript入门经典 第3版
HTML CSS JavaScript入门经典 第3版
HTML+CSS+JavaScript网页制作 从入门到精通
HTML+CSS+JavaScript网页制作 从入门到精通
从0到1:HTML5 Canvas动画开发
从0到1:HTML5 Canvas动画开发
从零开始:HTML5+CSS3快速入门教程
从零开始:HTML5+CSS3快速入门教程

相关文章

相关课程