HTML5 程序员参考(一)

原文:HTML5 Programmer’s Reference

协议:CC BY-NC-SA 4.0

零、简介

万维网已经存在了将近 25 年。它最初是由蒂姆·伯纳斯·李和罗伯特·卡里奥提出的一个简单的建议,作为欧洲粒子物理研究所的科学家们轻松发表论文的一种方式,但它迅速发展成为一个吸引了全世界想象力的平台。

网络可能是从一个简单的文档发布平台开始的,但是很快就清楚了,它注定要变得更好。随着人们要求更多的交互性和更丰富的体验,最初的 HTML 标准的局限性很快变得明显。级联样式表和 JavaScript 等其他技术的出现有所帮助,但开发人员仍然花费大量资源来构建人们想要的体验。

HTML5 旨在帮助解决这些问题。HTML 标准的第五代,HTML5,被设计成既有丰富的特性又更容易使用。HTML 的早期版本关注于如何最好地标准化文档标记,这是为早期 Web 的混乱带来标准的好方法。然而,HTML5 专注于为构建丰富的交互提供一个平台。HTML5 的大部分也是专门为移动技术而设计的,而旧版本的 HTML 则不是。

这本书涵盖的内容

这本书旨在成为 HTML5 特性的最佳参考。它分为两个部分。

第一部分“深入研究 HTML5”有几章提供了对 HTML5 特性的详细检查,包括多个示例和截至发稿时的当前支持级别。

  • 第一章“欢迎来到 HTML5”是一堂历史课,解释了万维网及其技术是如何发展的,以及 HTML5 是如何出现的。这将有望帮助你理解为什么 HTML5 与以前的 HTML 标准如此不同,并让你对 HTML5 的结构有更好的了解。
  • 第二章“HTML5 元素”涵盖了 html 5 中新的语义标签。和以前的标准一样,HTML5 包含了一组新的标记,用于标记文档中的内容。在这里,您将了解如何使用新的音频和视频标签,以及许多其他 HTML5 功能。
  • 第三章“HTML5 API”深入探讨 html 5 标准中规定的 JavaScript APIs。您将了解 HTML5 应用通信和保存数据的新方法。
  • 第四章,“画布”,涵盖了 HTML5 最具创新性的特性之一:canvas元素。在这里,您将学习如何使用这个元素来绘制、修改图像和创建动画。
  • 第五章“相关标准”涵盖了几个与 HTML5 相关的 JavaScript APIs(经常与 HTML5 一起使用),但实际上并不是 HTML5 标准的一部分。这些 API 也倾向于拥有强大的移动焦点。
  • 第六章“实用 HTML5”涵盖了在生产项目中实际使用 HTML5 的内容。它涵盖了检测功能和应用垫片,并包括一个完整的 HTML5 移动游戏的设计和建立从地面向上。

第二部分“HTML5 参考”,包含第一部分中所有 html 5 特性的参考章节。每章旨在为每种功能提供一目了然的参考,包括功能的简要描述、使用方法(包括语法和示例)以及在哪里可以找到其标准。

  • 第七章是 HTML5 元素的参考章节。
  • 第八章是 HTML5 JavaScript APIs 的参考章节。
  • 第九章是canvas元素的参考章节。

你需要知道什么

虽然整本书有很多详细的例子,但它是作为参考而不是作为教程来写的。我假设您对浏览器如何工作以及如何使用 JavaScript 有一定的了解,并且至少对 CSS 和 HTTP 中涉及的网络协议有基本的了解。你应该能够轻松地创建和编辑网页,编写自己的 CSS 和 JavaScript。

运行代码示例

整本书有大量的代码示例。可以从www.apress.com下载样本,也可以手工输入。许多示例可以通过使用浏览器的文件菜单将文件加载到 web 浏览器中来运行。

但是,有些示例必须从实际的服务器上运行,要么是出于安全限制,要么是因为您希望在移动设备上查看它们。为了构建和测试书中的所有例子,我使用了 Aptana Studio,可以在http://www.aptana.com免费获得。Aptana Studio 附带了一个内部调试服务器,您可以使用它来运行这些示例。如果您更喜欢独立的解决方案,我非常喜欢 XAMPP,它是 Apache web 服务器的独立安装,以及可选的组件,如 MySQL、PHP 和 Perl。当然,MacOS 和 Windows 都有自己的 web 服务器解决方案,您可以激活和使用,就像大多数标准的 Linux 发行版一样。

最后,请务必查看附录 A“JavaScript 技巧和技术”中的“注释注释”部分,了解示例格式的解释以及如何阅读注释。

一、欢迎来到 HTML5

在这一章中,我将深入探究 HTML 的历史以及 HTML5 是如何产生的。我将谈论 HTML 从一个简单的提案一直到其当前版本的演变,包括相关技术的回顾。我还将介绍 HTML5 是什么,它的范围,它与以前版本的不同之处,以及它如何与其他技术相结合。

HTML5 是什么?

自 1989 年以来,超文本标记语言(HTML)一直伴随着我们。HTML 之前的版本只为内容定义了标记:列表、段落、标题、表格等等。然而,HTML5 定义了更多。它有新的内容标签(如<audio><video>),但它也定义了复杂的交互,如拖放,新的网络接口,如服务器事件,甚至有新的异步功能,如 web workers。HTML5 之前的 HTML 规范也定义了 SGML 中的标签(稍后会详细介绍),但是 HTML5 规范只根据注释内容和预期行为来定义标签。因为 HTML5 是一套新的高级 web 技术的重要组成部分,很多时候你会在网络上或流行媒体上看到一些文章,错误地包含了 HTML5 中与 HTML 无关的技术。

那么 HTML5 到底是什么?为什么 HTML5 定义的不仅仅是标签?HTML5 是怎么来的?为什么 HTML5 标准在定义和范围上与以前的标准有如此大的不同?为了回答这些问题,我将首先快速回顾一下 HTML 最初是如何出现的。

HTML 的简史

HTML 的起源可以追溯到 1989 年。那时,网上分享信息最常见的方式是通过电子邮件、新闻组新闻组和公共 FTP 站点。电子邮件和新闻组使人们直接相互交流变得容易,FTP 站点为人们提供了访问文件集的途径。主要问题是,所有这些形式的信息共享都需要不同的软件,以及一定水平的技能才能真正在互联网上导航——尽管那时的互联网比现在小得多。

蒂姆·伯纳斯·李在 1989 年提出了一个更好的解决方案。当时他在欧洲核研究组织(其法语缩写 CERN,即欧洲核研究委员会)工作,他敏锐地意识到需要一种更好的方式来在线共享信息。尤其是,Berners-Lee 需要解决在线共享技术文档的问题。CERN 产生了大量的技术文档,从准备出版的核物理论文到内部政策文档,他们需要一个适用于所有这些不同用例的解决方案。

伯纳斯·李发现自己试图同时解决两个问题:

  • 他需要一个解决方案,提供一种可视化格式化 CERN 科学家产生的信息的方法。这种信息可以采取文件的形式,如发表的论文以及在实验中观察到的数据。
  • 他需要一个能够处理交叉引用并嵌入图形和其他媒体的解决方案。CERN 的许多文件包括图表和图形,并相互引用,或引用其他内部数据源,甚至外部文件和数据源。

幸运的是,伯纳斯-李已经有了一些解决这些问题的经验。早在 1980 年,当他还是 CERN 的一名承包商时,他已经建立了一个名为 ENQUIRE 的原型系统,提供了该组织所需的一些功能,但未能很好地扩展。然而,它使用了一个非常重要的关键概念:超文本。

输入超文本

超文本 是一种引用其他信息的文本,用户可以激活它来立即访问这些信息。这包括同一文档中包含的信息以及外部文档或其他数据源中的信息。这些链接被称为超链接??。在大多数现代计算机中,超文本显示在屏幕上,超链接通过用鼠标点击或(在触摸屏的情况下)用手指轻击来激活。术语 超媒体是超文本概念的延伸,不仅包括超链接,还包括图形、音频、视频和其他信息源。

超媒体的概念已经存在很长时间了。1945 年,美国工程师兼发明家万尼瓦尔·布什为《大西洋月刊》写了一篇名为《正如我们所想》的文章。作为论文的一部分,布什提出了一种“记忆扩展器”或“memex”,一种人们可以用来存储所有个人信息来源的设备:书籍、唱片、相册等等。memex 将通过使用一组书签向人们提供对他们所有信息的访问,并且可以根据需要进行扩展。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示你可以在大西洋的网站www.theatlantic.com/magazine/archive/1945/07/as-we-may-think/303881/上阅读“我们可能认为的”。

1960 年,Ted Nelson 创立了 Project Xanadu ,试图建立一个能够存储多种版本文档的文字处理系统,让用户能够以非顺序的方式浏览这些文档。他将这些不连续的路径称为“拉链式列表”,并假设通过使用这些拉链式列表,可以在一个他称之为“串并”的过程中从其他文档的片段形成新文档 1963 年,尼尔森创造了术语超文本超媒体,这两个术语首次发表在他的论文《复杂信息处理:复杂、变化和不确定的文件结构》(可在http://dl.acm.org/citation.cfm?id=806036获得)。当时 Nelson 使用超文本来指代可编辑的文本,而不是基于文本的交叉引用,所以这个术语从 Nelson 第一次创造它开始就有了一些语义上的变化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1962 年,美国工程师和发明家道格拉斯·恩格尔巴特开始研究他的“在线系统”或“NLS”NLS 是第一个包括当今可用的大多数现代计算机功能的系统:一个指点设备、窗口、用于呈现不同种类数据的独立程序、按相关性组织的信息、超媒体链接等等。1968 年 12 月,Englebart 在旧金山的秋季联合计算机会议上演示了 NLS。这次演示是开创性的,不仅因为它首次同时展示了所有这些正在使用的现代功能,还因为它使用了最先进的视频会议技术来展示 Englebart 使用的 NLS 用户界面。因为这个演示在范围上是如此具有突破性,所以它经常被称为“所有演示之母”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示该演示可以在斯坦福大学的网站http://web.stanford.edu/dept/SUL/library/extra4/sloan/MouseSite/1968Demo.html上看到。

Berners-Lee 在超文本概念的基础上建立了 ENQUIRE。在 ENQUIRE 中,一个给定的文档由一个名为“卡”的信息页面表示,它实际上是一个超链接列表,定义了文档包括的内容、使用方法、描述以及作者。激活这些链接可以很容易地跟踪它们,使用户能够浏览整个网络的文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-1 。ENQUIRE 的截图

在这方面,ENQUIRE 类似于图书馆卡片目录系统的在线版本,不幸的是需要大量的工作来保持更新。

ENQUIRE 也没有解决文档可视化格式的需求。然而,CERN 已经以文档标记语言的形式使用了一种可能的解决方案。

输入标记语言

文档标记语言是一种编程语言,它提供了一种对文档进行注释(或“标记”,就像编辑对正在审阅的文档进行标记)的方式,使得注释在语法上不同于主要内容文档。基于注释的目的,标记语言分为三大类:

  • 表示标记语言用来描述文档应该如何呈现给用户。大多数现代文字处理器使用嵌入在文档中的二进制代码形式的表示标记。表示性标记通常是为特定的程序或显示方法设计的,因此并不意味着人类可读。
  • 过程化的标记语言提供注释,说明文档的内容应该如何处理,通常是在印刷的布局和排版环境中。过程标记语言最常见的例子之一是 PostScript。
  • 描述性的标记语言被用来用对文档内容的描述来注释文档。描述性标记没有给出内容应该如何处理或显示的任何指示;这是留给处理代理的。

文档标记语言已经存在了几十年。第一种广为人知的文档标记语言是由计算机科学家 William Tunnicliffe 在 1967 年提出的,但是 IBM 研究员 Charles Goldfarb 通常被称为现代标记语言的“父亲”,因为他在 1969 年发明了 IBM 通用标记语言(GML) 。戈德法布负责推动 IBM 将 GML 纳入其文档管理解决方案。GML 最终发展成为标准通用标记语言(SGML ,它在 1986 年成为 ISO 标准(ISO 8879:1986 信息处理-文本和办公系统-标准通用标记语言),由 Goldfarb 担任委员会主席。

SGML 不是一种你可以直接使用的语言;相反,它是一种“元语言”——一种用于定义其他语言的语言。在这种情况下,SGML 用于定义标记语言,然后这些语言可用于描述文档。具体来说,SGML 要求标记语言描述文档的结构和内容属性(相对于描述如何处理文档),并且严格定义标记语言,以便可以构建遵循相同规则的处理和查看软件。由 SGML 定义的语言被称为“SGML 应用”(不要与在计算机上运行并执行任务的应用相混淆)。常见的 SGML 应用包括 XML(可扩展标记语言)和 DocBook(为技术文档设计的标记语言)。

CERN 一直使用名为 SGMLguid) 的 SGML 应用来标记其文档,Berners-Lee 认识到 SGMLguid 与超文本的结合可能是他解决 CERN 文档管理问题所需的解决方案。

超文本标记语言诞生了

1989 年末,Berners-Lee 提出了一个以超文本和简单标记语言为基础的试验项目。Berners-Lee 设想超链接将成为将所有不同文档联系在一起的关键功能:

超文本是一种链接和访问各种信息的方式,就像一个由节点组成的网络,用户可以随意浏览。潜在地,超文本为许多大类的存储信息,如报告、笔记、数据库、计算机文档和在线系统帮助,提供了单一的用户界面。

摘自 1990 年 11 月 12 日的“万维网:超文本项目提案”

该提案概述了一个简单的客户机/服务器网络协议,用于新的文档“网络”,以及它们将如何一起工作,将信息从服务器传输到浏览客户机。伯纳斯-李将新协议称为“超文本传输协议”或 HTTP。该项目获得批准,伯纳斯-李和他的团队开始研究最终成为万维网的东西。

在为新的文档系统创建了客户机和服务器软件之后,Berners-Lee 发表了第一个文档,该文档定义了一组基本的标记,这些标记可用于标记将要包含在新的在线文档 web 中的文档。这份名为“HTML 标签”的文档定义了 18 个标签,它们可以用来标记文档的内容,新的 web 客户端可以解析并显示它们。几乎所有的标记都来自 SGMLguid,只有一个例外:anchor 标记。锚标签是对新系统如此重要的超文本链接功能的实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示你可以在www.w3.org/History/19921103-hypertext/hypertext/WWW/MarkUp/Tags.html阅读 W3C 历史档案中的原始“HTML 标签”文档。

第一个文档是一个简单的标签列表,描述了如何使用它们来描述文档的内容。后来,随着“超文本标记语言(HTML)”(www.w3.org/MarkUp/draft-ietf-iiir-html-01.txt)作为提交给 互联网工程任务组(IETF)的工作草案的出版,这些标签在 1993 年正式成为 SGML 应用。该草案到期,同年晚些时候由戴夫·拉格特撰写的题为“HTML+(超文本标记格式)”的竞争草案紧随其后。

开放和协作

蒂姆·伯纳斯·李致力于保持 HTML 定义过程的开放性和协作性,利用许多参与者的知识和经验。这些早期的合作不仅为通过公共合作设计的整个 web 技术生态系统的创建铺平了道路,也为维护项目的核心小组的创建铺平了道路。

浏览器大战

在致力于定义 HTML 的同时,蒂姆·伯纳斯·李也在开发第一个可以利用新的网络文档的软件。1991 年,Berners-Lee 为 NeXTStep 平台发布了第一款网络浏览器“WorldWideWeb”。其他程序员对开发他们自己的 web 浏览器有着浓厚的兴趣,所以 1993 年 Berners-Lee 向公众发布了一个名为 libwww的可移植 C 库,这样任何人都可以开发 web 浏览器。(在此之前,该库是作为更大的万维网软件应用的一部分提供的。)

至此,在多个平台上已经有了几个实验性的 web 浏览器项目。其中一些是简单的基于文本的浏览器,可以在任何终端上使用,比如 Lynx 浏览器。其他的是当时图形桌面中使用的图形应用。

最受欢迎的图形应用之一是 Mosaic,由伊利诺伊大学的国家超级计算应用中心(NCSA) 开发。Marc Andreesen 和 Eric Bina 于 1992 年底开始了 Mosaic 的工作,并于 1993 年发布了第一个版本。

1994 年,安德里森离开 NCSA,成立了一家名为马赛克通信的公司,在那里他们用全新的代码构建了一款新的浏览器。新浏览器被称为网景导航器(最终马赛克通信更名为网景通信)。

实际的马赛克代码库本身是由一家名为 Spyglass,Inc .的公司从 NCSA 获得许可的。Spyglass 从未对代码做任何事情,1995 年微软从他们那里获得了代码许可,对其进行了修改,并将其重命名为 Internet Explorer。

网景公司和微软公司都开始扩展他们浏览器的功能,增加新的 HTML 标签和其他功能。网景于 1995 年在 Navigator 中加入了 JavaScript(代号“mocha”,最初发布时为“LiveScript”)。微软很快在 1996 年推出了他们自己版本的同一种语言,称为 JScript,以避免商标问题。

Netscape Navigator 和 Internet Explorer 对相同的功能有完全不同的实现,也有它们自己的专有功能。给定的 HTML 文档可能在 Navigator 中以一种方式呈现,而在 Internet Explorer 中呈现时看起来完全不同。即使简单的 HTML 标记在两种浏览器中也会产生明显不同的视觉效果,任何更高级的尝试都是不可能的。

这为所谓的浏览器大战埋下了伏笔。任何为 Web 制作内容的人都必须做出选择:选择支持单个浏览器,或者花费大量资源尝试同时支持两个浏览器(在许多情况下,这意味着制作同一内容的两个不同版本,每个浏览器一个版本)。常见的是只针对一种浏览器进行优化的网站,用图形表示选择,如图 1-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-2 。浏览器大战中的图片

通过将 Internet Explorer 作为 Windows 操作系统的标准部分,微软轻松赢得了第一轮浏览器大战。这给了 Internet Explorer 一个巨大的安装基础,人们没有理由为 Netscape 付费。到 1999 年,Internet Explorer 占据了万维网浏览器使用量的 96%。网景通信被美国在线收购,网景导航器(当时叫网景通信器)被封存。

浏览器大战:网景反击

AOL 开源了 Netscape Communicator 代码库,并将其委托给新成立的非营利组织 Mozilla Foundation 。作为一个开源项目,Mozilla 基金会继续在 Navigator 代码基础上进行构建,并获得了相当大的发展势头,为浏览器添加了新功能,包括电子邮件和 HTML 编辑功能。2002 年末,该套件的一个精简版被创建出来,最初被命名为 Phoenix,然后是 Firebird,后来(由于项目命名冲突)被命名为 Firefox。Firefox 继续成功挑战了 Internet Explorer 在浏览器市场的垄断地位,这被许多人称为第二轮浏览器大战。

救援标准

打击网络碎片化意味着让各方坐到谈判桌前,就每个人都可以建立的技术标准达成一致。标准为浏览器制造商和内容创作者提供了一个共同的基础:

  • 通过将标准作为制造过程的一部分,浏览器制造商将为网络提供一个可预测的平台。
  • 通过将标准作为编码实践的一部分,内容创建者可以确信他们的内容将在所有浏览器上一致地呈现。

1994 年 10 月,这正是蒂姆·伯纳斯·李所做的,这一举动唤起了他保持网络开放和协作的愿望。他离开了 CERN,成立了万维网联盟(W3C) ,,这是一个致力于网络技术的标准组织。该联盟由任何想要参与定义和维护网络技术标准的人组成:最终包括微软、苹果、脸书和谷歌的公司;像 NASA 和国家标准技术研究所这样的政府组织;像斯坦福大学和牛津大学这样的大学;像欧洲核子研究中心这样的研究机构;以及非营利组织,如 Mozilla 基金会和电子前沿基金会。

W3C 标准进程从发布标准的工作草案开始。然后,联合体成员可以对草案发表意见,草案可能会经历相当大的演变。一旦草稿固化,候选人推荐就会发布。从实施的角度审查候选推荐标准——实施和使用标准的难度有多大。一旦实施者有了发言权,草案就进入了建议状态。提议的建议将提交给 W3C 顾问委员会进行最终批准。一旦获得最终批准,该标准就获得了 W3C 官方推荐标准的地位。

标准不会在一夜之间解决浏览器战争。浏览器制造商实施这些标准花了一段时间。微软尤其支持“拥抱和扩展”的哲学,在这种哲学中,他们同意标准,但也继续增加他们自己的专有技术,试图使 Internet Explorer 成为一个更有吸引力的 web 开发平台。不过,最终,对跨所有浏览器的一致行为的需求取得了胜利,标准为胜利提供了蓝图。

HTML 的持续发展

HTML 标准最初由 IETF 维护,IETF 于 1995 年将 HTML 2.0 标准发布为 RFC 1866。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 “RFC”代表“征求意见”,这意味着发布了文档,并邀请利益相关者对其进行评论,作为持续审核流程的一部分。

W3C 在 1996 年接管了 HTML 标准。1997 年,W3C 发布了 HTML 3.2 标准。这个版本正式否决了几个供应商特定的功能,并进一步稳定了浏览器制造商和内容创作者的标准。在不到一年的时间里,W3C 发布了 HTML 4.0。这个版本的 HTML 将标准推向了纯语义标记的方向:许多可视化标签,比如那些创建粗体或斜体标签的标签,都被弃用,取而代之的是级联样式表(CSS ) 。W3C 在 1999 年发布了 HTML 4.1,它本质上是 HTML 4.0 ,做了一些小的编辑和修改。在 2000 年 HTML 4.1 因为一个 ISO 标准:ISO/IEC 15445:2000。

所有这些 HTML 版本都被定义为 SGML 应用。每个标签及其属性都是使用 SGML 规则定义的,如清单 1-1 中的所示。

清单 1-1 。HTML 4.1 中 UL 标签的 SGML 定义

<!ELEMENT UL - - (LI)+               -- unordered list -->
<!ATTLIST UL
  %attrs;                            -- %coreattrs, %i18n, %events -->
<!ELEMENT OL - - (LI)+               -- ordered list -->
<!ATTLIST OL
  %attrs;                            -- %coreattrs, %i18n, %events -->

随着标准的发展,内容创建者必须越来越严格地遵循这些标准,以保证跨浏览器的一致行为。

XHTML 的兴衰

2008 年,一个新的 SGML 应用被提出,它将提供一个更小、更易管理的 SGML 指令子集。被称为可扩展标记语言,或 XML,它也是用来定义数据标记语言的。HTML 4 标准很快被翻译成 XML,产生了 XHTML。XHTML 1.0 标准于 2000 年发布。

XHTML 旨在使 HTML 语言更加模块化和可扩展。XHTML 语法比普通 HTML 更严格,XHTML 标记中的错误将导致呈现代理发布错误并停止,而不是恢复到基本行为并继续。然而,由于缺乏对旧内容的向后兼容性和浏览器支持,XHTML 从未被广泛采用。

WHATWG 的形成和 HTML5 的创建

到 2004 年,W3C 开始致力于 XHTML 2.0。然而,联盟的一些成员认为基于 XML 的方向不是 web 技术应该遵循的正确方向。Mozilla 基金会和 Opera 软件在 2004 年 6 月向 W3C 提交了一份立场文件。本文将 web 应用作为一个整体来关注:如何构建它们,它们应该采用什么技术,与现有 web 浏览器的向后兼容性,等等。这篇论文包括了一个 Web 表单的规范草案,作为指导的例子。你可以在 W3C 网站上的www.w3.org/2004/04/webapps-cdf-ws/papers/opera.html阅读这篇论文。这篇论文提出的问题比它回答的要多,但是总的来说,它指向了一个不同于 W3C 目前的基于 XML 的解决方案的方向。最终 W3C 投票否决了这份文件,选择继续使用 XML 解决方案。

许多利益相关者强烈地感受到以本文提出的整体方式来看待 web 应用,因此成立了一个小组来专注于 web 应用标准的创建。名为网络超文本应用技术工作组(WHATWG) ,的成员包括来自苹果公司、Mozilla 基金会和 Opera 软件公司的个人。最初,他们为 web 应用标准创建了一个草案,其中涵盖了该团队认为对于创建丰富的交互式 Web 应用非常重要的所有特性,包括:

  • 新的语义标记标签用于常见的内容模式,如页脚、侧栏和引用。
  • 新的状态管理 0074 和数据存储功能。
  • 本机拖放交互。
  • 新的网络功能,如服务器推送事件。

这个新标准最终与 Web Forms 标准(同样由 WHATWG 制定)合并,合并后的标准被重新命名为 HTML5 。这就是 HTML5 标准不是 SGML 应用的原因,也是它不仅仅包含标记的原因:它旨在为创建 web 应用提供更好的工具。

2007 年,W3C 的 HTML 小组采用了 WHATWG 的 HTML5 规范,并开始向前推进。这两个组织都继续维护他们自己版本的相同标准。根据双方协议,W3C 维护 HTML5 的规范标准。WHATWG 的标准被认为是一种“生活标准”,因此它永远不会完整,而且会不断发展。这样,W3C 的标准就像是 WHATWG 标准的快照。

W3C HTML5 标准

W3C 的 HTML5 标准在www.w3.org/TR/html5/Overview.html可用。这是 W3C 的官方建议。

WHATWG 生活标准

WHATWG HTML 标准位于https://html.spec.whatwg.org/multipage/index.html

HTML5 特性

因为 HTML5 被设计成能够创建丰富的交互式 web 应用,所以它规定的不仅仅是标记标签——尽管它也涵盖了这些。

新标签

HTML5 标准为标记文档指定了许多新的标签。新的 sectioning 标记提供了指示常见设计模式(如页脚和导航组件)的方法,并为屏幕阅读器提供了改进的语义信息。新的分组标签提供了指示内容组(如图表)的方法。当然,HTML5 包括新的音频和视频标签,可以像嵌入图像一样轻松地将多媒体嵌入到 web 应用中。HTML5 还包括一整套新的交互元素,用于实现常见的设计模式,如对话框和渐进式披露。

由于包含了 Web 表单规范,HTML5 还包含了许多新的表单元素,包括数据列表(可过滤的下拉列表)、指示器和进度条以及滑块。HTML5 还指定了几个新的表单属性,允许与表单进行更丰富的交互。现在,通过简单的属性,您可以在表单域中指定占位符文本,或者指示在页面加载时哪个表单域应该具有焦点(处于活动状态)。

帆布

HTML5 指定了新的canvas特性,一种以编程方式在网页上绘图的方式。canvas还包括文本、图层混合和图像处理功能。

JavaScript API

HTML5 标准包括一组新的 JavaScript API,为 web 应用添加更多功能。客户端/服务器通信有了新的 API,包括服务器将事件推送到网页的能力,以及保护跨文档和域通信的新方法。还有在浏览器中本地存储数据、拖放交互和多线程的特性。

相关标准

有一系列与 HTML5 交互的相关标准,由 W3C 维护,但在技术上不是 HTML5 标准的成员。这些功能包括地理定位、设备定位和 WebGL。

摘要

在这一章中,我讲述了 HTML 的历史以及 HTML5 是如何产生的,包括:

  • 底层技术的起源,
  • 浏览器大战,还有
  • 标准的诞生。

我还介绍了 HTML5 标准及其相关标准的总体构成。

历史够了!下一章将深入新的 HTML5 元素,包括audiovideo元素。

二、HTML5 元素

虽然 HTML5 规范比以前的版本复杂得多,但像那些版本一样,它包括新元素的定义和旧元素的废弃。在这一章中,我将把重点放在 HTML5 规范的元素部分。

我将首先展示最佳实践是如何对 HTML 的发展做出贡献的。然后,我将介绍 HTML5 规范中包含的许多新标签:用于创建新部分、内容分组、语义标记、嵌入内容、新交互内容和表单的标签。我还将介绍 web 表单的新特性:新的表单属性、字段属性和输入类型。最后,我将介绍 HTML5 中已被否决的元素。

HTML 的功能、语义和演变

HTML5 代表了该语言进化路线的最新发展。在网络之初,这种演变主要是由浏览器制造商推动的,他们都想在网络上创建自己的专有空间,以区别于他们的竞争对手。不幸的是,这导致了现在被称为“浏览器战争”的网络分裂

第一个标准就是为了对抗这种分裂而诞生的。通过为所有的浏览器制造商提供一个共同的基础,他们使得开发者能够编写独立于平台的 HTML 代码。

更重要的是,随着标准的进一步发展,一组最佳实践也随之发展,以帮助开发人员利用符合标准的优势。这些实践中最重要的两个可能是功能和语义标记分离的概念。

功能 的分离决定了我们应该根据每个工具的长处来使用它们。它通常被总结为“表示与内容的分离”,但它比这更深入:使用 HTML 表示内容,使用 CSS 表示表示,使用 JavaScript 表示功能。

*将 HTML 从 CSS 和 JavaScript 中分离出来,允许这三种语言独立发展,也使得开发人员可以更容易地升级,甚至在以后完全改变技术,而不必完全重做所有三种语言的代码。

语义 标记 的核心思想是使用正确的标签来标记由内容决定的给定部分或数据段。在某种程度上,这是功能分离的更深层次的应用:为工作使用正确的标签。因此段落应该用<p>标记,无序列表用<ul>,列表项用<li>等等。今天,这种最佳实践已经成为 web 开发人员的第二天性,但并不总是这样。常见的是,标签只用于默认样式提供的缩进或空白,这使得标记非常混乱。

这些最佳实践的问题是,除了在 Web 上构建简单的信息文档之外,它们几乎没有什么帮助。如果你想做复杂的布局,HTML 没有必要的语义。如果你想用 web 技术来构建功能,HTML 开始显示出它缺乏语义。

这种缺乏的一个主要症状是像<div><span>这样的无意义标签的泛滥。因为这些标签只表示节(<div>表示块级节,<span>表示内联节),所以它们可以安全地用于包含任何内容。这种无意义的标签导致了新词“div-itis”的产生,或者更常见的“divitis”

HTML5 的主要目的之一就是解决这些缺点。HTML5 为文档的不同部分指定了几个新的标签,新的语义标签,以及用于提高交互性和扩展表单功能的标签。

部分

维持价

好的

所有主流浏览器都支持至少后两个版本的 section 元素。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/sections.html#sections

W3C 候选人推荐:http://www.w3.org/TR/html5/sections.html#sections

HTML5 包括一组新标签,旨在解决以前版本的 HTML 中缺乏结构化标签的问题。标记即使是中等复杂程度的文档也暴露了原始 HTML 标记集中的几个弱点,这些弱点导致将无意义的标记用于许多常见目的,如导航部分、文档页眉和文档页脚。

新标签如下:

  • 一篇文章是一个页面中一组完整、独立的内容。从概念上讲,一篇文章可以被分发或重用。有效文章的示例包括大型杂志中的一篇杂志文章、一篇博客文章、用户界面中可重用的小部件或任何其他自包含的内容集。
  • 旁白是表示侧边栏的一种方式:一组独立于周围内容并与之相切的内容。例子包括引用、侧边栏,甚至是大文档中的广告部分。
  • 导航部分是指向其他文章或其他文档的主要导航链接。它通常不用于次要链接的集合,例如经常被归入页脚的链接(在这种特定情况下,<footer>标记被认为在语义上是足够的)。
  • <footer>这个名副其实的标签代表了包含 section 元素的页脚(<body><article>等等)。).页脚通常包含有关包含节元素的信息,如版权信息、联系信息以及支持文档和站点地图的链接。
  • <header>``<header>标签将当前包含的 section 元素的一组介绍性标签(<body><article>等)组合在一起。).标题可以包含导航、搜索表单,甚至是文档的目录和内部链接。
  • 标签用于将主题相似的内容组合在一起,通常带有某种标题。

在引入这些标签之前,这些部分通常是用相关 CSS 类的<div>标签标记的,如清单 2-1 中的所示。

清单 2-1 。带有无意义标签 0073 的旧标记

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
body {
  margin: 0;
  padding: 0;
}
.page {
  background-color: #C3DBE8;
}
.header {
  background-color: #DDDDDD;
}
.header li {
  display: inline-block;
  border: 1px solid black;
  border-radius: 5px;
  padding: 0 5px;
}
.footer {
  background-color: #DDDDDD;
}
    </style>
  </head>
  <body>
    <div class="page">
      <div class="header">
        <h1>Lorem Ipsum Dolor Sit Amet</h1>
        <div class="navigation">
          <ul>
            <li>one</li>
            <li>two</li>
            <li>three</li>
          </ul>
        </div>
      </div>
      <div class="section">
        <h2>Section Header</h2
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin congue leo ut nut tincidunt, sed hendrerit justo
          tincidunt. Mauris vel dui luctus, blandit felis sit amet,
          mollis enim. Nam tristique cursus urna, id vestibulum
          tellus condimentum vulputate. Aenean ut lectus adipiscing,
          molestie nibh vitae, dictum mauris. Donec lacinia odio
          sit amet odio luctus, non ultrices dui rutrum. Cras
          volutpat tellus at dolor rutrum, non ornare nisi
          consectetur. Pellentesque sit amet urna convallis, auctor
          tortor pretium, dictum odio. Mauris aliquet odio vel
          congue fringilla. Mauris pellentesque egestas lorem.</p>
      </div>
      <div class="aside">
        <h2>Aside Header</h2>
        <p>Vivamus hendrerit nisl nec imperdiet bibendum. Nullam
          imperdiet turpis vitae tortor laoreet ultrices. Etiam
          vel dignissim orci, a faucibus dui. Pellentesque
          tincidunt neque sed sapien consequat dignissim.</p>
      </div>
      <div class="footer">
        <div class="address">
          Sisko’s Creole Kitchen, 127 Main Street,
          New Orleans LA 70112
        </div>
      </div>
    </div>
  </body>
</html>

清单 2-1 将你的内容分成一个单独的“页面”,包含在一个应用了类"page"<div>标签中。在这个页面中,你有一个带有导航的页眉、一个部分、一个旁白和一个页脚。您还对标记应用了一些基本样式,以更好地说明页眉和页脚部分,并使导航元素看起来更像按钮,而不是简单的无序列表。

这是您可能已经习惯看到的那种标记,除了它在很大程度上依赖于无意义的<div>标签这一事实之外,它没有任何问题。然而,有了新的 HTML5 标签,你可以去掉所有的<div>标签,用语义标签代替它们,就像你在清单 2-2 中做的那样。

清单 2-2 。带有 HTML5 语义标签的新热点标记

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
body {
  margin: 0;
  padding: 0;
}
.page, article {
  background-color: #C3DBE8;
}
.header, header {
  background-color: #DDDDDD;
}
.header li, header li {
  display: inline-block;
  border: 1px solid black;
  border-radius: 5px;
  padding: 0 5px;
}
.footer, footer {
  background-color: #DDDDDD;
}
    </style>
  </head>
  <body>
    <article>
      <header>
        <h1>Lorem Ipsum Dolor Sit Amet</h1>
        <nav>
          <ul>
            <li>one</li>
            <li>two</li>
            <li>three</li>
          </ul>
        </nav>>
      </header>
      <section>
        <h2>Section Header</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
          Proin congue leo ut nut tincidunt, sed hendrerit justo
          tincidunt. Mauris vel dui luctus, blandit felis sit amet,
          mollis enim. Nam tristique cursus urna, id vestibulum
          tellus condimentum vulputate. Aenean ut lectus adipiscing,
          molestie nibh vitae, dictum mauris. Donec lacinia odio
          sit amet odio luctus, non ultrices dui rutrum. Cras
          volutpat tellus at dolor rutrum, non ornare nisi
          consectetur. Pellentesque sit amet urna convallis, auctor
          tortor pretium, dictum odio. Mauris aliquet odio vel
          congue fringilla. Mauris pellentesque egestas lorem.</p>
      </section>
      <aside>
        <h2>Aside Header</h2>
        <p>Vivamus hendrerit nisl nec imperdiet bibendum. Nullam
          imperdiet turpis vitae tortor laoreet ultrices. Etiam
          vel dignissim orci, a faucibus dui. Pellentesque
          tincidunt neque sed sapien consequat dignissim.</p>
      </aside>
      <footer>
        <address>
          Sisko’s Creole Kitchen, 127 Main Street,
          New Orleans LA 70112
        </address>
      </footer>
    </article>
  </body>
</html>

您已经用相关的语义 HTML5 标签替换了所有无意义的 div。您还更新了样式表,因此新标签将与应用于您移除的<div>标签的旧类共享相同的样式。

浏览器对这两个例子的渲染略有不同。不同浏览器之间的区别是不同的:Internet Explorer 10 的变化最小,唯一的区别是包含在<address>标签中的文本自动以斜体显示。Chrome 和 Firefox 的差别更大,如图 2-1 中的所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1 。Chrome 渲染的清单 2-1 截图(左)和 Firefox 渲染的清单 2-2 截图(右)

正如你所看到的,在两种浏览器中,<header>标签中的<h1>标签的字体较小,而且它们都以斜体显示<address>标签中的文本,就像 Internet Explorer 一样。如果您正在迁移到新的语义标签,请确保考虑到这些差异。

分组

维持价

好的

所有主流浏览器至少支持最近两个版本的<figure><figcaption>特性。Internet Explorer 本身不支持<main>标签,但其他浏览器支持。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/grouping-content.html#grouping-content

W3C 候选人推荐:http://www.w3.org/TR/html5/grouping-content.html#grouping-content

HTML5 为分组内容定义了一些新标签。这些标签与 HTML5 Section 标签的不同之处在于,它们将给定的一组数据定义为特定类型的数据,而 Section 标签为文档提供结构。新标签如下:

  • <figure>该标签用于将一组独立于主文档流的内容组合在一起,但从文档流内部引用。图形的例子包括插图、屏幕截图和代码片段。
  • <figcaption>该标签用于为<figure>标签提供标题。标题是可选的。
  • 在 W3C 和 WHATWG 规范中,<main>标签的定义是不同的。根据 W3C 的规定,<main>标签应该用来将文档或应用中与主题或功能相关的主要内容组合在一起。根据 WHATWG 的说法,<main>标签没有内在含义,而是代表其内容。W3C 的 bugbase: https://www.w3.org/Bugs/Public/show_bug.cgi?id=21553中的 Bug 21553 详细解释了这种差异的原因。

新的分组标签使用起来很简单,如清单 2-3 所示。

清单 2-3 。使用新的 HTML5 分组标签

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
figure figcaption {
  font-style: italic;
}

figure pre {
  line-height: 1.6em;
  font-size: 11px;
  padding: 1em 0.5em 0.0em 0.9em;
  border: 1px solid #bebab0;
  border-left: 11px solid #ccc;
  margin: 0.3 0 1.7em 0.3em;
  overflow: auto;
  max-height: 500px;
  position: relative;
  background: #faf8f0;
}
    </style>
  </head>
  <body>
    <main>
      <article>
        <h1>Main, Figure and Figcaption</h1>
        <h2>Best Things Ever</h2>
        <p>Vivamus hendrerit nisl nec imperdiet bibendum. Nullam
          imperdiet turpis vitae tortor laoreet ultrices. Etiam
          vel dignissim orci, a faucibus dui. Pellentesque
          tincidunt neque sed sapien consequat dignissim.</p>
          <figure>
            <figcaption>Using Figure and Figcaption for Code Samples</figcaption>
            <pre>
[sample code here]
            </pre>
          </figure>
          <p>More content about Main, Figure and Figcaption...</p>
      </article>
    </main>
  </body>
</html>

这个示例使用<main>来表示示例文档的主要部分,使用<figure><figcaption>来定义代码示例区域。你还在代码区域及其标题上应用了一些简单的 CSS 样式,使其从文档的其余部分中更加突出,如图 2-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2 。火狐渲染的清单 2-3 截图

在这个截图中,你可以看到浏览器对<figure>标签应用了一些默认的边距,这在不同的浏览器中是相当一致的。

语义学

维持价

混合

任何浏览器都很少支持<bdi><data><ruby><rt><rp><time>

<mark>的支持很好(至少可以追溯到主流浏览器的两个版本),对<wbr>的支持非常好(可以追溯到主流浏览器的最早版本)。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#text-level-semantics

W3C 候选人推荐:http://www.w3.org/TR/html5/text-level-semantics.html#text-level-semantics

HTML5 包括几个新的语义标签,旨在帮助澄清内容类型。

  • <bdi>双向隔离元素用于隔离可能以不同于周围文本的方向呈现的文本的行内范围。
  • <data>``<data>标签用于将机器可读数据与其包含的内容相关联。它提供了用数据 002E 标注内容的语义方式
  • 此标签用于标记文档中的事件,如搜索结果。
  • <ruby><rp><rt>这些标签用于拼音注释,用于显示东亚字符的发音。有关红宝石注释的详细信息,请参见http://www.w3.org/TR/ruby/http://en.wikipedia.org/wiki/Ruby_character
  • <time>``<time>标签与<data>相似,它提供了一种将数据(在本例中,具体是日期/时间数据)与所包含的内容相关联的方法。
  • <wbr>断字机会标签用于指示文档流中的一个位置,在该位置浏览器可以启动一个换行符,尽管其内部规则可能不会这样做。它对双向排序没有影响,如果浏览器确实在标签处开始换行,则不使用连字符。

不幸的是,对这些标签的支持相当少。<data><time>标签,以及 Ruby 注释标签,即使在最现代的浏览器中也没有得到广泛的支持。

然而,<mark>标签得到了很好的支持,并且和其他内联标签一样易于使用。清单 2-4 显示了一个非常简单的使用<mark>标记来突出显示文档中的某些单词。

清单 2-4 。在文档中标记单词

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
mark {
  background-color: #E3DA5D;
}
    </style>
  </head>
  <body>
    <article>
      <h1>Using the &lt;mark&gt; tag</h1>
      <p>Vivamus hendrerit nisl nec imperdiet <mark>bibendum</mark>. Nullam
        imperdiet turpis vitae tortor laoreet ultrices. Etiam
        vel dignissim orci, a faucibus dui. Pellentesque
        tincidunt <mark>neque</mark> sed sapien consequat dignissim.</p>
    </article>
  </body>
</html>

本例在所有浏览器中呈现相同的效果(图 2-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3 。火狐渲染的清单 2-4 截图

<wbr>标签可能是所有 HTML5 标签中支持最广泛的标签之一。这是一个在所有浏览器中都可用的非标准标签,它是由 HTML5 引入标准的。它用于在长词中提供断词建议,这在特定情况下很有用。清单 2-5 显示了一个简单的例子,在插入<wbr>标签之前有很长的单词:

清单 2-5 。文档中的长单词

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
.larger {
  font-size: 2em;
}
    </style>
  </head>
  <body>
    <article>
      <h1>Using the &lt;wbr&gt; tag</h1>
      <p>Here are some long words in a slightly larger font size to demonstrate
        how useful the &lt;wbr&gt; tag can be.</p>
      <p class="larger">Supercalifragilisticexpialidocious and antidisestablishmentarianism,
        also pneumonoultramicroscopicsilicovolcanoconiosis.</p>
    </article>
  </body>
</html>

如图 2-4 所示,清单 2-5 在所有现代浏览器中呈现出你所期望的效果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-4 。Firefox 中清单 2-5 的渲染

你可以很容易地使用一些<wbr>标签来帮助浏览器决定在哪里打断那些长单词,如清单 2-6 中的所示。

清单 2-6 。建议在大词中换行

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
.larger {
  font-size: 2em;
}
    </style>
  </head>
  <body>
    <article>
      <h1>Using the &lt;wbr&gt; tag</h1>
      <p>Here are some long words in a slightly larger font size to demonstrate
        how useful the &lt;wbr&gt; tag can be.</p>
      <p class="larger">Supercali<wbr>fragilistic<wbr>expialidocious and
        antidis<wbr>establishment<wbr>arianism.</p>
    </article>
  </body>
</html>

如果需要的话,浏览器可以根据我们的建议断词(图 2-5 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-5 。在不同的浏览器宽度下渲染清单 2-6

如你所见,如果需要的话,浏览器现在可以使用我们的断词建议。如果您在屏幕空间非常宝贵的狭窄布局上工作,例如在移动设备上,这可能特别有用。

音频和视频内容

维持价

好的

所有现代浏览器都支持至少后两个版本的音频和视频元素,但请参阅以下有关格式支持的信息。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/edits.html#embedded-content

W3C 候选人推荐:www.w3.org/TR/html5/embedded-content-0.html#embedded-content-0

以前版本的 HTML 最大的缺点之一是它们不能方便地在网页上包含多媒体内容。HTML5 有专门解决这个问题的新标签。有了这些新标签,在网页上包含多媒体内容就像包含静态图像一样简单。更好的是,所有现代浏览器都非常好地支持这些功能。

在 HTML5 之前,如果你想在网页中嵌入一个视频,你需要一个第三方插件,它能够播放你想要的内容,还能调节音量、快进或快退内容等等。通过 HTML5,浏览器制造商已经将这些功能内置到他们的软件中。这些功能包括控制回放的用户界面和播放各种音频和视频编码媒体格式的能力。

一个不幸的复杂情况是,音频和视频都可以用许多不同的格式编码,而这些格式中的许多都有专利障碍,使得浏览器制造商不愿意支持它们。因此,尽管所有现代浏览器都支持多媒体标签,但有些浏览器支持的格式与其他浏览器不同。有关何种浏览器支持何种格式的详细信息,请参见第七章。

另一个复杂因素来自与多媒体的互动。例如,用户经常想要跳过内容,根据需要前进或后退。支持这样的交互功能需要服务器能够对这些用户交互做出反应,并根据需要提供部分内容。简单的 web 服务器通常没有这种能力,尽管它们中的许多可以配置成这样。有关配置多媒体服务器的更多信息,请参见第七章。

嵌入的音频内容

有了 HTML5 <audio>标签,你可以将音频内容嵌入到你的网页中,就像包含图像一样简单。像任何 HTML 标签一样,<audio>标签有几个可以设置的属性:

  • autoplay:这是一个布尔标志,当设置为(任何值,甚至false)时,将使浏览器立即开始播放音频内容,而不需要停下来进行缓冲。
  • controls:如果设置了此属性,浏览器将显示音频播放器的默认用户界面控件(音量控件、进度条/滑动条等)。).
  • loop:如果设置了该属性,浏览器将循环播放指定文件。
  • muted:该属性指定默认静音播放。
  • preload:该属性用于向浏览器提供如何为指定内容提供最佳用户体验的提示。可以取三个值:nonemetadataautonone值指定作者想要最小化音频内容的下载,可能因为内容是可选的,或者因为服务器资源是有限的。metadata值指定作者推荐下载音频内容的元数据(持续时间、曲目列表、标签等。)和可能的前几帧内容。auto值指定浏览器可以把用户的需求放在第一位,而不会给服务器带来风险。这意味着浏览器可以开始缓冲内容、下载所有元数据等等。请注意,这些值可以在页面加载后更改。例如,如果您有一个包含许多<audio>标签的页面,每个标签的preload都被设置为none以防止淹没服务器,当用户选择他们想要听到的<audio>标签时,您可以动态地将其preload值更改为auto以提供更好的用户体验。这使您能够平衡用户体验和可用资源。
  • src:这个属性指定内容的来源,就像一个<img>标签一样。如果需要的话,可以省略这个属性,而在<audio>标签中包含一个或多个<source>标签。

<audio>标签不是自动关闭的,因此需要一个关闭标签。请注意,由于较旧的浏览器不支持<audio>标记,因此包含在其中的任何内容都将在这些浏览器中显示,从而提供了一种在较旧的浏览器中提供替代内容的向后兼容方式。

音频标签非常容易使用,如清单 2-7 所示。

清单 2-7 。在网页中嵌入音频内容

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the &lt;audio&gt; tag</h1>
      <audio controls="controls" src="../media/windows-rolled-down.mp3">
      </audio>
    </article>
  </body>
</html>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意本节的例子使用音频文件。您应该根据需要替换您自己的文件。

清单 2-7 选择显示本地玩家的控件来演示它们默认的样子。每个浏览器的原生播放器看起来略有不同,功能也略有不同(图 2-6 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-6 。清单 2-7 以 Chrome(左)和 Firefox(右)呈现

Chrome 的默认音频播放器有一个音量滑块,而 Firefox 的播放器有一个指示当前播放时间的工具提示。设计播放器的样式是不可能的,所以如果你想在所有浏览器上有一致的外观和感觉,你必须构建自己的播放器——这实际上很容易做到,因为 HTML5 还指定了一个 JavaScript API 来处理<audio>标签。有关该 API 的详细信息,请参见第七章。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示在 Chrome 中,音频和视频播放器被实现为使用新的 Shadow DOM 规范的 web 组件。使用 Shadow DOM API 可以直接访问播放器的组件并设置样式。例如,播放器的背景是一个阴影<div>,它可以用 CSS 选择器audio::-webkit-media-controls-panel选择,并且它的外观(例如背景颜色)可以根据需要改变。同样,音量条是一个带有选择器audio::-webkit-media-controls-volume-slider<input type="range">标签。不幸的是,在撰写本文时,Chrome 和 Opera 是仅有的两种支持影子 DOM 规范的浏览器。其他浏览器也可能使用 Shadow DOM 实现其播放器,当它们完全支持该规范时,它们的播放器也可能变得可访问,从而允许 web 开发人员控制播放器的外观,而不必从头开始构建自己的播放器。

嵌入式视频内容

HTML5 <video>标签支持浏览器中的基本视频功能。它的功能类似于<audio>标签,并且有一组类似的可以设置的属性:

  • autoplay:这是一个布尔标志,当它被设置(设置为任何值,甚至是false)时,将使浏览器立即开始播放视频内容,而不需要停下来进行缓冲。
  • controls:如果设置了该属性,浏览器将显示视频播放器的默认用户界面控件(音量控件、进度条/滑动条等)。).
  • height:该属性可用于指定视频播放器的高度,以像素为单位。
  • loop:如果设置了该属性,浏览器将循环播放指定文件。
  • muted:该属性指定默认静音播放。
  • poster:该属性可用于指定视频播放前海报显示的 URL。如果没有指定海报,那么一旦加载,播放器将默认显示视频的第一帧。
  • preload:该属性用于给浏览器提供一个提示,如何为指定的内容提供最佳的用户体验。可以取三个值:nonemetadataautonone值指定作者希望最小化视频内容的下载,可能是因为内容是可选的,或者是因为服务器资源有限。metadata值指定作者推荐下载视频内容的元数据(持续时间、曲目列表、标签等。)和可能的前几帧内容。auto值指定浏览器可以把用户的需求放在第一位,而不会给服务器带来风险。这意味着浏览器可以开始缓冲内容、下载所有元数据等等。请注意,这些值可以在页面加载后更改。例如,如果您有一个包含许多<video>标签的页面,每个标签都将preload设置为none以防止淹没服务器,当用户选择他们想要查看的<video>标签时,您可以动态地将其preload值更改为auto以提供更好的用户体验。这使您能够平衡用户体验和可用资源。
  • src:这个属性指定内容的来源,就像一个<img>标签一样。如果需要的话,可以省略这个属性,而在<video>标签中包含一个或多个<source>标签。
  • width:该属性可用于指定视频播放器的宽度,以像素为单位。

标签<video>和标签<audio>一样容易使用,如清单 2-8 所示。

清单 2-8 。在网页中嵌入视频内容

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the &lt;video&gt; tag</h1>
      <video controls="controls" src="../media/podcast.m4v">
      </video>
    </article>
  </body>
</html>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意本节的例子使用的是视频文件。您应该根据需要替换您自己的文件。

<audio>标签一样,每个浏览器提供了稍微不同的视频播放器,如图 2-7 中的所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-7 。清单 2-8 以 Chrome(左)和 Firefox(右)呈现

和以前一样,这两种浏览器的视频播放器界面略有不同。如果你想改变播放器的外观和它的控制,你必须自己构建。

指定多个来源

<audio><video>标签都有一个 src属性,但是您可以放弃该属性,在<audio><video>标签中包含一个或多个<source>标签。您甚至可以为同一个文件指定不同的编码,从而避开浏览器在编码支持方面的任何限制。浏览器将沿着<source>标签列表向下,并播放它支持的第一个文件。

<source>标签有两个属性:

  • src:音频文件的 URL。
  • type:音频文件的 MIME 类型,带有可选的编解码器参数,根据 RFC 4281 指定。

举个例子,假设你有一个我们想要提供的视频。你有两种不同的格式:Ogg Vorbis 和 MP4。使用两个<source>标签,如清单 2-9 中的所示。

清单 2-9 。为多媒体指定多个源

<video controls>
  <source src="../media/video-1.mp4" type="video/mp4">
  <source src="../media/video-1.ogv" type="video/ogg">
</video>

通过使用“类型”属性中的可选编解码器参数,您可以非常精确地了解音频和视频的编码。例如,如果你有一个 H.264 视频(profile 3)和低复杂度的 AAC 音频都包含在一个 MP4 容器中,你可以指定如清单 2-10 所示的编解码器:

清单 2-10 。为视频源指定音频和视频编解码器

<source src="../media/video-1.mp4" type="video/mp4, codecs=’ avc1.4D401E, mp4a.40.2’">

这对于为您的视频提供尽可能高质量的编码,同时允许大多数浏览器访问它,而不管编码支持限制,特别有用。

互动元素

维持价

不支持

除了实验版本,现代浏览器不支持交互元素。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#interactive-elements

W3C 候选人推荐:http://www.w3.org/TR/html5/interactive-elements.html

HTML5 包括一组新的交互式元素,旨在提供一些可以在网页和应用中使用的预构建用户界面元素。不幸的是,大多数浏览器还不支持这些特性,但是随着时间的推移,支持程度可能会提高。

对话

最令人兴奋的新特性之一是<dialog>标签,它提供了轻松创建弹出对话框的能力。包含在<dialog>标签中的任何内容都不会呈现在文档中,直到您调用其显示方法之一:

  • show:调用这个方法将打开一个标准的弹出对话框。
  • showModal:调用这个方法将打开一个模态对话框,对话框后面的页面是灰色的。

此外,每个对话框在关闭时都会调度一个close事件。

清单 2-11 是一个简单的例子,演示了如何使用<dialog>标签。在撰写本文时,唯一支持<dialog>标签的浏览器是 Chrome,即使这样,你也必须在chrome://flags中激活实验性的网络平台功能。如果您启用了这些特性,这个例子将会运行得很好。

清单 2-11 。网络对话

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
li {
  display: inline-block;
  background-color: #A9DCF5;
  border-radius: 4px;
  padding: 2px 10px;
  cursor: pointer;
}
    </style>
  </head>
  <body>
    <article>
      <h1>Using the &lt;dialog&gt; tag</h1>
      <ul>
        <li id="open-dialog">Open Dialog</li>
        <li id="open-modal">Open Modal</li>
      </ul>
      <dialog id="dialog">
        <p>Hello World!</p>
        <button id="close-dialog">Okay</button>
      </dialog>
    </article>
    <script>
var myDialog = document.getElementById(’dialog’),
    openDialog = document.getElementById(’open-dialog’),
    openModal = document.getElementById(’open-modal’),
    closeDialog = document.getElementById(’close-dialog’),
    status = document.getElementById(’status’);

closeDialog.addEventListener(’click’, function(event) {
  myDialog.close();
}, false);

openDialog.addEventListener(’click’, function(event) {
  myDialog.show();
}, false);

openModal.addEventListener(’click’, function(event) {
  myDialog.showModal();
}, false);

myDialog.addEventListener(’close’, function(event) {
  alert(A close event was dispatched.);
}, false);
    </script>
  </body>
</html>

这将呈现一个非常简单的对话框,如图 2-8 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-8 。清单 2-11 产生的对话框

每当您关闭其中一个对话框时,close 事件将被触发,产生一个警告,内容为“close 事件已被调度”

这是对话框的默认外观。可以很容易地用 CSS 对它们进行样式化,使它们更有吸引力,当然它们可以包含任何内容,包括图像、表单域等等。您还可以为模式实例设置背景样式;它是一个伪元素,可以使用对话框选择器上的::backdrop来访问。

例如,在清单 2-12 中,如果你在你的样式表中添加几个简单的 CSS 指令,你会有一个更吸引人的对话框。

清单 2-12 。网络对话框的 CSS 样式

dialog {
  text-align: center;
  padding: 1.5em;
  margin: 1em auto;
  border: 0;
  border-radius: 8px;
  box-shadow: 0 2px 10px #111;
}

dialog::backdrop {
  background-color: rgba(187, 217, 242, 0.8);
}

这些样式将改变对话框和模态背景的外观(图 2-9 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-9 。应用 CSS 样式的 Web 对话框(模式状态)

虽然目前只有 Chrome 支持<dialog>标签,但是在https://github.com/GoogleChrome/dialog-polyfill有一个 polyfill 可以提供其他浏览器中的大部分功能。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示 Polyfill 是一个术语,指在浏览器中启用或复制不支持的功能的库。polyfill 的另一个术语是垫片

逐步解密

一个常见的 UI 特性是渐进式披露:你提供一个简单的条目列表,当用户点击一个条目时,下面的空间就会展开,显示更多的信息。这些小部件根据使用的框架有不同的名称(例如,jQuery UI 将它们称为 accordions )。HTML5 使用<summary><details>标签包含了这个特性的定义。<details>标签包含了所有需要的内容,包括一个<summary>标签,它应该只包含内容的简短摘要。一个<details>标签的默认呈现是只显示前面有一个小三角形的<summary>标签的内容。然后,用户可以点击摘要上的任何地方,其余的内容就会显示出来。您可以通过赋予给定的<details>标签 open 属性来指定该标签在打开状态下呈现。

目前,Chrome、Opera 和 Safari 是唯一支持<details><summary>标签的浏览器。火狐将会支持这些标签,你可以在https://bugzilla.mozilla.org/show_bug.cgi?id=591737的 bug 591737 中查看它们的支持状态。Internet Explorer 中的支持状态未知。

标签使用起来很简单,如清单 2-13 所示。

清单 2-13 。逐步解密

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the &lt;summary&gt; and &lt;details&gt; tags</h1>
      <details>
        <summary>Item 1</summary>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus
          accumsan orci nec justo rhoncus facilisis. Integer pellentesque
          ipsum vitae semper lacinia. Quisque non nisl rutrum, porta est at,
          ultrices neque. Aenean consequat, lacus vulputate vestibulum
          faucibus, turpis magna mollis quam, a congue neque lorem at
          justo.</p>
      </details>
      <details>
        <summary>Item 2</summary>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus
          accumsan orci nec justo rhoncus facilisis. Integer pellentesque
          ipsum vitae semper lacinia. Quisque non nisl rutrum, porta est at,
          ultrices neque. Aenean consequat, lacus vulputate vestibulum
          faucibus, turpis magna mollis quam, a congue neque lorem at
          justo.</p>
      </details>
      <details open>
        <summary>Item 3--this one will be open by default</summary>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus
          accumsan orci nec justo rhoncus facilisis. Integer pellentesque
          ipsum vitae semper lacinia. Quisque non nisl rutrum, porta est at,
          ultrices neque. Aenean consequat, lacus vulputate vestibulum
          faucibus, turpis magna mollis quam, a congue neque lorem at
          justo.</p>
      </details>
      <details>
        <summary>Item 3</summary>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus
          accumsan orci nec justo rhoncus facilisis. Integer pellentesque
          ipsum vitae semper lacinia. Quisque non nisl rutrum, porta est at,
          ultrices neque. Aenean consequat, lacus vulputate vestibulum
          faucibus, turpis magna mollis quam, a congue neque lorem at
          justo.</p>
      </details>
    </article>
  </body>
</html>

在 Chrome 中,这个例子渲染得很好,如图 2-10 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-10 。清单 2-13 用 Chrome 渲染的

单击关闭的项目会显示其隐藏的内容,单击打开的项目会隐藏其内容。

形式

表单在 HTML5 中得到了显著的改进。该规范包括表单的新标签(如数据列表、进度表和日期选择器)以及现有表单标签的新属性。这些新功能旨在使表单对用户更具交互性,并且更易于构建和维护。

新表单元素

维持价

混合

最近两个版本的主流浏览器都很好地支持这些特性。然而,Internet Explorer 不支持<meter>标签。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#forms

W3C 候选人推荐:http://www.w3.org/TR/html5/forms.html#forms

HTML5 有一些新的表单元素,它们是专门为实现过去几年发展起来的通用用户界面模式而设计的。具体来说,这些新标签实现了自动完成功能和进度条。

数据列表

第一个新标记实现了一个常见的自动完成功能:当您开始在表单域中键入内容时,会出现一个下拉列表,其中包含与已经键入的内容相匹配的选项。随着您继续键入,列表会变得更加具体,并且您可以随时使用箭头键来选择其中一个选项。这种自动完成字段通常被称为数据列表(有时也称为组合框),HTML5 有一个新的<datalist>标签来实现这个用户界面元素。

实际上,<datalist>标签包含<option>标签,数据列表中的每一项都有一个标签。<datalist>元素本身不在页面中呈现,可以出现在文档结构中的任何地方。创建后,数据列表必须与输入字段相关联才能使用。给<datalist>标签一个唯一的id属性。要将其与一个<input>元素相关联,将该元素的 list 属性设置为惟一的id。这告诉浏览器使用<input>元素呈现指定的数据列表,如清单 2-14 所示。

清单 2-14 。数据列表

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <!-- Note the datalist can be anywhere -->
    <datalist id="browsers">
      <option value="Chrome">
      <option value="Firefox">
      <option value="Internet Explorer">
      <option value="Opera">
      <option value="Safari">
    </datalist>
    <article>
      <h1>Using the &lt;datalist&gt; tag</h1>
      <input list="browsers" />
    </article>
  </body>
</html>

与其他 HTML5 用户界面元素一样,每个浏览器呈现的数据列表略有不同(图 2-11 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-11 。清单 2-14 在 Chrome(左)和 Firefox(右)中呈现

可以看到,Chrome 在输入栏的右侧提供了下拉箭头提示,表示输入栏是数据列表,Firefox 在下拉菜单上有轻微的阴影。不同浏览器之间的列表功能保持不变。

新的<meter>标签提供了一个简单的仪表栏或仪表视觉元素。此条旨在模拟已知范围内的测量值,或整体的分数值(例如,卷、磁盘使用情况等)。).它不应该用于显示进度(例如,在下载中);为此使用新的<progress>标签。

<meter>标签具有以下属性:

  • value:要显示的当前值。如果指定,该值必须在最小值和最大值之间。如果没有设置值,或者值不正确,浏览器将默认为 0。如果已指定,但该值大于 max 属性,则该值将被设置为 max 属性的值。如果该值小于 min 属性,则该值将被设置为 min 属性的值。
  • min:范围的最小值。如果未指定,则默认为 0。
  • max:范围的最大值。必须大于 min 属性的值(如果指定)。默认为 1。

也可以在测量范围内指定子范围。可以有low范围、high范围和optimum范围。low范围从min值到指定值,而high范围从high值到max值。通过使用optimum属性在范围内指定一个数字,可以将low范围或high范围指定为optimum范围。

  • low:低量程的最高值。当 value 属性在low范围内时,默认情况下,条将呈现黄色。
  • high:high范围的最小值,从该值到max属性的值。当value属性在high范围内时,默认情况下,条会呈现黄色。
  • optimum:表示范围的最佳值。该值必须在范围的minmax值之间。如果使用了lowhigh范围,在其中一个范围内指定一个optimum值将指示这些范围中的哪一个是优选的。当该值在首选范围内时,该条将呈现绿色。当它在另一个范围内时,它将呈现红色。

创建这些计量器很简单,只需在文档中添加一个<meter>标签,如清单 2-15 所示。

清单 2-15 。米条

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the &lt;meter&gt; tag</h1>
      <p>Simple meter from 1 to 100, value set to 25:<br>
        <meter min="1" max="100" value="25"></meter>
      </p>
      <p>Simple meter from 1 to 100, low range from 1 to 25, high range from
         75 to 100, value set to 90:<br>
        <meter min="1" max="100" low="25" high="75" value="90"></meter>
      </p>
      <p>Simple meter from 1 to 100, low range from 1 to 25, high range from
         75 to 100, value set to 10:<br>
        <meter min="1" max="100" low="25" high="75" value="10"></meter>
      </p>
      <p>Simple meter from 1 to 100, low range from 1 to 25, high range from
         75 to 100, optimum set to 10, value set to 10:<br>
        <meter min="1" max="100" low="25" high="75" optimum="10" value="10"></meter>
      </p>
      <p>Simple meter from 1 to 100, low range from 1 to 25, high range from
         75 to 100, optimum set to 10, value set to 10:<br>
        <meter min="1" max="100" low="25" high="75" optimum="10" value="90"></meter>
      </p>
    </article>
  </body>
</html>

这些仪表在不同的浏览器中呈现得相当一致(图 2-12 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-12 。清单 2-15 在 Chrome(左)和 Firefox(右)中呈现

输出

新的<output>标签提供了一种在表单中指定计算输出或其他用户操作的方式。它没有任何特殊的功能;相反,它提供了一个语义标签来标记这类内容。

清单 2-16 中显示了一个简单的例子。

清单 2-16 。表单中的计算输出

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the &lt;output&gt; tag</h1>
      <form id="mainform" onsubmit="return false">
        <label for="input-number">Temperature</label>
        <input name="input-number" id="input-number" type="number" step="any"><br>
        <input type="radio" name="convert-choice" id="radio-ftoc" checked value="ftoc">
        <label for="radio-ftoc">Convert Fahrenheit to Celcius</label><br>
        <input type="radio" name="convert-choice" id="radio-ctof" value="ctof">
        <label for="radio-ctof">Convert Celcius to Fahrenheit</label><br>
        Result:
        <output name="output-target" for="input-number" id="output-target"></output>
      </form>
    </article>
    <script>
var myForm = document.getElementById(’mainform’);
var converter = {
  ctof: function(degreesC) {
    return (((degreesC * 9) / 5) + 32);
  },
  ftoc: function(degreesF) {
    return (((degreesF - 32) * 5) / 9);
  }
};
myForm.addEventListener(’input’, function() {
  var inputNumber = document.getElementById(’input-number’),
      outputTarget = document.getElementById(’output-target’);
  var sel = document.querySelector(’input[name=convert-choice]:checked’).value;
  outputTarget.value = convertersel);
}, false);
    </script>
  </body>
</html>.

这是一个简单的例子,但是您使用了一些巧妙的技巧。

  • 您创建了一个 converter 对象,它有两个方法,ctof(将摄氏温度转换为华氏温度)和ftoc(将华氏温度转换为摄氏温度)。
  • 您将单选按钮的值属性之一设置为ctof,将另一个设置为ftoc
  • 您使用选择器input[name=convert-choice]:checked来获取选中的单选按钮,然后获取其值(ctof 或 ftoc)。
  • 然后,只需使用查询结果,就可以直接访问 converter 对象的正确方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示 JavaScript 也受一个标准——ECMA-262——控制,它明确定义了两种访问对象成员的方法:点符号或括号符号。所以objectName.identifierName在功能上等同于objectName[<identifierName string>],即使所讨论的对象不是数组。有关详细信息,请参见http://www.ecma-international.org/ecma-262/5.1/的 ECMA-262 中的第 11.2.1 节“属性访问器”。

图 2-13 显示了用 Chrome 渲染的清单 2-16 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-13 。清单 2-16 用 Chrome 渲染的

进步

HTML5 定义了一个新的<progress>标签,在文档中呈现为一个进度条。它用于指示任务的进度或完成情况,并向用户提供已经完成的工作量和剩余工作量的概念。它不应该用于可视化已知范围内的测量—为此,使用<meter>标签。

<progress>标签具有以下属性:

  • max:活动的最大值。该值必须是有效的正浮点数。如果没有指定max,最大值默认为 1。
  • value:进度的当前值。该值必须是介于 0 和max(如果指定)或 1(如果未指定max)之间的有效浮点数。如果没有指定value,那么进度条被认为是不确定的,这意味着它正在建模的活动正在进行,但是没有给出需要多长时间才能完成的指示。

清单 2-17 提供了一个进度条的简单演示。

清单 2-17 。进度条

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the &lt;progress&gt; tag</h1>
      <p>Downloading file1<br>
        <progress max="100" value="10">10/100</progress> 10%</p>
      <p>Downloading file2<br>
        <progress max="100" value="50" orient="vertical">50/100</progress> 50%</p>
    </article>
  </body>
</html>

如图 2-14 所示,进度条在不同的浏览器中呈现不同的效果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-14 。清单 2-17 在 Chrome(左)、Internet Explorer(中)和 Windows 8 上的 Firefox(右)中呈现

在 MacOS 上渲染时,这些示例看起来也会有所不同。幸运的是,这些酒吧很容易设计。Firefox 和 Internet Explorer 可以直接访问元素的样式,而在 Chrome 中,你必须选择伪元素来更改它们。通过在你的 CSS 中添加一些简单的指令,如清单 2-18 所示,你可以让工具条在所有浏览器中看起来都一样。

清单 2-18 。进度条的 CSS 规则

progress {
  color: #0063a6;
  font-size: .6em;
  line-height: 1.5em;
  text-indent: .5em;
  width: 15em;
  height: 1.8em;
  border: 1px solid #0063a6;
  background-color: #fff;
}
::-webkit-progress-bar {
  background-color: #fff;
}
::-webkit-progress-value {
  background-color: #0063a6;
}

正如你在图 2-15 中看到的,这些条现在在不同的浏览器中呈现相同的效果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-15 。应用 CSS 规则的进度条

一个稍微实际一点的例子是定时器。使用<progress>标签,你可以指示某个分配的时间——例如十秒——正在过去,如清单 2-19 中的所示。

清单 2-19 。十秒钟计时器

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
progress {
  color: #0063a6;
  font-size: .6em;
  line-height: 1.5em;
  text-indent: .5em;
  width: 15em;
  height: 1.8em;
  border: 1px solid #0063a6;
  background-color: #fff;
}
::-webkit-progress-bar {
  background-color: #fff;
}
::-webkit-progress-value {
  background-color: #0063a6;
}
    </style>
  </head>
  <body>
    <article>
      <h1>Using the &lt;progress&gt; tag</h1>
      <h2>Ten Second Timer</h2>
      <p><progress max="10" value="0" id="myProgress">0</progress></p>
    </article>
    <script>
var progress = 0;
var myProgress = document.getElementById("myProgress");
var myTimer = setInterval(function() {
  myProgress.value = ++progress;
  if (progress > 10) {
    clearInterval(myTimer);
  }
}, 1000);
    </script>
  </body>
</html>

这个例子使用 DOM 方法setInterval()每秒运行一个函数来更新进度条的值。当进度条已满时,它用clearInterval()方法取消计时器。

新表单元素属性

HTML5 规范为表单元素提供了一些有用的新属性。同样,这些新属性是专门为解决以前版本的 HTML 表单中的缺点而设计的,并添加了通常需要的功能,到目前为止,这些功能都必须使用 JavaScript 来构建。

自动完成

所有的浏览器都提供了存储表单数据以备后用的功能。这对移动设备特别有帮助,因为它减少了打字。autocomplete属性允许您指定哪些<input>元素可以自动完成,哪些应该总是手动填充。autocomplete属性可以取两个值:on(允许自动完成;这是默认设置)或off(不允许自动完成)。

清单 2-20 是一个简单的例子,有两个表单域,一个允许自动完成,另一个不允许。

清单 2-20 。控制表单中的自动完成

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the autocomplete attribute</h1>
      <form id="test-form" action="#" method="post">
        <p><label for="input-auto">This input allows autocomplete:</label><br>
        <input autocomplete="on" id="input-auto" name="input-auto"></p>
        <p><label for="input-noauto">This input does not allow autocomplete:</label><br>
        <input autocomplete="off" id="input-noauto" name="input-noauto"></p>
        <p><input type="submit"></p>
      </form>
    </article>
  </body>
</html>

几乎在每个浏览器中,您都应该能够填写表单字段并单击提交按钮。然后,重新加载页面。双击第一个表单域,您应该会看到一个包含您之前输入的值的下拉框(图 2-16 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-16 。清单 2-20 用 Chrome(左)和 Firefox(右)呈现

请注意,您必须在浏览器中启用自动填充功能。大多数浏览器在默认情况下会启用它,但出于安全考虑,许多人会将其关闭。如果用户在其浏览器中禁用了该功能,autocomplete 属性将不起作用。

浏览器使用许多线索来确定哪些表单字段应该用什么数据自动完成:字段的名称和 ID、<form>标签的actionmethod属性,等等。这个过程是相当不标准的,并且进入了“魔术”的领域 2012 年,谷歌提议对autocomplete属性进行扩展,以帮助标准化流程。在这个提议中,他们提出了一个具有从address-line1postal-code再到url的一系列值的autocompletetype属性。你可以在http://wiki.whatwg.org/wiki/Autocompletetype阅读他们的完整提案。那个提议从未被完全采纳,但其中的一些部分最终进入了新的自动填充规范,你可以在https://html.spec.whatwg.org/multipage/forms.html#autofill查看。

自(动)调焦装置

autofocus属性允许您指定页面加载时哪个表单字段应该有焦点。因为它是排他的,所以只能在给定页面上的一个表单字段上设置autofocus,当页面加载完成时,焦点将转到该元素。您不能在类型为hidden的表单元素上设置autofocus。自动对焦可以设置为任何<input><button><textarea>字段。

如果你在页面加载时给清单 2-20 中的第二个字段添加一个autofocus属性,它将被聚焦,如清单 2-21 中的所示。

清单 2-21 。自动聚焦输入场

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the autofocus attribute</h1>
      <form id="test-form" action="#" method="post">
        <p><label for="input-auto">This input allows autocomplete:</label><br>
        <input autocomplete="on" id="input-auto" name="input-auto"></p>
        <p><label for="input-noauto">This input does not allow autocomplete:</label><br>
        <input autocomplete="off" id="input-noauto" name="input-noauto" autofocus="autofocus"></p>
        <p><input type="submit"></p>
      </form>
    </article>
  </body>
</html>

当页面加载完成后,第二个输入字段将被选中并准备接收输入,如图图 2-17 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-17 。清单 2-21 用 Chrome 渲染的

当然,只要用户单击浏览器中的其他地方,该字段就会失去焦点,除非用户再次单击该字段,否则它不会返回。自动对焦只发生在页面加载时。

占位符

表单的另一个常见设计特征是在<input>字段中的占位符文本。占位符文本有助于提供有关该字段用途的更多信息,当用户开始键入时,它会消失。HTML5 包括一个新的placeholder属性,可以应用于<input><textarea>字段。为属性指定的值用作字段内的占位符文本。

清单 2-22 是一个简单的表单示例,用户可以在其中编写和发送电子邮件。

清单 2-22 。占位符文本

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <article>
      <h1>Using the placeholder attribute</h1>
      <p><label for="message-title">Title:</label><br>
        <input placeholder="title of message" id="message-title"></p>
      <p><label for="message-body">Body:</label><br>
        <textarea placeholder="body of message" id="message-body"></textarea></p>
      <p><input type="submit" value="Send Email"></p>
    </article>
  </body>
</html>

在现代浏览器中,占位符文本在表单字段内将显示为灰色文本(图 2-18 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-18 。清单 2-22 用 Chrome 渲染的

一旦用户开始在字段中输入,占位符文本就会消失,如图 2-19 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-19 。清单 2-22 用户输入后的

注意,这个例子仍然包括<label>标签。占位符文本是一个很好的设计概念,但是它不应该取代<label>标签,后者是表单可访问性的重要组成部分。您通常不需要这两个标签——正如您在示例中看到的,这些标签有些多余。在这种情况下,你可以简单地用 CSS 隐藏<label>标签,如清单 2-23 所示。

清单 2-23 。用 CSS 隐藏标签

label: {
  display: none;
}

这个简单的 CSS 使得图 2-20 中的表单看起来更好。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-20 。清单 2-23 用 Chrome 渲染的

新输入类型

HTML5 规范还包括了input元素的type属性的新值。您可能很熟悉使用这个属性来创建复选框和单选按钮:

<input type="checkbox">
<input type="radio">

HTML5 增加了几个新类型,为输入字段增加了新的用户界面功能,从颜色和日期选择器到搜索框。不幸的是,不同的浏览器对这些新输入类型的支持大不相同。

对于桌面浏览器,Chrome 的支持最好,Firefox 和 Internet Explorer 都远远落后。不幸的是,这限制了它们在桌面应用中的用途。

在移动浏览器上支持更好。大多数新的输入类型将使用设备的特殊键盘和输入工具。例如,当类型为tel的输入字段在 Safari Mobile 中处于活动状态时,手机将显示电话键盘。这使得移动用户更容易输入电话号码。

尽管这些新类型现在没有得到广泛支持,但对它们的支持正在增长,特别是在移动设备上。考虑到使用特定键盘进行移动输入的好处,使用这些输入类型是一个好习惯,即使它们没有得到广泛支持。

新的输入类型如下:

  • color:允许用户选择颜色。在 Chrome desktop 中,这将显示一个色卡,当单击它时,会显示主机操作系统的颜色选择器用户界面小部件。没有其他浏览器支持此元素。
  • datedatetimedatetime-localmonthtimeweek:这些输入类型允许用户输入日期和时间。在 Chrome 桌面中,这些显示 Chrome 内置的日历和时间选择小部件。在移动设备上,这些显示日期和时间选择器(在 iOS 上,它们以微调器的形式出现)。
  • email:表示用于收集电子邮件地址的输入字段。在移动设备上,这种类型在激活时会显示一个互联网地址友好的键盘。在 iOS 上,这种键盘采用常规键盘的形式,带有@键和. com 键。
  • number:该输入类型指定用户将输入一个数字。在 Chrome 和 Firefox 桌面上,这种输入类型将显示一个简单的增加/减少小部件。在移动设备上,这种输入类型将显示字母数字键盘的数字页。
  • range:显示滑块小工具。这是桌面和移动设备上所有浏览器都广泛支持的唯一输入类型。
  • search:表示该字段为搜索字段。搜索字段和常规输入字段的主要区别在于,搜索字段包含“清除”功能,通常实现为输入字段边缘的×按钮。在 Chrome 和 Internet Explorer 桌面上,这显示了一个简单的搜索栏,右边有一个清除按钮。
  • tel:表示该字段将用于输入电话号码。在移动设备上,这种类型的输入字段将在激活时显示电话号码键盘。
  • url:表示该字段将用于输入一个 URL,很可能是一个网址。在移动设备上,这将显示一个互联网地址友好的键盘,而活跃。

如果你想测试这些输入字段,清单 2-24 有完整的输入字段,你可以把它们加载到任何浏览器中。

清单 2-24 。演示了新的输入类型

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <style>
input {
  display: block;
  margin-bottom: 20px;
}
    </style>
  </head>
  <body>
    <article>
      <h1>New HTML5 Input Types</h1>
      <form id="mainform" onsubmit="return false">
        <label>type=color</label>
        <input type="color">
        <label>type=date</label>
        <input type="date">
        <label>type=datetime</label>
        <input type="datetime">
        <label>type=datetime-local</label>
        <input type="datetime-local">
        <label>type=email</label>
        <input type="email">
        <label>type=month</label>
        <input type="month">
        <label>type=number</label>
        <input type="number">
        <label>type=range</label>
        <input type="range">
        <label>type=search</label>
        <input type="search">
        <label>type=tel</label>
        <input type="tel">
        <label>type=time</label>
        <input type="time">
        <label>type=url</label>
        <input type="url">
        <label>type=week</label>
        <input type="week">
      </form>
    </article>
  </body>
</html>

Chrome 是这个例子中最有趣的浏览器。它为几种类型生成了一些非常有用的小部件,如图 2-21 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-21 。Chrome 桌面中呈现的颜色、日期和搜索输入类型

不推荐使用的元素和过时的参数

HTML5 已经正式否决了几个元素。其中一些已经被新的标签所取代,或者被 CSS 特性所取代,而另一些则不再需要。

  • <applet>:用<embed><object>代替。
  • <acronym>:用<abbr>代替。
  • <frame><frameset><noframes>:框架集在 HTML5 中已经完全被弃用。相反,可以考虑使用 iframes 或服务器端技术。
  • <strike>:使用<s>,除非标记是为了编辑,在这种情况下使用<del>
  • <basefont><big><blink><center><font><marquee><multicol><nobr><spacer><tt>:使用合适的元素或 CSS 来代替。对于<tt>元素,使用<kbd>(表示键盘输入)、<var>(表示变量)、<code>(表示计算机代码)或<samp>(表示样本输出)。在<big>元素的情况下,如果内容是标题,使用 header 标签,使用<strong>元素表示强调或重要性,使用<mark>元素突出显示引用。

此外,HTML5 已经废弃了现有元素的许多参数。同样,这些参数中的许多已经被 CSS 或其他特性所取代,而其他参数则是早期版本的 HTML 或 XHTML 的遗留物。

  • 属性backgrounddatasrcdataflddataformats已从所有适用的标签中被弃用。在背景的情况下,使用 CSS 将背景应用于元素。
  • <a> : charsetcoordsmethodsnamerev(使用rel的反义词)、shape(使用area的影像地图)、urn
  • <body> : alinkbgcolorlinkmarginbottommarginheightmarginleftmarginrightmargintopmarginwidthtextvlink
  • <br> : clear
  • <caption> : align
  • <col> : aligncharcharoffvalignwidth
  • <div> : align
  • <dl> : compact
  • <hr> : aligncolornoshadesizewidth
  • 所有表头标签:align
  • <iframe> : alignallowtransparencyframeborderhspacelongdescmarginheightmarginwidthscrollingvspace
  • <img> : alignborderdatasrchspacelongdesclowsrcnamevspace
  • <input> : alignhspaceismapusemapvspace
  • <legend> : align
  • <link> : charsetmethodsrevtargeturn
  • <menu> : compact
  • <object> : alignarchiveborderclassidcodecodebasecodetype(使用数据和类型属性以及<param>元素)、declarehspacestandbyvspace
  • <ol> : compact
  • <option> : name(用id代替)。
  • <p> : align
  • <param> : typevaluetype(使用名称和值属性)。
  • <pre> : width
  • <script> : eventforlanguage
  • <table> : alignbgcolorborderbordercolorcellpaddingcellspacingframerulessummarywidth
  • <tbody> : aligncharcharoffvalign
  • <td> : abbralignaxisbgcolorcharcharoffheightnowrapscopevalignwidth
  • <tfoot> : aligncharcharoffvalign
  • <th> : alignbgcolorcharcharoffheightnowrapvalignwidth
  • <thead> : aligncharcharoffvalign
  • <tr> : alignbgcolorcharcharoffvalign
  • <ul> : compacttype

尽管浏览器仍然可以在 HTML5 文档中呈现这些标签并识别这些属性,但是您不应该使用它们。任何验证器也应该抛出错误。

摘要

在这一章中,我已经介绍了 HTML5 规范的元素部分的重点:

  • 我讨论了 HTML 的发展如何受到语义的影响。
  • 我简要介绍了 HTML5 为节、分组和语义引入的新标签。这些标签进一步扩展了语言处理复杂文档和布局的能力。
  • 我探索了 HTML5 新的多媒体特性,并演示了<audio><video>标签的基本用法。
  • 然后,我向您展示了 HTML5 指定的新交互元素:对话框和渐进式披露。不幸的是,这些功能还没有得到很好的支持,但这应该会改变久而久之。
  • 我回顾了 HTML5 引入的表单更改,其中一些在移动环境中特别有用。
  • 最后,我查看了 HTML5 中不赞成使用的标签和属性。

在下一章,你将深入 HTML5 指定的 JavaScript APIs。*

三、HTML5 API

正如在第一章中提到的,HTML5 标准不同于以前的 HTML 标准,因为它不仅仅是标记语言的定义。由于该标准旨在成为创建 web 应用和网页的平台,因此它引入了许多旨在简化应用构建的新特性:浏览器与服务器通信的新方式、存储和检索数据的新方式、对常见用户交互(如拖放)的支持等等。

像新的audiovideo标签一样,这些新的网络应用功能在过去已经使用大量的 JavaScript 程序甚至浏览器插件实现了。现在,有了 HTML5,浏览器制造商直接在他们的浏览器中实现它们。

所有这些新特性都可以在 JavaScript 程序中使用,所以浏览器制造商提供了使用脚本访问它们的接口。这些接口通常采用 JavaScript 对象和方法的形式。HTML5 标准也定义了这些接口,因此 JavaScript 对象和方法在所有实现该标准的浏览器中以相同的方式出现和运行(假设它们完全正确地实现了该标准)。

这些接口被称为应用编程接口 。如果你曾经在浏览器中编写过脚本,你可能已经熟悉了最常见的 API 之一:文档对象模型(DOM )。DOM 是一个 API,用于访问当前在浏览器中呈现的文档。任何获取浏览器中元素引用的方法(如getElementById)都是 DOM API 的一部分,任何访问事件模型的方法(如addEventListener)也是如此。该浏览器还发布了用于访问内部浏览器功能(如浏览历史)的Navigator API。另一个常用的 API 是XMLHttpRequest构造函数,它是浏览器网络通信系统的一个接口,允许您与服务器进行异步通信。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意术语 API 有相当广泛的应用,从库和框架到网络服务(包括许多可以用浏览器异步访问的 web 服务),甚至是单个应用中对象之间的内部接口。

在这一章中,我将介绍 HTML5 规范中定义的新 API。我将采用一种实用的方法来研究 API,重点关注特性的支持程度以及可以用它来做什么,并提供大量的例子来作为您自己的 web 应用的起点。

服务器发送的事件

维持价

混合

Internet Explorer 不支持此 API。所有其他浏览器都完全支持。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#server-sent-events

W3C 草案:http://dev.w3.org/html5/eventsource/

假设您正在构建一个简单的股票行情应用。您有一个服务器资源,它发布了获取股票值所需的 API,所以很容易就可以开始了。但是如何更新股票价值呢?服务器如何让您的客户端应用知道股票的价值已经改变?

这可能是服务器发送事件的典型用例:服务器需要通知客户端发生了一些事情。因为 HTTP 作为一种标准只定义了无状态通信,因此只有客户机可以向服务器发起请求,如果客户机不首先请求,服务器就无法向客户机发送消息。服务器发送的事件是 HTML5 解决这个问题的方式之一,在从服务器到客户端的单向通信的特定情况下。

在过去,人们将简单的轮询计时器写入他们的脚本,这实质上是询问服务器“有什么新东西吗?”上一个定时器,如清单 3-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意本节中的例子需要从服务器上运行,而不是直接从文件系统中加载。

清单 3-1 。轮询脚本示例

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
  <script>

// This is a mock API that simply returns the same JSON structure
// every time the URL is requested. This JSON structure has a single
// property, isChanged, which is set to false.
var strUri =./example3-1.json’;

// This is how often we’ll query the mock API, in milliseconds.
var timerLength = 1000;

/**
 * Fetch an update from a web service at the specified URL. Initiates an
 * XMLHttpRequest to the service and attaches an event handler for the success
 * state.
 * @param {string} strUrl The URL of the target web service.
 */
function fetchUpdates(strUrl) {
  // Create and configure a new XMLHttpRequest object.
  var myXhr = new XMLHttpRequest();
  myXhr.open("GET", strUrl);
  // Register an event handler for the readyStateChange event published by
  // XMLHttpRequest.
  myXhr.onreadystatechange = function() {
    // If readyState is 4, request is complete.
    if (myXhr.readyState === 4) {
      handleUpdates(myXhr.responseText);
    }
  };
  // Everything is configured. Send the request.
  myXhr.send();
}

/**
 * Handle an update from the mock API. Parses the JSON returned by the API and
 * looks for changes.
 * @param {string} jsonString A JSON-formatted string.
 */
function handleUpdates(jsonString) {
  // Parse the JSON string.
  var jsonResponse = JSON.parse(jsonString);
  if (jsonResponse.isChanged) {
    // Handle changes here, probably by checking the structure to determine
    // what changed and then route the change to the approprate part of the app.
    console.log(’change reported.);
  } else {
    console.log(’no changes reported.);
  }
}

// Everything is all set up: we have a function for fetching an update and a
// function to handle the results of an update query. Now we just have to kick
// off everything. Using setInterval, we will call our fetchUpdates method every
// timerLength milliseconds. We can cancel the timer by calling the
// cancelInterval(pollTimer) method.
var pollTimer = setInterval(fetchUpdates.bind(this, strUri), timerLength);
  </script>
  </body>
</html>

这个例子在开始轮询过程之前做了大量的设置。首先定义模拟 API 的 URL,这只是您在文件系统上创建的一个文件。您还可以定义轮询模拟 API 的频率。然后创建一个获取更新的函数。这是您将使用计时器调用的方法,它向模拟服务发起XMLHttpRequest (简称 XHR)。XHR 将在与服务器通信时发布readyStateChange事件。通过查看 XHR 对象的readyState属性,您可以知道查询处于什么状态:仍在与服务器对话,还是已经结束对话,或者即使发生了错误。因此,向 XHR 对象添加一个事件处理程序,每次发生readyStateChange事件时都会调用它。在这种情况下,您使用内联函数来保持代码简单,尽管您可以在代码块之外定义它并通过名称引用它。事件处理程序检查readyState属性,如果它处于正确的状态,它就调用handleResponse函数。该函数接受从模拟 API 获取的 JSON 格式的字符串,并对其进行相应的处理。

这是一个非常简单的例子,但是它演示了使用定时器定期轮询 web 服务的基本原理。使用方法setIntervalcancelInterval很容易启动和停止定时器。如果您的应用需要多个计时器,您可以构建一个构造函数,用方便的方法来创建、启动、停止、暂停和释放它们。您可以围绕创建和管理计时器快速构建大量代码。

如果你仔细想想,简单的计时器并不是处理这个问题的好方法。如果出现暂时的网络问题,并且对fetchUpdates的一个调用花费了超过一秒钟的时间,该怎么办?在这种情况下,对fetchUpdates的另一个调用将在第一个调用返回之前执行,导致对服务器的两个活动和挂起的调用。根据网络条件,第二个呼叫可能会在第一个呼叫之前返回。这种情况被称为竞争条件,,因为两个未决调用实质上是在竞争看哪一个先完成。

幸运的是,这种竞争状况很容易修复:与其让计时器不顾外部限制发出请求,不如改变handleUpdates方法,这样它做的最后一件事就是调度对fetchUpdates的下一次调用。这样,你就不会有一个以上的调用发生,并且消除了竞争条件。脚本会如清单 3-2 中的所示改变(周围的 HTML 保持不变)。

清单 3-2 。从清单 3-1 的示例中删除竞争条件

// This is a mock API that simply returns the same JSON structure
// every time the URL is requested. This JSON structure has a single
// property, isChanged, which is set to false.
var strUri = ’./example3-1.json’;

// This is how often we’ll query the mock API, in milliseconds.
var timerLength = 1000;

// Reference to the currently active timer.
var pollTimeout;

/**
 * Fetch an update from a web service at the specified URL. Initiates an
 * XMLHttpRequest to the service and attaches an event handler for the success
 * state.
 * @param {string} strUrl The URL of the target web service.
 */
function fetchUpdates(strUrl) {
  // Create and configure a new XMLHttpRequest object.
  var myXhr = new XMLHttpRequest();
  myXhr.open("GET", strUrl);
  // Register an event handler for the readyStateChange event published by
  // XMLHttpRequest.
  myXhr.onreadystatechange = function() {
    // If readyState is 4, request is complete.
    if (myXhr.readyState === 4) {
      handleUpdates(myXhr.responseText);
    }
  };
  // Everything is configured. Send the request.
  myXhr.send();
}

/**
 * Handle an update from the mock API. Parses the JSON returned by the API and
 * looks for changes, and then initiates the next query to the mock service.
 * @param {string} jsonString A JSON-formatted string.
 */
function handleUpdates(jsonString) {
  // Parse the JSON string.
  var jsonResponse = JSON.parse(jsonString);
  if (jsonResponse.isChanged) {
    // Handle changes here, probably by checking the structure to determine
    // what changed and then route the change to the approprate part of the app.
    console.log(’change reported.’);
  } else {
    console.log(’no changes reported.’);
  }
  // Schedule the next polling call.
  pollTimeout = setTimeout(fetchUpdates.bind(this, strUri), timerLength);
}

// Initiate polling process. 
fetchUpdates(strUri);

与前一版本相比,该脚本的变化以粗体显示。这个版本的代码消除了竞争条件,因为在任何给定的时间只有一个对fetchUpdates的调用是活动的。但是,您现在可能会以不可预知的速度轮询服务器。

围绕这些问题进行编程也是可能的,但是处理好所有的边缘情况可能是困难的,并且您将会得到大量的代码,而这些代码仅仅是为了智能地处理对 web 服务的轮询。

理想情况下,这种通信应该是浏览器的一个特性,这就是新的服务器发送事件特性的作用。服务器发送事件让浏览器处理连接到服务器和轮询事件的所有细节,并让您留下基于计时器的轮询脚本和它们带来的所有问题。服务器发送的事件为您提供了一种打开到服务器的通道的方式,然后将事件侦听器附加到该通道,以处理服务器将发布的各种事件类型。浏览器会为您处理所有其他事情。

要使用服务器发送的事件,您不仅需要一个可以打开 web 服务通道并处理该通道上发生的事件的客户端;您还需要一个能够正确处理传入频道订阅并发布正确格式化的事件的服务器。

客户端设置

服务器发送的事件规范在全局上下文中定义了一个新的构造函数EventSource,您可以使用它来创建到服务器的新连接:

var serverConnection = new EventSource(targetUrl);

构造函数返回一个EventSource对象,它是一个连接的接口,浏览器将维护这个连接到由targetUrl指定的服务器资源。浏览器将为您处理所有的连接维护和轮询—您所要做的就是监听来自服务器的事件。

当服务器向连接发布事件时,服务器资源将发布从EventSource对象分派的事件。像任何 DOM 事件一样,您可以使用addEventListener方法将事件处理程序附加到EventSource对象。

默认情况下,EventSource接口将发布三种事件类型:

  • open:首次打开连接,网络通信初始化时发布。用于初始化连接。最多触发一次,如果浏览器无法建立到指定服务的连接,则根本不会触发。
  • message:服务器发送新消息时发布。
  • error:如果连接中出现错误(例如连接超时),则发布。

当一个事件被调度时,EventSource将调用为该事件类型注册的事件处理函数。这个函数将被调用,使用一个event对象作为参数,这个event对象将有一个data属性,包含从服务器发送的数据。清单 3-3 展示了如何创建一个新的EventSource对象并给它附加事件监听器。

清单 3-3 。存根 EventSource 事件处理程序和订阅

/**
 * Handles message events published by the EventSource.
 * @param {EventSourceEvent} event
 */
function handleMessage(event) {
  // Handle message.
  console.log(’A message was sent from the server: ’, event.data);
}

/**
 * Handles error events published by the EventSource.
 * @param {EventSourceEvent} event
 */
function handleError(event) {
  // Handle an error.
  console.error(’An error happened on the EventSource: ’, event.data);
}

/**
 * Handles an open event published by the EventSource.
 * @param {EventSourceEvent} event
 */
Function handleOpen(event) {
  // Handle the open event.
  console.log(’The connection is now open.’);
}

// Create a new connection to the server.
var serverConnection = new EventSource(targetUrl);

// Attach event handlers.
serverConnection.addEventListener(’message’, handleMessage);
serverConnection.addEventListener(’error’, handleError);
serverConnection.addEventListener(’open’, handleOpen);

现在,每当由strUrl指定的资源发布事件时,就会调用handleMessage事件处理程序。如果连接出现错误,浏览器将发布一个error事件,并调用handleError事件处理程序。请注意,您可以配置您的服务器来发布自定义事件类型,并且可以用完全相同的方式为它们添加事件处理程序(请参见下一节“从服务器发送事件”)。

要关闭到服务器的连接,调用EventSource对象上的close方法:

serverConnection.close();

这将通知浏览器停止轮询服务器并关闭连接。然后,您可以将 EventSource 对象设置为null以将其从内存中删除。一旦连接被关闭,就无法重新打开。

从服务器发送事件

为了让服务器发送的事件工作,服务器上需要有一个资源,它知道如何处理来自浏览器的传入轮询请求,以及如何根据需要正确发布事件。服务器资源可以用任何语言编写——Java、PHP、JavaScript、C++等等。资源必须用text/event-stream MIME 类型来响应轮询请求。响应由多行key: value对组成,由一个双换行结束。有效密钥如下:

  • data:指定一行发送给客户端的任意数据,客户端将接收它作为event对象的数据属性。
  • event:指定与此服务器发送的事件相关联的任意事件类型。这将导致一个同名的事件从活动的EventSource对象中被调度,从而允许从服务器中触发openmessageerror之外的任意事件。如果未指定事件类型,该事件将仅触发EventSource上的message事件。
  • id:指定与事件序列关联的任意 ID。在事件流上设置一个 ID 使浏览器能够跟踪最后触发的事件,如果连接断开,它将向服务器发送一个last-event-ID HTTP 头。
  • retry:指定在浏览器为下一个事件重新查询服务器之前的毫秒数。默认设置为3000(三秒)。这使得服务器资源能够抑制浏览器查询并防止自己被淹没。

例如,服务器发送的基本事件如下所示:

data: arbitrary line of text\n\n

这个事件将触发相关联的EventSource对象上的message事件,并调用message事件处理程序(假设已经注册了一个)。消息事件处理程序接收的event对象将有一个data属性,它将包含服务器发送的文本(在前面的例子中,它将是“任意文本行”)。

您也可以发送多行事件,只需用一个双换行来终止事件:

data: arbitrary line of text\n
data: another arbitrary line of text\n\n

在这种情况下,event.data属性将被设置为“任意文本行\ n 另一个任意文本行”。事件数据可以是任何文本:HTML、CSS、XML、JSON 等等。

多个事件类型也可以包含在一个服务器发送的事件中。回到股票报价器的原始示例,这里有一个显示两只不同股票的更新的事件:

event: update\n
data: {\n
data: "ticker":"GOOG",\n
data: "newval":"559.89",\n
data: "change":"+0.05"\n
data: }\n
event: update\n
data: {\n
data: "ticker":"GOOGL"\n
data: "newval":"571.65"\n
data: "change":"+1.09"\n
data: }\n\n

这个服务器发送的事件将触发对象EventSource上的两个update事件。每次调用update事件处理程序时,都会调用一个包含该事件数据的event对象。第一个事件的数据将是以下 JSON 格式的文本:

{
  "ticker":"GOOG",
  "newval":"559.89",
  "change":"+0.05"
}

第二个事件的数据是以下 JSON 格式的文本:

{
  "ticker":"GOOGL",
  "newval":"571.65",
  "change":"+1.09"
}

原点限制

服务器发送的事件受制于相同的源策略,因此来自一个源的页面不能订阅来自另一个源的事件流。特别是,事件流通常发布在与标准网页不同的 TCP 端口上,因此,发布在标准端口(如端口 80)上的网页不可能订阅发布在不同端口上的事件流,即使它来自同一服务器。只有从与事件流相同的源提供服务的客户端才能访问该事件流。

如果您想使用服务器发送的事件,您将需要一个足够灵活的 web 服务器来服务基于 HTML 的客户端(及其所有依赖的资源,如 CSS、JavaScript、图像等)。)并发布事件流。这使得 PHP、JSP 或 ColdFusion 等服务器集成脚本语言成为构建依赖于服务器发送事件的应用服务器的主要候选语言,因为您可以用集成脚本语言编写事件流,并使用相同的 web 服务器为客户端提供服务。还可以很容易地配置大多数 web 服务器,将对特殊 URL 的请求路由到不同的资源,使得从同一服务器发布常规 web 内容和事件流成为可能。这种实现的细节超出了本书的范围,但是这是对服务器发送事件的一个重要限制。

在下面的例子中,您将选择一个更简单的解决方案:构建一个服务器,它可以在充当事件流的同时为客户端 HTML 文件提供服务。由于 HTML 客户机文件和事件流都来自同一个来源,所以订阅不会有问题。

安全性

就像任何处理网络通信的技术一样,在使用服务器发送的事件构建应用时,注意安全性是一个好主意。不要在未加密的连接上发送敏感信息(如密码),因为服务器事件是以纯文本形式发送的。如果你需要发送敏感信息,你至少应该使用 HTTPS。

如前所述,服务器发送的事件受到同源策略的限制,因此脚本不能从不同于自己的网络资源订阅事件流。此外,EventSource 事件处理程序接收的事件对象将有一个 origin 属性,您可以检查该属性以验证服务器事件是否来自您期望的来源,如清单 3-4 所示。

清单 3-4 。从服务器发送的事件中检查事件来源

// The EventSource object.
var serverConnection;

/**
 * Handle an event published on an EventSource object.
 * @param {EventSourceEvent} event
 */
function messageHandler(event) {
  if (event.origin !== ’https://www.myexample.com’) {
    // Something bad has happened, stop listening for events and surface a warning to the user.
    serverConnection.close();
    alert(’Warning: Server event received from wrong network resource.’);
    return;
  }
  // Handle event here.
}

// Initiate subscription to event stream and register event handler.
serverConnection = new EventSource(targetUrl);
serverConnection.addEventListener(’message’, messageHandler);

在这个代码片段中,您将检查浏览器所报告的事件的来源。然而,这不是一个万无一失的检查,因为它可能会被欺骗,但是它是您可以添加到您的应用中的又一层安全性。

一个示例应用

要构建一个功能性的示例应用,您需要一个服务器资源,它可以以预期的格式发送事件,还可以为订阅事件流的客户端提供服务。如上所述,您可以使用任何语言来构建这个服务器资源,但是为了与本书中的其他示例保持一致,请使用 JavaScript。您可能习惯于在浏览器中使用 JavaScript。您也可以在服务器上使用它,就像任何其他脚本引擎一样。作为独立脚本引擎的 JavaScript 最流行的实现是 Node.js 框架,它已经被移植到多个操作系统。Node.js 框架提供了一个快速的 JavaScript 解释器和一个 API 框架,用于访问文件系统、网络堆栈和服务器上的其他资源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示如果你从未使用过 Node.js,你可以在http://nodejs.org了解更多。

要运行这个示例,您需要一个安装了 Node.js 的服务器。您将构建一个简单的脚本,既充当事件流,又服务于事件客户端。正如你在清单 3-5 中看到的,用 Node.js 构建一个简单的 HTTP 服务器非常容易。

清单 3-5 。用 JavaScript 编写的简单事件流服务器

// Include the modules needed to build the server.
var http = require(’http’);
var sys = require(’sys’);
var fs = require(’fs’);

// Use the http.createServer method to create a server listening on port 8030.
// The server will call the handleRequest method each time a request is
// received.
http.createServer(handleRequest).listen(8030);

/**
 * Handle an incoming request from the server.
 * @param {Object} request The request headers.
 * @param {Object} resource A reference to the server resource that received
 * the request.
 */
function handleRequest(request, resource) {
  // Incoming requests to our server will be to one of two URLs.
  // If the request is for /example3-5-events we should send our SSE.
  // If the request is for /example3-5.html, we should serve the example client.
  if (request.url == ’/example3-5-events’) {
    // Initialize an event stream.
    sendSSE(request, resource);
  } else if (request.url == ’/example3-6.html’){
    // Send the client.
    resource.writeHead(200, {’Content-Type’: ’text/html’});
    resource.write(fs.readFileSync(’example3-6.html’));
    resource.end();
  }
}

/**
 * Initializes an event stream and starts sending an event every 5 seconds.
 * @param {Object} request
 * @param {Object} resource
 */
function sendSSE(request, resource) {
  // Initialize the event stream.
  resource.writeHead(200, {
    ’Content-Type’: ’text/event-stream’,
    ’Cache-Control’: ’no-cache’,
    ’Connection’: ’keep-alive’
  });

  // Send an event every 5 seconds.
  setInterval(function() {
    // Randomly generate either 0 or 1.
    var randNumber = Math.floor(Math.random() * 2);
    // If the random number is 1, set isChanged to true. Otherwise, set it to
    // false.
    var isChanged = (randNumber === 1) ? true : false;
    resource.write(’data: ’ + ’{"isChanged":’ + isChanged + ’}\n\n’);
  }, 5000);
}

如果你请求example3-6.html,它将服务于 HTML 客户端(你将在清单 3-6 中定义),如果你请求example3-5-events,它将启动一个事件流,每五秒将一个事件推送到客户端。该事件将是一个简单的 JSON 格式的字符串,带有一个随机设置为truefalseisChanged属性。要运行此服务器,请使用以下命令:

node example3-5server.js

这个服务器的 HTML 客户端只需要将EventSource初始化到正确的 URL,如清单 3-6 中的所示。

清单 3-6 。服务器发送的事件客户端

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
#changeme {
  width: 300px;
  height: 300px;
  border: 1px solid black;
  overflow: auto;
}
    </style>
  </head>
  <body>
    <h1>Server-sent Events Demonstration</h1>
    <div id="changeme"></div>
  <script>
// The URL for our event stream. Note that we are not specifying a domain or
// port, so they will default to the same ones used by the host script.
var strUri =/example3-5-events’;
// Get a reference to the DOM element we want to update.
var changeMe = document.getElementById(’changeme’);

// Create a new server-side event connection and register an event handler for
// the ’message’ event.
var serverConnection = new EventSource(strUri);
serverConnection.addEventListener(’message’, handleSSE, false);

/**
 * Handles a server-sent event by parsing the JSON in the data and handling
 * any changes.
 * @param {EventSourceEvent} event The event object from the event source.
 */
function handleSSE(event) {
  // Parse the JSON string.
  var jsonResponse = JSON.parse(event.data);
  // Create a new element to append to the DOM.
  var newEl = document.createElement(’div’);
  if (jsonResponse.isChanged) {
    newEl.innerHTML = ’Change reported.;
  } else {
    newEl.innerHTML = ’No changes reported.;
  }
  // Append the new element to the DOM.
  changeMe.appendChild(newEl); 
}
  </script>
  </body>
</html>

这个客户端为事件流的 URL 启动一个新的EventSource,然后为它附加一个消息事件处理程序。每次服务器发布事件时,都会调用消息事件处理程序,解析事件数据,并将结果附加到 DOM 中。这个客户端比您之前的轮询示例简单得多,因为现在所有的细节都由浏览器处理。不需要初始化一个XMLHttpRequest对象,不需要管理你自己的定时器——你所要做的就是初始化一个EventSource对象并注册事件处理程序。

服务器发送的事件只允许从服务器到浏览器的单向通信。关于全双工通信,请参见 WebSocket 章节。

WebSockets

维持价

好的

所有现代浏览器都支持 WebSockets。

WHATWG 生活水平:https://html.spec.whatwg.org/multipage/comms.html#network

W3C 草案:http://www.w3.org/TR/websockets/

WebSockets 通过在客户机和服务器之间提供全双工通信建立在服务器发送的事件上:不仅服务器可以向客户机发送任意信息,客户机也可以将任意信息传输回服务器。此外,WebSockets 不受同源策略的约束,这使得来自一个源的脚本无法与来自不同源的页面进行交互。

浏览器中的 WebSocket JavaScript API 在全局上下文中表现为一个新的 WebSocket 构造函数,它返回一个 WebSocket 对象:

var mySocket = new WebSocket(url, protocols);

url参数指定 WebSocket 兼容服务的有效 URL。WebSockets 有自己的通信协议,不同于我们一直看到的超文本传输协议(HTTP )。WebSocket 协议由ws://(标准 WebSocket 连接)或wss://(安全 WebSocket 连接)指定。在构建 WebSocket 时,任何指定不同协议(如httphttps)的尝试都会导致错误。

可选的protocols参数可以是单个协议字符串,也可以是指定由服务器实现的一个或多个子协议的协议字符串数组。(本文中的协议是一组关于数据如何在客户端和服务器之间传输的规则。)该参数不改变浏览器用来创建和维护与服务器的连接的整体网络协议;相反,它允许您为通过该连接发送和接收信息指定可接受的数据格式。这使得单个服务器能够实现向客户端传输数据和从客户端接收数据的多种方式。例如,您可以实现一个服务器,该服务器同时实现服务器发送的事件协议(使用为该 API 指定的键/值对)和以文本形式发送 JSON 格式的字符串的协议。然后,您的客户端可以指定它期望的协议。如果未指定协议,服务器将不得不选择默认协议。如果指定的协议在服务器上不可用,服务器将拒绝连接。

连接到服务器:在 WebSocket 握手中

web 浏览器使用握手过程创建与服务的双向连接。当您使用构造函数创建一个新的 WebSocket 时,浏览器将立即向 URL 中指定的主机发送一个简单的GET查询。该查询将包含指定浏览器试图创建 WebSocket 连接(与发出简单的 HTTP 请求相反)所需的所有标头。例如,假设你在example.com访问网站,他们正在宣传他们新的基于 WebSocket 的聊天服务。(聊天服务是 WebSockets 的一个常见用例,因为它需要发送和接收消息。)单击链接登录聊天服务,应用启动。

在后台,浏览器将尝试连接到聊天服务。假设服务也托管在位于 URL ws://www.example.com/chat的同一个域example.com上,JavaScript 套接字创建可能看起来像这样:

var mySocket = new WebSocket(’ws://www.example.com/chat’, [’chat’, ’json’]);

得到的请求和它的头看起来像这样:

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: upgrade
Origin: http://www.example.com
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat, json
Sec-WebSocket-Version: 13

第一行是一个简单的GET请求。请求的“chat”部分是可选的,但是允许单个服务器发布许多 WebSocket 服务。

头部包含与服务器建立 WebSocket 连接所需的信息。具体来说,Sec-WebSocket-Key头包含一个惟一的标识符,客户机希望服务器在它的响应中以特定的方式使用这个标识符。Sec-WebSocket-Protocol头包含构造函数中指定的子协议。在这种情况下,客户端指定它知道’chat’’json’协议。

服务器将获取头中的信息,并制定一个响应,可能如下所示:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

Sec-WebSocket-Accept头的值取决于客户端发送的Sec-WebSocket-Key头的值。客户机知道期望从服务器得到一个特定的值,如果指定了一个不同的值(或者根本没有指定),客户机就知道服务器不能处理 WebSocket 连接。Sec-WebSocket-Protocol报头表示服务选择了’chat’协议进行通信。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意web socket 协议在 RFC 6455“web socket 协议”中有定义,你可以在https://tools.ietf.org/html/rfc6455阅读。该文档指定了整个协议,包括处理Sec-WebSocket-Key报头以创建Sec-WebSocket-Accept值的细节。

握手完成后,客户端和服务器将使用特殊的数据帧协议相互通信。该协议允许客户端和服务器轻松地相互发送任意信息。

从服务器接收信息

在客户端,与服务器的交互是事件驱动的:当与服务器发生通信时,特定的事件被发布在相关联的 WebSocket 连接对象上。

  • error:当 WebSocket 无法连接到服务器,或者失去连接时,在 connection 对象上发布一个error事件。
  • open:当 WebSocket 第一次成功连接指定的服务时,在 connection 对象上发布open事件。此事件表示套接字已准备好发送和接收数据。
  • close:当 WebSocket 关闭时,无论是由于一个错误还是因为客户端故意关闭了连接,都会在连接对象上发布一个close事件。
  • message:当服务器通过连接发送信息时,浏览器会在连接对象上发布消息事件。该事件将包含从服务器传输的数据。

清单 3-7 使用假想的聊天服务展示了这些事件的一些存根事件处理程序。

清单 3-7 。Web 套接字的存根事件处理程序

// Create a new WebSocket connection to the chat service.
var chatUrl = ’ws://www.fgjkjk4994sdjk.com/chat’;
var validProtocols = [’chat’, ’json’];
var chatSocket = new WebSocket(chatUrl, validProtocols);

/**
 * Handles an error event on the chat socket object.
 */
function handleError() {
  console.log(’An error occurred on the chat connection.’);
}

/**
 * Handles a close event on the chat socket object.
 * @param {CloseEvent} event The close event object.
 */
function handleClose(event) {
  console.log(’The chat connection was closed because ’, event.reason);
}

/**
 * Handles an open event on the chat socket object.
 * @param {OpenEvent} event The open event object.
 */
function handleOpen(event) {
  console.log(’The chat connection is open.’);
}

/**
 * Handles a message event on the chat socket object.
 * @param {MessageEvent} event The message event object.
 */
function handleMessage(event) {
  console.log(’A message event has been sent.’);

  // The event object contains the data that was transmitted from the server.
  // That data is encoded either using the chat protocol or the json protocol,
  // so we need to determine which protocol is being used.
  if (chatSocket.protocol === validProtocols[0]) {
    console.log(’The chat protocol is active.’);
    console.log(’The data the server transmitted is: ’, event.data);
    // etc...
  } else {
    console.log(’The json protocol is active.’);
    console.log(’The data the server transmitted is: ’, event.data);
    // etc...
  }

// Register the event handlers on the chat socket.
chatSocket.addEventListener(’error’, handleError);
chatSocket.addEventListener(’close’, handleClose);
chatSocket.addEventListener(’open’, handleOpen);
chatSocket.addEventListener(’message’, handleMessage);

在本例中,您创建了一组简单的事件处理程序,每种事件类型一个,然后在 connection 对象上注册这些处理程序。如果你愿意,你可以运行这个例子。域fgjkjk4994sdjk.com不存在,所以首先浏览器会在连接上发布一个error事件,然后是一个close事件。在控制台中,你会看到类似于图 3-1 的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-1 。在 Chrome 中运行清单 3-7 的结果

在关闭的事件处理程序handleClose中,检查event对象的reason属性,查看是否指定了关闭的原因。该属性可能存在,也可能不存在,具体取决于发生的错误和为连接指定的子协议。

handleMessage事件处理程序非常简单,但是演示了如何检查活动的子协议,以及如何访问服务器传输的数据。我们习惯于通过 HTTP 进行简单的基于文本的通信(就像服务器发送的事件一样),但是 WebSockets 也可以传输二进制数据。您可以通过 WebSocket 传输任意二进制数据;例如,您可以发送和接收图像。

在 JavaScript 中,使用二进制大型对象 (也称为blob)或数组缓冲区来表示二进制数据。这两种都是 JavaScript 中的有效数据类型:Blob表示 blobs,ArrayBuffer表示数组缓冲区。这两种类型的区别在于数据的使用方式。如果你正在处理一个永远不会改变的原始数据块(比如一幅图像),那么最好用一个Blob来表示。如果您需要处理数据(查看数据的一部分,或者甚至修改它),最好使用ArrayBuffer来表示。这两者都是 JavaScript 中的数据类型,所以很容易检查从服务器传输的信息是否是这种格式。下面是对handleMessage事件处理程序的更新,演示了对BlobsArrayBuffers 的检查:

/**
 * Handles a message event on the chat socket object.
 * @param {MessageEvent} event The message event object.
 */
function handleMessage(event) {
  console.log(’A message event has been sent.’);

  // The event object contains the data that was transmitted from the server.
  // That data is encoded either using the chat protocol or the json protocol,
  // so we need to determine which protocol is being used.
  if (chatSocket.protocol === validProtocols[0]) {
    console.log(’The chat protocol is active.’);

    // Check the data type of the incoming data.
    if (event.data instanceof Blob) {
      console.log(’The data is a Blob.’);
    }
    if (event.data instanceof ArrayBuffer) {
      console.log(’The data is an ArrayBuffer.’);
    }

    console.log(’The data the server transmitted is: ’, event.data);
    // etc...
  } else {
    console.log(’The json protocol is active.’);
    console.log(’The data the server transmitted is: ’, event.data);
    // etc...
  }

}

这里,您在chat子协议部分添加了检查,以确定数据是 Blob 还是ArrayBuffer。您也可以在json子协议部分进行类似的检查;可以用 JSON 格式编码BlobsArrayBuffers(通常使用 Base64 编码)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意关于处理斑点的更多细节,见https://developer.mozilla.org/en-US/docs/Web/API/Blob,关于使用数组缓冲区的更多细节,见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

向服务器发送信息

您也可以使用 WebSocket 将信息传输到服务器。每个 WebSocket 连接对象都有一个send方法,您可以使用它将信息立即传输到服务器。您应该确保只在触发了open事件之后发送信息,否则信息可能会丢失。因此,您经常会在 WebSocket 应用中看到一种模式,其中open事件处理程序启动应用的初始化,以保证在连接可用之前不会发生任何事情。

send方法只有一个参数:要发送的数据,如下所示:

var mySocket = new WebSocket(url);
mySocket.send(’hello world!’);

有效的数据类型有StringsBlobsArrayBuffers

关闭连接

connection 对象有一个close方法,可以用来在您完成连接时关闭与服务器的连接。调用这个方法将立即导致客户端向服务器发送一个close请求,这又将关闭连接。然后,客户端将在连接对象上调度一个close事件。无法重新打开已关闭的 WebSocket 连接对象。

一个示例 WebSocket 应用

与服务器发送的事件一样,您将需要一个能够处理 WebSocket 连接的服务器,以便构建一个正常运行的示例。从头开始构建这样的服务器是一项相当困难的任务,因为服务器必须知道如何将 HTTP 连接升级到 WebSocket 连接,以及如何根据 WebSocket 协议发送和接收数据。幸运的是,您可能不需要从头开始构建一个。有许多开源项目致力于创建可以在项目中使用的 WebSocket 服务器。对于示例 WebSocket 应用,您将使用 Node.js 构建一个简单的 WebSocket 服务器,这是一个用于服务器的 JavaScript 框架。Node.js 提供了一个快速的 JavaScript 解释器以及用于访问文件系统、网络堆栈和其他服务器技术的库。

您将使用 WebSocket-Node,而不是从头开始构建服务器,这是 Node.js 中 WebSocket 协议的开源实现。要安装该模块,请使用节点程序包管理器 npm:

npm install websocket

这将为您安装模块。如果没有,请查看项目主页上的安装说明。

一旦安装了 WebSocket-Node,您就可以使用它来为您的示例构建一个服务器。最简单的 WebSocket 例子是一个服务器,它简单地回显客户机发送的任何内容。清单 3-8 显示了使用 WebSocket-Node 库构建一个服务器是多么简单。

清单 3-8 。一个简单的 WebSocket 服务器

// Include the modules needed to build the server.
var WebSocketServer = require(’websocket’).server;
var http = require(’http’);
var currentConnection;

// Define the subprotocol name for the WebSocket connection.
var subProtocol = ’echo’;

/**
 * Handles a request event on the WebSocket server.
 * @param {Object} request
 */
function handleRequest(request) {
  currentConnection = request.accept(subProtocol, request.origin);
  currentConnection.on(’message’, handleMessage);
}

/**
 * Handles a message event on a socket connection.
 * @param {Object} message The message event object.
 */
function handleMessage(message) {
  // Echo back whatever was received.
  if (message.type === ’utf8’) {
    currentConnection.sendUTF(message.utf8Data);
  } else if (message.type === ’binary’) {
    currentConnection.sendBytes(message.binaryData);
  }
}

// Create a simple server that always returns 404 (not found) to any request.
// (We’re only going to use it to upgrade to the WebSocket protocol.)
var simpleServer = http.createServer(function(request, response) {
    response.writeHead(404);
    response.end();
});
simpleServer.listen(8080);

// Create a WebSocket server based on the simple server.
var socketServer = new WebSocketServer({
    httpServer: simpleServer,
    autoAcceptConnections: false
});

// Register the request event handler.
socketServer.on(’request’, handleRequest);

这个例子基于一个简单的 HTTP 服务器创建了一个 WebSocket 服务器。每当 socket 服务器收到连接请求时,就会在该对象上调度一个request事件。在handleRequest事件处理程序中,通过接受请求建立一个 WebSocket 连接,然后为message事件注册一个事件处理程序。每当客户端向服务器发送消息时,就会在连接对象上调度一个message事件。您的handleMessage事件处理程序只是简单地回显接收到的任何数据。

将该脚本保存在名为example3-8-server.js的文件中。要运行它,输入node example3-8-server.js

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示如果不想自己搭建服务器,有一个简单的 echo 服务器运行在ws://echo.websocket.org。使用它作为 URL,不要指定协议。请注意,如果您以这种方式运行该示例,它将证明 WebSocket 没有绑定到单一来源策略,因为原始页面将从本地服务器提供服务,但是 web socket 服务器在完全不同的域上。

接下来,您需要构建一个可以利用服务器的客户机。您的客户端应该尝试发送各种类型的数据,并显示从服务器返回的任何内容。清单 3-9 显示了一个连接到你的服务器并运行一系列测试的客户端。

清单 3-9 。一个 WebSocket 演示类

<!DOCTYPE HTML>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <h1>WebSockets Demonstration</h1>
    <div id="display"></div>
    <script>
function WebSocketDemo() {

  /**
   * The URL for the WebSocket server. There is a simple echo server running at
   * ws://echo.websocket.org/ if you don’t have a local server running.
   * @private {string}
   */
  this.demoUrl_ = ’ws://localhost:8080/;

  /**
   * The protocol used by the server. If using the server at echo.websocket.org
   * set this to null, as it does not have a protocol.
   * @private {string|Array<string>}
   */
  this.subProtocol_ = ’echo’;

  /**
   * @private {WebSocket}
   */
  this.demoSocket_ = null;

  /**
   * A reference to the DOM element to use for displaying messages.
   * @private {HtmlElement}
   */
  this.display = document.getElementById(’display’);

  /**
   * Displays a message on the page.
   * @param {string} messageText A simple string of text.
   * @param {*=} opt_messageData An optional set of data to append to the text
   * string.
   * @private
   */
  this.displayMessage_ = function(messageText, opt_messageData) {
    var messageData = opt_messageData ? opt_messageData : ’’;
    var newParagraph = document.createElement(’p’);
    newParagraph.innerText = messageText + messageData;
    this.display.appendChild(newParagraph);
  };

  /**
   * Handles an error event on the demo socket object.
   * @private
   */
  this.handleError_ = function() {
    this.displayMessage_(’An error occurred on the demo connection.);
  };

  /**
   * Handles a close event on the demo socket object.
   * @private
   */
  this.handleClose_ = function() {
    this.displayMessage_(’The demo connection was closed.);
  };

  /**
   * Handles an open event on the demo socket object.
   * @private
   */
  this.handleOpen_ = function() {
    this.displayMessage_(’The demo connection is open.);

    // Now that the socket is open, we can send data.
    this.sendDataAndClose_();
  };

  /**
   * Handles a message event on the demo socket object.
   * @param {MessageEvent} event The message event object.
   * @private
   */
  this.handleMessage_ = function(event) {
    this.displayMessage_(A message event has been received from the server.);
      // Check the data type of the incoming data.
      if (event.data instanceof Blob) {
        this.displayMessage_(’The data is a blob.);
      }
      if (event.data instanceof ArrayBuffer) {
        this.displayMessage_(’The data is an ArrayBuffer.);
      }
      this.displayMessage_(’The data the server transmitted is:, event.data);
  };

  /**
   * Initializes the demo by creating a new connection and registering event
   * handlers.
   * @private
   */
  this.initDemo_ = function() {
    // Open the socket.
    this.demoSocket_ = new WebSocket(this.demoUrl_, this.subProtocol_);

    // Register the event handlers on the demo socket.
    this.demoSocket_.addEventListener(’error’, this.handleError_.bind(this));
    this.demoSocket_.addEventListener(’open’, this.handleOpen_.bind(this));
    this.demoSocket_.addEventListener(’message’,
 this.handleMessage_.bind(this));
    this.demoSocket_.addEventListener(’close’, this.handleClose_.bind(this));
  };

  /**
   * Sends data to the server, then closes the socket.
   * @private
   */
  this.sendDataAndClose_ = function() {
    // Send a text string.
    this.demoSocket_.send(’Hello world!);

    // Send a JSON-formatted string.
    var testObject = {
      message: ’hello world’,
      active: true
    };
    var testObjectString = JSON.stringify(testObject);
    this.demoSocket_.send(testObjectString);

    // Send a Blob.
    var testBlob = new Blob([’some data’]);
    this.demoSocket_.send(testBlob);

    // Done! Demo over. Close the socket after waiting for a few seconds for
    // all of the messages to be sent and received. You might need to adjust
    // this depending on the speed of your connection.
    setTimeout(function() {
      this.demoSocket_.close();
    }.bind(this), 5000);
  };

  /**
   * Runs the demonstration.
   */
  this.run = function() {
    this.initDemo_();
  };
}

// Create the demo and run it.
var myDemo = new WebSocketDemo();
myDemo.run();

    </script>
  </body>
</html>

在本例中,您已经将演示封装在一个类构造函数中。尽管您只运行了一次演示,但这是在构建这样的复杂连接时可以遵循的一个好模式,因为它有助于封装它们的功能。这也意味着,如果您愿意,可以很容易地一次实例化多个连接。

以前在全局范围内的所有函数或变量都被移到了这个类中。您还添加了一个新方法sendDataAndClose_ ,它演示了发送各种类型的数据,然后在五秒钟的延迟后关闭连接。open事件处理程序调用sendDataAndClose_,所以除非连接就绪,否则不会发送任何数据。服务器发送的任何内容都将显示在页面上。

运行这个例子,它将产生一个类似于图 3-2 中截图的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2 。运行的结果清单 3-9

您还可以通过为this.subProtocol_指定无效的子协议值,在 WebSocket 连接中引发错误:

/**
 * The protocol used by the server. If using the server at echo.websocket.org
 * set this to null, as it does not have a protocol.
 * @private {string|Array<string>}
 */
this.subProtocol_ = ’invalid protocol’;

WebSocket 服务器将不执行升级,套接字连接将失败。

跨文档信息/网络信息

维持价

好的

所有现代浏览器都支持这些功能。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#crossDocumentMessages

W3C 草案:http://www.w3.org/TR/webmessaging/

当 web 浏览器制造商开始采用 JavaScript 时,人们很快意识到安全性将是一个重要的问题。早期,Netscape 引入了同源策略,它规定一个脚本只能访问与自己同源的 DOM 内容。如果没有这个策略,来自任何域的恶意脚本都可以在您的浏览器上运行,然后读取并修改浏览器可以访问的所有数据:呈现的页面、历史记录、cookies,甚至保存的密码。

同源策略没有明确的标准,但它主要基于 RFC 6454 “网络起源概念”(你可以在http://tools.ietf.org/html/rfc6454阅读这个 RFC。)粗略地说,如果两个资源的协议(HTTP,HTTPS)、主机(例如,www.example.com)和端口(默认为端口 80)都匹配,则这两个资源具有相同的来源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意 Internet Explorer 在其来源确定中不包括端口。相反,它使用资源所属的安全区域。

同源策略是 web 应用安全性的基石,它被浏览器严格执行。不幸的是,这给构建利用不同域甚至子域上的多种资源的 web 应用带来了困难(例如,www.example.com将具有与services.example.com不同的来源,尽管它们都具有相同的根域example.com)。因此,web 开发人员创造了许多不同的变通办法,有些比其他的更为粗糙。

Web 消息传递,也称为跨文档消息传递,是 HTML5 提供在同源策略内工作的安全方法的方式之一,同时允许来自不同源的资源之间的安全通信。具体来说,该特性允许一个框架中的脚本使用可以随意触发的事件与另一个框架中的脚本进行通信。

规范在浏览器的window对象上创建了新方法postMessage。您使用这个方法向目标框架发送一条消息,这又导致在那个window中触发一个message事件。目标框架中的事件处理程序可以捕获事件并接收消息。

postMessage方法有两个参数:

  • message:您想要传输到目标框架的信息。
  • origin:您期望目标框架中资源具有的原点。如果目标框架中的资源没有指定的原点,则不会调度该事件。

目标框架在收到消息时会调度一个message事件。得到的event对象将有两个重要的属性:

  • Event.data:该属性将包含从另一个窗口发送的消息。
  • Event.source:该属性包含发送窗口的原点。您应该始终仔细检查消息来源的来源,以防止意外捕获和处理来自意外(可能是恶意)来源的事件。

要创建一个例子,你需要两个页面,你应该称之为主页面(清单 3-10 )和目标页面(清单 3-11 )。主页将包含一个加载目标页面的 iframe。

清单 3-10 。跨域消息传递,主页面

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <h1>Cross-Domain Messaging</h1>
    <iframe src="example3-8.html" id="targetFrame"></iframe>
    <p><button id="clickme">Click to send a message to the iframe.</button></p>
    <script>
// Message to send to the target window.
var strMessage = "This is a message sent from the main window.";

// Reference to the button.
var clickme = document.getElementById("clickme");

// Reference to the target frame.
var targetFrame = document.getElementById("targetFrame");

// Add a click event handler to the button.
clickme.addEventListener("click", function() {
  // Send a message to the target frame.
  targetFrame.contentWindow.postMessage(strMessage, "*");
});

/**
 * Handle a cross domain message.
 * @param {Event} event The event object from the cross domain message.
 */
function handleMessage(event) {
  // Create a message and show it to the user using an alert.
  var strAlert = "Message event in the main window!\nThe message was:\n";
  strAlert += event.data;
  alert(strAlert);
}

// Register the handleMessage event handler on the window.
window.addEventListener("message", handleMessage, false);
    </script>
  </body>
</html>

清单 3-11 。跨域消息传递,目标页面

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <h1>Target iframe</h1>
    <script>
/**
 * Handles message events dispatched to this window.
 * @param {Event} event The event object from the cross domain message.
 */
function handleMessage(event) {
  // Create a message and show it to the user using an alert.
  var strAlert = "Message event in target iframe!\nThe message was:\n";
  strAlert += event.data;
  alert(strAlert);

  // Post a message back to the parent frame.
  window.top.postMessage("This is a message from the target iframe.", "*");
}

// Register the message event handler on the window.
window.addEventListener("message", handleMessage, false);
    </script>
  </body>
</html>

若要运行此示例,请将两个页面保存在同一目录中。将目标页面保存在名称“example3-8.html”下。当您将主页加载到浏览器中时,它会将目标页面加载到 iframe 中。通过单击文本“单击向 iframe 发送消息”来运行示例

加载时,两个文档都将message事件处理程序绑定到它们的window对象。当您单击按钮时,父文档使用postMessage向 iframe 发送一条消息。这触发了 iframe 中的一个message事件,该事件由handleMessage事件处理器处理。这将向事件数据发出警报,然后向父文档发回一条消息。这又触发了父窗口中的message事件,该事件调用那里的handleMessage事件处理程序,并导致第二个警告发生。

在这个例子中,您没有利用这个特性的主要目的,即从不同来源的资源发送消息。如果你有不止一个来源,我鼓励你尝试这个例子。将页面上传到不同的源(确保相应地改变 iframe 的 URL ),看看结果是否如预期的那样工作。

注意,在这个例子中,您没有在对postMessage的调用中指定目标原点,也没有在事件处理程序中检查原点。这本身就不安全,我强烈建议不要在产品代码中这样做。您在这里这样做只是因为这是一个示例,具体的域信息将根据您提供文件的方式而有所不同。我鼓励您修改这些脚本,以便它们正确地指定目标源,并根据您的特定环境检查源源。此外,尝试将它们设置为不同的值,以引起原点冲突,这样您就可以自己看到结果。

网络存储

维持价

优秀

所有现代浏览器都支持这些特性,并且在最近的三个版本中都有。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/webstorage.html

W3C 草案:http://www.w3.org/TR/webstorage/

我已经提到过超文本传输协议是无状态的。这在一定程度上意味着服务器独立于其他请求处理来自客户端的每个请求。因此,没有内置的机制来跨网页加载或重新加载维护数据。例如,如果应用的第一页是一个登录表单,并且用户成功登录,那么当用户在站点的其余部分导航时,就没有机制来维护该会话。或者,如果您正在构建一个购物车应用,就没有办法将用户的选择从一个页面传递到另一个页面。

当然,这导致了糟糕的用户交互,所以在 1995 年——在网络历史的早期——网景公司的雇员 Lou Montulli 创建了一个规范,允许小块数据在浏览器和服务器之间使用特殊的 HTTP 请求进行通信。这些片段将存储在客户机中,但是服务器可以根据需要请求它们。Montulli 将这些小块数据称为“神奇的 Cookie”,这就是术语 HTTP Cookie 的由来。Cookies 支持 web 上的有状态通信,并迅速成为 web 应用的基石。然而,HTTP cookie 有点笨重。它们的大小非常有限(在大多数浏览器中为 4KB ),并且很难用 JavaScript 直接管理。

HTML5 引入了 Web 存储的概念,作为 HTTP Cookies 的替代。Web 存储允许在浏览器中存储更多的数据(在除 Internet Explorer 之外的所有浏览器中,每个源最多 5MB,Internet Explorer 允许每个源 10MB)。Web 存储也有一个非常简单的键/值 API,使得它很容易与 JavaScript 一起使用。与 HTTP Cookies 不同,Web 存储完全由浏览器控制,服务器不能直接访问内容。如果您想与服务器共享 Web 存储数据,您的脚本必须将数据传输到服务器。

Web 存储定义了两种不同形式的存储:会话存储和本地存储。顾名思义,会话存储只存储当前浏览器会话的数据。当用户关闭浏览器时,会话存储的内容将被清除。另一方面,本地存储是永久性的。一旦您将数据存储在本地存储中,即使用户关闭浏览器或重启计算机或设备,它也会一直保存在本地存储中,直到您将其删除。

像 HTTP Cookies 一样,Web 存储也受来源的限制。来自给定来源的脚本只能访问该来源的 Web 存储。不允许跨来源访问。与 HTTP Cookies 不同,您不能为 Web 存储数据设置到期日期或指定路径。

方法和语法

Web 存储在全局上下文中定义了两个新对象:sessionStoragelocalStorage 。他们都有相同的方法:

  • getItem(key):返回与指定键相关的数据。
  • removeItem(key):删除与指定键相关的数据。
  • setItem(key, data):用指定的键将数据存储在存储器中。
  • clear():清除所有内容的存储。

使用网络存储非常简单,如清单 3-12 所示。

清单 3-12 。使用 Web 存储

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <h1>localStorage Example</h1>
    <script>
// Check to see if we’ve visited this page before.
var myValue = localStorage.getItem("test");
if (myValue == null) {
  alert(’This is the first time you loaded this page! Now reload this page.);
  localStorage.setItem("test", "true");
} else {
  alert(’You have loaded this page before!);
}
    </script>
  </body>
</html>

这个例子首先测试你以前是否访问过这个页面。如果没有,它会在本地存储中存储一些数据。当您重新加载页面时,数据应该仍然存在。

Web 存储的一个主要限制是它只能存储原语(文本、数字和布尔值)。更复杂的数据如数组或对象不能存储在 Web 存储器中。但是,如果需要的数据可以被格式化为 JSON 字符串,那么它可以被序列化,然后被存储。检索之后,可以解析 JSON 字符串,并恢复数据结构以供使用。编写函数来处理这一点并不困难:

/**
 * Serializes and stores an object in session storage under the specified key.
 * @param {string} key The key to store the data under.
 * @param {Object} value The object to serialize and store.
 */
function setSessionObject(key, value) {
  sessionStorage.setItem(key, JSON.stringify(value));
}

/**
 * Retrieves, deserializes, and returns the object stored in session
 * storage under the specified key.
 * @param {string} key The key that the object was stored under.
 * @return {Object} The restored object.
 * /
function getSessionObject(key) {
  var value = sessionStorage.getItem(key);
  return value && JSON.parse(value);
}

这里的setSessionObject包装了sessionStorage.setItem方法,而getSessionObject方法包装了sessionStorage.getItem方法。您也可以很容易地为localStorage创建类似的函数。但是如果sessionStoragelocalStorage都有getObjectsetObject方法,而不必使用单独的函数,这不是很好吗?幸运的是,由于 JavaScript 及其继承模型的可扩展性,这很容易做到。

在不详细研究原型继承的情况下,这里有个秘密:sessionStoragelocalStorage对象都继承自Storage抽象对象。这意味着原型对象上对Storage可用的任何方法对sessionStoragelocalStorage都可用。所以你所要做的就是把你的新方法添加到Storage:

/**
 * Serializes and stores an object in web storage under the specified key.
 * @param {string} key The key to store the data under.
 * @param {Object} value The object to serialize and store.
 */
Storage.prototype.setObject = function(key, value) {
  this.setItem(key, JSON.stringify(value));
};

/**
 * Retrieves, deserializes, and returns the object stored in web storage under
 * the specified key.
 * @param {string} key The key that the object was stored under.
 * @return {Object} The restored object.
 */
Storage.prototype.getObject = function(key) {
  var value = this.getItem(key);
  return value && JSON.parse(value);
};

同样,不要太在意语法,只要记住sessionStoragelocalStorage都是Storage的“孩子”,所以你对Storage所做的任何改进也可以用于它的孩子。

清单 3-13 展示了新方法的使用。

清单 3-13 。使用自定义存储方法

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
  </head>
  <body>
    <h1>Web Storage Example</h1>
    <script>
/**
 * Serializes and stores an object in web storage under the specified key.
 * @param {string} key The key to store the data under.
 * @param {Object} value The object to serialize and store.
 */
Storage.prototype.setObject = function(key, value) {
  this.setItem(key, JSON.stringify(value));
};

/**
 * Retrieves, deserializes, and returns the object stored in web storage under
 * the specified key.
 * @param {string} key The key that the object was stored under.
 * @return {Object} The restored object.
 */
Storage.prototype.getObject = function(key) {
  var value = this.getItem(key);
  return value && JSON.parse(value);
};

// Create a simple test object.
var myObject = {
  test: true
};

// Create a simple test array.
var myArray = [1, ’two’, true];

// Check session storage for the stored data.
if (sessionStorage.getItem(’myObject’) == null) {
  // First time here, so store the data.
  sessionStorage.setObject(’myObject’, myObject);
  sessionStorage.setObject(’myArray’, myArray);
  alert(’Data stored. Reload the page to validate.);
} else {
  // We have been here before. Get values from storage and test them.
  var newObject = sessionStorage.getObject(’myObject’);
  var newArray = sessionStorage.getObject(’myArray’);
  alert(myObject.test === newObject.test); // should alert true.
  alert(myArray[1] === newArray[1]); // should alert true.
}
    </script>
  </body>
</html>

这个例子的第一步是用你的新方法扩展Storage。接下来,创建一个对象和一个数组来测试新方法。当您加载页面时,它检查存储的值,如果不存在,它使用您的新方法存储对象和数组。如果有,它会用你的新方法得到它们,然后测试看值是否相同。

隐私和网络存储

在浏览器中存储大量永久数据的可能性引发了严重的隐私问题。尽管 Web 存储受单一来源策略的限制,但可用于设置第三方 HTTP Cookies 的相同技术也可用于设置第三方 Web 存储数据。虽然所有浏览器都为用户提供了对他们存储的 HTTP Cookies 的大量控制,但大多数浏览器还没有将这些功能扩展到 Web 存储。事实上,网络存储是用来创建所谓的 Evercookies 的方法之一(你可以在http://samy.pl/evercookie/阅读关于 Evercookies 的内容)。

与 HTTP Cookies 一样,您不应该认为 Web 存储是安全的。不要在 Web 存储器中存储任何敏感信息,如密码。

大多数浏览器都实现了某种形式的隐私浏览。没有标准规定“隐私浏览”需要什么,但在大多数情况下,这意味着所有客户端存储仅限于当前会话。这包括网络存储。如果用户正在使用浏览器的隐私浏览功能,当用户关闭该标签时,使用 Web 存储器存储的任何数据都将被清除,即使您使用localStorage存储了这些数据。因此,不能保证当用户下次返回你的应用时,你存储在localStorage中的内容还会在那里,所以如果你的应用严重依赖于localStorage,请记住这一点。

拖放

维持价

优秀

所有现代浏览器都支持这些特性,并且至少在最近的三个版本中都支持。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html

W3C 草案:http://www.w3.org/TR/html5/editing.html#dnd

图形界面中最常见的交互之一是将元素从一个地方拖放到另一个地方。不幸的是,没有简单的方法来实现这种与 HTML 和 JavaScript 的基本交互。您可以这样做,但是这非常困难,需要大量的脚本,或者使用现有的库,比如 jQuery UI(参见http://jqueryui.com/draggable/中的例子)。

现在,HTML5 规范为浏览器带来了原生的拖放交互。该流程由事件驱动,遵循以下简单步骤:

  • 将一个或多个对象声明为可拖动的,并附加所需的事件处理程序。
  • drop事件处理程序附加到目标元素。
  • 当用户拖动项目并将它们放到目标上时,会触发各种事件并调用您的处理程序。

该规范包括几个新事件、一个用于 HTML 元素的draggable属性和一个用于在事件间安全通信的dataTransfer对象。

通常有两个原因可以解释为什么你想要在你的应用中构建一个拖放交互。最简单的(也可能是最常见的)是当拖放操作表示应用将要执行的另一个过程时。在这种情况下,被拖动的项目和它们被放置的目标本身并不重要——它们只是用户界面中的元素。根据拖放操作的结果,幕后还会发生其他事情。这方面的一个例子是购物车系统的界面。您将页面中的商品拖到购物车中,但是您正在拖动的东西和目标购物车只是任意的 HTML 元素,它们的样式已经被用户识别。在幕后,数据是基于拖放操作进行操作的:购物车的数据结构随着商品的添加或删除而改变,等等。

另一种情况是被拖动的项目和/或它们被放置的目标本身很重要。在这种情况下,被拖动的内容实际上很重要,因为它本身会被处理。这方面的一个例子是可视剪贴板,您可以在其中突出显示文档中的文本,然后将其拖动到剪贴板。文本实际上是通过拖放过程从 DOM 中的一个地方转移到另一个地方的。

HTML5 拖放规范很容易处理这两种情况,我将在我的示例中展示这两种情况。在深入研究这些之前,先详细了解一下这个过程。

draggable属性

拖放规范的第一个组件是新的draggable属性。这个属性被设置在 DOM 中任何你想拖动的 HTML 元素上。如果一个元素的draggable属性设置为true,如果用户在指针位于该元素上时按住鼠标按钮,然后移动指针,浏览器将从该元素启动一个拖放序列。

可以将draggable属性设置为true(表示该项可拖动)、false(表示该项不能用于启动拖动序列)或auto(表示应用浏览器的默认规则)。

例外情况是 DOM 中任何地方的选定文本,包括表单字段,如inputtextarea字段。选定的文本总是可以启动拖动序列。

拖放事件

有几个新的拖放事件:

  • dragstart:从被拖动的元素调度。
  • dragenter:当一个可拖动的项目被拖动到任何元素中时,从该元素调度。
  • dragover:从任何一个元素连续调度,只要一个可拖动的项目在它上面。请注意,无论可拖动项是否在移动,此事件都会连续触发。
  • dragleave:当一个可拖动的项目离开它的边界时,从一个元素调度。
  • drag:从整个拖动序列中被拖动的元素调度。像dragover一样,不管指针是否被移动,这个事件都会被连续触发。
  • dragend:从鼠标释放时被拖动的元素调度。
  • drop:当用户通过释放鼠标按钮将可拖动的项目放到元素上时,从元素调度。

像其他 DOM 事件一样,您可以将拖放事件的事件处理程序添加到任何所需的元素中。但是请记住,拖放事件只会在拖动序列正在进行时触发。

拖放事件的一个重要特点是如何指定放置目标。HTML5 规范定义了一个dropzone属性作为 draggable 属性的对应物。dropzone属性应该指出哪些元素是有效的放置目标。dropzone属性没有被广泛实现,所以你必须通过操作事件来指示有效的拖放目标。

一般来说,DOM 中的大多数元素不应该是有效的放置目标,所以dragover事件的默认动作是取消放置。因此,为了指示一个有效的拖放目标,您必须通过调用事件处理程序中的event对象上的preventDefault() 方法来取消dragover事件的默认动作。

dataTransfer对象

拖放拼图的最后一块是dataTransfer对象。所有的拖放事件都可以用标准的事件处理程序来处理,这些事件处理程序将接收一个event对象作为参数。拖放的event对象的属性之一是dataTransfer对象。该对象用于控制拖放助手的外观(在拖放操作中跟随光标的幻影视觉元素),指示拖放过程正在做什么,并轻松地将数据从dragstart事件传输到drop事件。

dataTransfer对象有以下方法:

  • Event.dataTransfer.addElement(HtmlElement):指定拖动序列的源元素。这会影响到dragdragend事件的触发位置。通常情况下,您可能不需要改变这一点。
  • Event.dataTransfer.clearData(opt_DataType):清除与特定DataType相关的数据(见该列表中的setData)。如果未指定DataType,所有数据将被清除。
  • Event.dataTransfer.getData(DataType):获取与特定DataType相关的数据(见setData,下一步)。
  • Event.dataTransfer.setData(DataType, data):将指定的dataDataType关联。有效的DataTypes取决于浏览器。Internet Explorer 只支持texturlDataTypes。其他浏览器支持标准 MIME 类型,甚至任意类型。data必须是一个简单的字符串,但也可以是 JSON 格式的序列化对象。
  • Event.dataTransfer.setDragImage(HtmlElement, opt_offsetX, opt_offsetY):将拖动辅助图像设置为指定的 HTML 元素。默认情况下,辅助图像的左上角位于鼠标指针的下方,但是可以通过指定可选参数opt_offsetXopt_offsetY进行偏移,以像素为单位。这种方法在 Internet Explorer 中不可用,而且显然永远也不会出现——参见http://connect.microsoft.com/IE/feedback/details/804304/implement-datatransfer-prototype-setdragimage-method

dataTransfer对象还具有以下属性:

  • Event.dataTransfer.dropEffect:拖放序列正在执行的拖放效果。有效值为copymovelinknone。该值在dragenterdragover事件中根据用户通过鼠标动作和修饰键的组合(例如,Ctrl-拖动、Shift-拖动、Option-拖动等)所请求的交互来自动初始化。).这些依赖于平台。只有由effectAllowed(见下一页)指定的值才会真正启动拖放序列。
  • Event.dataTransfer.effectAllowed:该拖放序列允许哪些dropEffects。有效值及其允许的效果如下:
    • copy:允许复制dropEffect
    • move:允许移动dropEffect
    • link:允许链接dropEffect
    • copyLink:允许复制和链接dropEffect
    • copyMove:允许复制和移动dropEffect
    • linkMove:允许链接和移动dropEffect
    • all:所有dropEffects都是允许的。这是默认值。
    • none:不允许dropEffects(该物品不能被丢弃)。
  • Event.dataTransfer.files:包含数据传输中所有可用文件的列表。只有将文件从桌面拖到浏览器时,才会有值。
  • Event.dataTransfer.types:包含所有已经添加到dataTransfer对象中的DataTypes的列表,按照添加的顺序排列。

拖放 API 示例

清单 3-14 产生了可以想象的最简单的东西:一组可以拖放到单个目标上的可拖动的盒子。当箱子被放在目标上时,一个计数器会增加显示箱子被放的次数。

清单 3-14 。一个简单的拖放界面

<!DOCTYPE html>
<html>
  <head>
    <title>The HTML5 Programmer’s Reference</title>
    <style>
.draggable {
  margin: 5px;
  width: 100px;
  height: 100px;
  background-color: #ccc;
  border: 1px solid #000;
  display: inline-block;
}

.target {
  border: 10px solid #000;
  width: 315px;
  height: 100px;
  margin-left: 5px;
  margin-top: 50px;
}

.target.over {
  border: 10px solid green;
}
      </style>
  </head>
  <body>
    <div class="draggable" draggable=’true’></div>
    <div class="draggable" draggable=’true’></div>
    <div class="draggable" draggable=’true’></div>
    <div class="target"></div>
    <script>

// Get a reference to the drop target.
var dropTarget = document.querySelector(.target’);

// Add a dragenter event handler to the drop target.
dropTarget.addEventListener(’dragenter’, function(event) {
  // Add the ’over’ CSS class to the drop target. This lets the user know that
  // they have dragged something over a valid drop target.
  this.classList.add(’over’);
}, false);

// Add a dragleave event handler to the drop target.
dropTarget.addEventListener(’dragleave’, function(event) {
  // Remove the ’over’ CSS class.
  this.classList.remove(’over’);
}, false);

// Add a dragover event handler to the drop target.
dropTarget.addEventListener(’dragover’, function(event) {
  // Prevent the default event action.
  event.preventDefault();
}, false);

// A counter that indicates how many times something has been dropped onto the
// drop target.
var counter = 1;

// Add a drop event handler to the drop target.
dropTarget.addEventListener(’drop’, function(event) {
  // Update the counter and remove the ’over’ CSS class.
  this.innerHTML = counter;
  this.classList.remove(’over’);
  counter++;
}, false);
    </script>
  </body>
</html>

在脚本中,您所做的只是在放置目标上注册了dragenterdragleavedragoverdrop事件处理程序。在dragenter上,你给元素添加一个 CSS 类,在dragleave上,你移除 CSS 类。这给出了用户已经成功地将元素拖动到可以接收它的目标上的视觉反馈。然后阻止dragover事件的默认动作,以阻止 drop 事件。在 drop 事件处理程序中,更新目标元素的innerHTML并增加计数器。您还删除了视觉反馈 CSS 类,因为此时您将终止拖放序列,并且不会触发dragleave事件。

这个例子在 Internet Explorer 和 Chrome 中运行良好。它在 Firefox 中根本不起作用。这是因为 Firefox 要求通过指定一些数据(任何数据)在dragstart上初始化dataTransfer对象。要更新你的脚本,你必须给每个可拖动的元素添加一个dragstart事件处理程序,并在其中设置一些任意的数据,这样 Firefox 就会从这些元素中启动拖动序列。清单 3-15 对脚本进行了必要的修改(周围的 HTML 和 CSS 保持与清单 3-14 中的相同)。

清单 3-15 。拖放脚本更新了以在 Firefox 中工作

// Get a reference to all of the draggable objects.
var draggables = document.querySelectorAll(’.draggable’);

// On each draggable element intialize the dataTransfer object on dragstart so
// Firefox will initiate drag events with them.
for (var i = 0; i < draggables.length; i++) {
  currEl = draggables[i];
  currEl.addEventListener(’dragstart’, function(event) {
    event.dataTransfer.setData(’text’, ’anything’);
  }, false);
};

// Get a reference to the drop target.
var dropTarget = document.querySelector(’.target’);

// Add a dragenter event handler to the drop target.
dropTarget.addEventListener(’dragenter’, function(event) {
  // Add the ’over’ CSS class to the drop target. This lets the user know that
  // they have dragged something over a valid drop target.
  this.classList.add(’over’);
}, false);

// Add a dragleave event handler to the drop target.
dropTarget.addEventListener(’dragleave’, function(event) {
  // Remove the ’over’ CSS class.
  this.classList.remove(’over’);
}, false);

// Add a dragover event handler to the drop target.
dropTarget.addEventListener(’dragover’, function(event) {
  // Prevent the default event action.
  event.preventDefault();
}, false);

// A counter that indicates how many times something has been dropped onto the
// drop target.
var counter = 1;

// Add a drop event handler to the drop target.
dropTarget.addEventListener(’drop’, function(event) {
  // Update the counter and remove the ’over’ CSS class.
  this.innerHTML = counter;
  this.classList.remove(’over’);
  counter++;
}, false);

您会注意到代码使用querySelectorAll来获取对所有可拖动元素的引用。然后,它在一个for循环中遍历所有这些元素,并对每个元素应用dragstart事件监听器。(另一种方法是将 dragstart 事件处理程序委托给包含它的元素。)现在元素在 Firefox 中是可拖动的,这个例子在那个浏览器中的工作方式和在其他浏览器中一样。

实际上,您可能会为拖放序列初始化数据。我之前提到过一个可视剪贴板的例子,你可以在清单 3-16 中看到。

清单 3-16 。可视剪贴板

<!DOCTYPE html>
<html>
    <head>
        <title>The HTML5 Programmer’s Reference</title>
        <style>
p {
  margin-bottom: 0;
}
div#dropTarget {
  width: 300px;
  height: 300px;
  border: 10px solid black;
}
div#dropTarget.over {
  border: 10px solid green;
}
.draggable {
  width:100px;
  height: 100px;
  background-color: #ccc;
}
        </style>
    </head>
    <body>
      <p>Type some text here, then highlight it and drag it to the clipboard below.</p>
      <textarea id="dragSource"></textarea>
      <p>Clipboard</p>
      <div id="dropTarget"></div>
      <script>
// Get references to our drag source and drop target.
var dragSource = document.getElementById(’dragSource’);
var dropTarget = document.getElementById(’dropTarget’);

// Add a dragstart event handler to the dragsource element.
dragSource.addEventListener(’dragstart’, function(event) {
  // Initialize the dataTransfer object with the current text.
  event.dataTransfer.setData(’text’, this.value);
}, false); 

// Add a dragenter event handler to the target.
dropTarget.addEventListener(’dragenter’, function(event) {
  // Add the ’over’ CSS class to the element.
  this.classList.add(’over’);
}, false);

// Add a dragleave event handler to the target.
dropTarget.addEventListener(’dragleave’, function(event) {
  // Remove the ’over’ CSS class from the element.
  this.classList.remove(’over’);
}, false);

// Add a dragover event handler to the target.
dropTarget.addEventListener(’dragover’, function(event) {
  // Prevent the default action of the dragover event.
  event.preventDefault();
}, false);

// Finally, add a drop event handler to the target.
dropTarget.addEventListener(’drop’, function(event) {
  // Append the text in the dataTransfer object to the clipboard.
  this.innerHTML = event.dataTransfer.getData(’text’);
  // Remove the ’over’ CSS class from the element.
  this.classList.remove(’over’);
}, false);
      </script>
    </body>
</html>

在本例中,您已经创建了一个简单的textarea,您可以在其中输入一些文本。然后,您可以突出显示该文本,并将其拖到剪贴板。在幕后,代码在dragstart事件期间将文本设置为dataTransfer对象上的数据,然后从drop事件上的dataTransfer对象获取文本。

这个例子在几乎所有的浏览器中都运行良好,除了 Firefox 再次出现了一个小问题。当你将文本拖放到剪贴板区域时,Firefox 会在drop上触发一个默认动作,试图将页面的 URL 更新为被拖放的文本。为了防止这种情况,您需要在drop事件处理程序中调用event.preventDefault() 。通过添加这一行,该示例将在所有浏览器中运行相同。

作为最后一个例子,考虑将可拖动项的移动限制到特定区域的需要。你不想让它离开一个包含元素。或者,您可能希望将运动限制在单个轴上。不幸的是,HTML5 拖放 API 没有为这个相当常见的用例提供现成的解决方案,但是您可以使用它提供的工具构建一个。

问题的核心是你没有办法用 JavaScript 来限制鼠标指针。这有道理;用 JavaScript 操纵鼠标指针动作会带来很大的安全风险。拖放序列的设置方式,指针走到哪里,辅助图像就跟到哪里,所以如果不能限制指针,就不能限制辅助图像的位置。

这意味着你要做的第一件事是使用dataTransfer.setDragImage() 删除默认的助手图像,这意味着这个例子不能在 Internet Explorer 中工作,因为它没有实现那个方法。但是这个例子确实可以在其他浏览器中工作,并且它是一个很有价值的例子来演示一些更复杂的与 API 的交互。

下一步是构建您自己的助手映像,可以根据需要对其进行操作。一旦做到这一点,它只是一个听事件的问题。

清单 3-17 提供了完整的例子。

清单 3-17 。将拖放限制在特定区域

<!DOCTYPE html>
<html>
    <head>
        <title>The HTML5 Programmer’s Reference</title>
        <style>
.container {
  width:200px;
  height:500px;
  border: 1px solid #000;
  position: relative;
}
#dragTarget {
  height: 20px;
  background-color: #ccc;
}
.drag-helper {
  opacity: 0.5;
  position: absolute;
  width: 200px;
  height: 20px;
  background-color: #ccc;
}
.hidden {
  display: none;
}
        </style>
    </head>
    <body>
      <div class="container">
        <div id="dragTarget" draggable="true">Drag me!</div>
      </div>
      <div id="helper" style="width: 1px;height: 1px;"></div>
      <script>
// Get references to the drag target and container.
var dragTarget = document.getElementById(’dragTarget’);
var dragContainer = document.querySelector(.container’);

// This variable will hold the new helper when we build it.
var dragHelper;

// Add a dragstart event handler to the drag target.
dragTarget.addEventListener(’dragstart’, function(event) {
  // Initialize the dataTransfer object for Firefox.
  event.dataTransfer.setData(’text’, ’Fix for Firefox’);

  // Replace the default drag image with a small, transparent DIV.
  var dragImage = document.getElementById(’helper’);
  event.dataTransfer.setDragImage(dragImage, 0, 0);

  if (dragHelper == null) {
    // Create our own drag helper by cloning the target element. Note that when
    // we clone the element we need to do some cleanup, like removing the clone’s
    // id attribute (so we do not introduce duplicate ids even temporarily) and
    // making not draggable.
    dragHelper = this.cloneNode(true);
    dragHelper.id = ’’;
    dragHelper.classList.add(’drag-helper’);
    dragHelper.draggable = false;
    dragContainer.appendChild(dragHelper);
  } else {
    // We’ve already created a clone, so let’s just use it.
    dragHelper.classList.remove(’hidden’);
  }
}, false);

// Add a dragover event handler to the drag container.
dragContainer.addEventListener(’dragover’, function(event) {
  // Prevent the default action of the event.
  event.preventDefault();

  // Move the helper to the desired location.
  if (event.clientY < 485) {
    dragHelper.style.top = event.clientY + ’px’;
  }
}, false);

// Add a dragend event handler to the target.
dragTarget.addEventListener(’dragend’, function(event) {
  // Dragging is done, so hide the clone.
  dragHelper.classList.add(’hidden’);
}, false);

// Add a drop event to the drag container.
dragContainer.addEventListener(’drop’, function(event) {
  event.preventDefault();
}, false);
      </script>
    </body>
</html>

您将在dragstart事件处理程序中完成所有的设置。首先是对 Firefox 的修复,否则这个例子就不能在那个浏览器中运行。然后,通过获取对一个小的透明div的引用并将其用作新的辅助图像来隐藏默认的辅助图像。接下来,克隆目标元素,将克隆的元素设置为新的助手,并将其添加到 DOM 中。

在您的dragover事件处理程序中,您所要做的就是将辅助对象移动到期望的位置。请记住,由于dragover事件在整个拖动过程中持续触发,您应该尽可能保持其事件处理程序的轻量级。这就是为什么您使用对拖动辅助对象的缓存引用,这样您就不必在每次事件触发时都查询它。当你使用这个例子时,你会注意到,因为你只监听包含元素中的dragover事件,当你将指针移出该元素时,助手将停止移动。您可以将dragover事件处理程序委托给body元素,这将允许用户从页面上的任何地方移动元素。

您将自定义助手隐藏在您的dragend事件处理程序中。这样做是为了无论用户在哪里释放鼠标按钮,助手都将被隐藏。这是dragend事件的常见用法。

如上所述,这个例子在 Internet Explorer 中不起作用,所以尽管这个例子很有趣,但对于大多数情况来说并不实用。不幸的是,也没有办法填充setDragImage方法。因此,如果这是您需要的交互类型,并且您必须支持 Internet Explorer,您可能需要寻找除 HTML5 原生拖放 API 之外的另一种解决方案。

Web Worker s

维持价

好的

所有现代浏览器都支持这些特性,并且至少在最近两个版本中都支持。

WHATWG 生活水平:http://www.whatwg.org/specs/web-apps/current-work/multipage/workers.html

W3C 草案:http://dev.w3.org/html5/workers/

对基于网络的应用最大的批评之一是速度:它们不够快,尤其是在移动设备上。尽管我是 web 技术(以及用这些技术构建应用)的粉丝,但速度确实是一个合理的问题。

web 应用运行缓慢的原因有很多,但其中一个主要问题是在 web 浏览器中运行的脚本一次只能做一件事。例如,考虑从服务器获取和解析(或者操作)一个大文件的常见例子。使用XMLHttpRequest 很容易建立一个异步请求,它将加载文件,然后在准备好的时候执行一个回调函数。在回调函数被调用之前,您的脚本可以执行其他任务。但是一旦回调函数开始执行,当它以您指定的方式处理数据时,一切都必须等待。你的脚本不能更新用户界面或响应用户交互或任何事情。这对用户来说是非常令人沮丧的。

web 浏览器的异步环境帮助我们创建了响应速度更快的 web 应用,但仍然不允许我们同时执行多项任务。HTML5 规范解决了 Web Workers 的这一限制,它使我们能够同时创建和操作多个独立的任务。

在引擎盖下,Web 工作者让我们能够访问现代浏览器及其主机系统的多线程功能。一个线程是执行一个特定任务所需的操作系统和程序资源的组合,并通过启动、暂停、停止和处理其完成来管理该任务的状态。在具有多个处理器内核的现代硬件上运行的现代操作系统可以同时处理多个线程。当您创建一个新的 Web Worker 时,浏览器会为该任务生成一个单独的线程,它与主浏览器线程同时执行。

多线程是一个强大的工具,像所有强大的工具一样,它也有一些危险。网络工作者有一些旨在减少这些危险的重要限制:

  • Web Worker 运行在自己独立的 JavaScript 上下文中。它不能直接访问任何其他执行上下文中的任何内容,比如其他 Web Workers 或主 JavaScript 线程。
  • Web Worker 上下文和主 JavaScript 线程之间的通信是通过一个类似于 Web 消息传递所使用的postMessage接口来完成的。这使您能够将数据传入和传出 Web Worker 上下文,但是因为所有上下文都是独立的,所以在上下文之间传递的任何数据都是复制的,而不是共享的。
  • Web Worker 无法访问 DOM。Web 工作者可以使用的 DOM 方法只有atobbtoaclearIntervalclearTimeoutdumpsetIntervalsetTimeout
  • Web Workers 受相同来源策略的约束,因此您不能从不同于原始脚本的来源加载 worker 脚本。

这些都是严格的限制(特别是缺少对 DOM 的访问权限),但是它们有助于使 Web Workers 成为您使用的更安全的工具。如果您曾经用其他语言构建过多线程应用,您可能对该功能中固有的所有危险都很熟悉:并发性、安全性等等。由于他们的限制,网络工作者大多没有这些危险。

Web Workers 的另一个重要特征是,您必须自己管理您的员工。您负责创建它们、启动它们、停止它们,以及在它们的任务完成后处置它们。因为 Web Workers 消耗主机系统资源,所以正确管理它们以避免影响整个系统的性能非常重要。

创建网络工作者

创建和管理 Web Workers 遵循三个基本步骤:

  1. 创建新的 Web Worker。
  2. 将一个message事件处理程序附加到新的 worker,假设您希望它传达结果。您还应该附加一个error事件处理程序,以便您的脚本可以对 worker 执行期间发生的任何错误做出反应。
  3. 通过向 worker 实例发布消息来启动它。这将导致 worker 开始运行,并且还会在其中触发一个message事件,这样 worker 就可以处理您刚刚发布的消息。

第一步很简单。Web Workers 规范在全局上下文中创建新的Worker构造函数。通过为它指定一个 JavaScript 程序来加载和执行,您创建了一个Worker的新实例:

var myNewWorker = new Worker(’my-new-worker.js’);

文件’my-new-worker.js’必须是有效的 JavaScript 文件,一旦创建了 worker,它就会被加载。

Worker 实例myNewWorker将发布messageerror事件,这样您就可以为这些事件附加事件处理程序。清单 3-18 显示了带有存根函数的基本模式。

清单 3-18 。Web Worker 的存根错误和消息事件处理程序

/**
 * Handles an error event from web worker.
 * @param {WorkerErrorEvent} event The error event object.
 */
function handleWorkerError(event) {
  // Handle the error here.
  console.warn(’Error in web worker: ’, event.message);
}

/**
 * Handles a message event from a web worker.
 * @param {WorkerMessageEvent} event The message event object.
 */
function handleWorkerMessage(event) {
  // Handle the message here.
  console.log(’Message from worker: ’, event.data);
}

// Create a new worker.
var myNewWorker = new Worker(’my-new-worker.js’);

// Register error and message event handlers.
myNewWorker.addEventListener(’error’, handleWorkerError);
myNewWorker.addEventListener(’message’, handleWorkerMessage);

在这个基本示例中,有一些简单的函数来处理errormessage事件,这些事件只是在 JavaScript 控制台中显示结果。您使用Worker实例myNewWorker上的addEventListener方法将它们注册为处理程序。请注意,您应该总是在启动 worker 之前注册事件处理程序。如果您启动 worker,然后注册事件处理程序,在完成注册的几毫秒内可能会发生一些事情,您可能会错过一条消息或一个错误。

要启动 worker,只需使用postMessage方法向它发布一条消息:

myNewWorker.postMessage(’start’);

当您将消息发送到 worker 实例时,它将开始执行脚本,并且还将在 worker 的执行上下文中为开始消息触发一个message事件。(注意,虽然Worker.postMessage方法类似于用于 Web 消息传递的window.postMessage方法,但是它没有后者的可选域参数。)

在网络工作者内部

在 Web Worker 内部,脚本的环境与主执行上下文略有不同。如前所述,Web Worker 不能访问 DOM,因此任何访问window对象或其子对象(比如document对象或 DOM 中的任何元素)的尝试都将失败。Web Workers 可以访问以下标准属性和方法:

  • DOM 方法atobbtoaclearIntervalclearTimeoutdumpsetIntervalsetTimeout

  • XMLHttpRequest构造函数,因此 Web 工作者可以执行异步网络任务。

  • WebSocket构造函数,因此 Web Workers 可以创建和管理 WebSockets(在撰写本文时,Firefox 没有为 Web Workers 启用WebSocket;不过,这个功能正在实现中,你可以在https://bugzilla.mozilla.org/show_bug.cgi?id=504553跟踪它的状态

  • Worker构造函数,因此 Web Workers 可以产生自己的 Workers(被称为子 workers )。截至本文撰写之时,Chrome 和 Safari 还没有为 Web Workers 实现Worker构造函数。在https://code.google.com/p/chromium/issues/detail?id=31666的 Chrome 和https://bugs.webkit.org/show_bug.cgi?id=22723的 Safari 的 WebKit 都有一个 bug。从版本 10 开始,Internet Explorer 支持子工作器。

  • EventSource构造函数,因此 Web 工作者可以订阅服务器发送的事件流。这似乎是一个非标准特性,但是在撰写本文时,似乎在所有主流浏览器中都可以使用。

  • Navigator属性的特殊子集,可通过navigator对象获得:

    • navigator.language:返回浏览器当前使用的语言。

    • navigator.onLine:返回一个布尔值,表示浏览器是否在线。

    • navigator.platform:返回表示主机系统平台的字符串。

    • navigator.product:返回当前浏览器名称的字符串。

    • navigator.userAgent: Returns the user agent string for the browser.

      这些属性的实现因浏览器而异,因此最好将所需的Navigator信息从主线程传递给 Web Worker。

  • location对象上可用的Location属性的特殊子集:

    • location.href:Web Worker 正在执行的脚本的完整 URL。
    • location.protocol:Web Worker 正在执行的脚本的 URL 的协议方案,包括最后的“:”。
    • location.host:Web Worker 正在执行的脚本的 URL 的主机部分(主机名和端口)。
    • location.hostname:Web Worker 正在执行的脚本的 URL 的主机名部分。
    • location.port:Web Worker 正在执行的脚本的 URL 的端口部分。
    • location.pathname:首字母“/”,后跟 Web Worker 正在执行的脚本的路径。
    • location.search:Web Worker 正在执行的脚本的 URL 的参数(如果有的话),后跟首字母“?”。
    • location.hash:Web Worker 正在执行的脚本的 URL 的片段标识符(如果有的话),后面是首字母“#”。
  • 还有一个方法location.toString()简单的返回location.href

Web Worker 执行上下文还有一个新的可用方法:importScriptsimportScripts方法采用逗号分隔的一个或多个 JavaScript 文件名列表,这些文件名将按顺序加载和执行。例如,这一行

importScripts(’script1.js’, ’script2.js’, ’subdirectory/script3.js’);

将按顺序加载并执行指定的三个脚本。相对 URL 被解析为相对于创建 Web Worker 实例时指定的脚本的 URL。importScripts方法也受相同起源策略的约束,因此您不能从为 Web Worker 实例的父脚本提供服务的不同起源导入脚本。

importScripts方法是一个阻塞方法,这意味着每个脚本都将按顺序加载和执行,并且在最后一个脚本完成加载和执行之前,工人不会继续下一行。如果其中一个脚本由于网络问题而无法加载,或者加载了但由于内部错误而无法运行,Web Worker 将停止执行并发布一个error事件。

importScripts加载的脚本在与 Web Worker 相同的上下文中执行。他们不能访问 DOM,但是他们可以访问上面列出的所有标准属性和方法以及importScripts方法,所以导入的脚本可以导入其他脚本。

当 Web Worker 启动时,它遵循以下步骤:

  • 它从头到尾执行脚本,包括任何异步任务(比如XMLHttpRequest调用)。
  • 如果其执行的一部分是注册一个message事件处理程序,那么它将进入一个等待传入消息的循环。它收到的第一条消息将是发布来启动工作进程的消息。工作线程将保持等待模式,直到您手动终止它,或者它自己终止。
  • 如果没有注册message事件处理程序,工作线程将自动终止。

这是很重要的一点:如果您的 Web Worker 注册了一个message事件处理程序,它将永远处于等待模式,除非您终止它。同样,因为 Web 工作线程消耗系统资源,所以您应该确保终止任何不需要的工作线程。您可以通过以下两种方式之一解雇员工:

  • 可以在 Worker 实例:myWebWorker.terminate();上调用terminate方法。
  • Web Worker 可以通过调用它的close方法:self.close();来终止自己。

这两种方法都会立即停止工作进程。

一个网络工作者的简单例子

清单 3-19 扩展了清单 3-18 中的存根示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意本节中的例子需要从服务器上运行。

清单 3-19 。创建 Web Worker

<!DOCTYPE html>
<html>
    <head>
        <title>The HTML5 Programmer’s Reference</title>
    </head>
    <body>
      <h1>Web Workers</h1>
      <div id="message-box"></div>
      <script>
// Get a reference to the target element.
var messageBox = document.getElementById(’message-box’);

/**
 * Handles an error event from web worker.
 * @param {WorkerErrorEvent} event The error event object.
 */
function handleWorkerError(event) {
  console.warn(’Error in web worker:, event.message);
}

/**
 * Handles a message event from a web worker.
 * @param {WorkerMessageEvent} event The message event object.
 */
function handleWorkerMessage(event) {
  messageBox.innerHTML = ’Message received from worker:+ event.data;
}

// Create a new worker.
var myNewWorker = new Worker(’web-worker.js’);

// Register error and message event handlers on the worker.
myNewWorker.addEventListener(’error’, handleWorkerError);
myNewWorker.addEventListener(’message’, handleWorkerMessage);

// Start the worker.
myNewWorker.postMessage(’begin’);
      </script>
    </body>
</html>

和前面一样,创建两个事件处理程序,一个用于错误事件,一个用于消息事件。错误事件处理程序只是将错误记录到控制台,而消息事件处理程序将消息文本附加到 DOM。然后创建一个新的 Web Worker,在其上注册您的事件处理程序,最后,向它发布一条消息来启动它。

清单 3-20 显示了工人本身的代码。

清单 3-20 。一个简单的 Web Worker 脚本

/**
 * Handles a message event from the main context.
 * @param {WorkerMessageEvent} event The message event.
 */
function handleMessageEvent(event) {
  // Do something with the message.
  console.log(’Worker received message:’, event.data);

  // Send the message back to the main context.
  self.postMessage(’Your message was received.’);
}

// Register the message event handler.
self.addEventListener(’message’, handleMessageEvent);

这个 Web Worker 创建一个消息事件处理程序,将消息记录到控制台。然后,它将确认消息发送回父线程,并在执行上下文中注册事件处理程序。

当您运行这个示例时,它将来回传递消息,但不会展示 Web Workers 的真正威力,即它们与主线程同时执行。

常见使用案例

Web Worker 允许您以这样一种方式重新构造应用,即您有一个处理 UI 的主线程,而任何其他密集型或异步操作都由 Web Worker 线程处理。好的例子包括:

  • 异步活动:因为 Web Workers 可以访问XMLHttpRequest构造函数以及 WebSockets 和theimportScripts方法,所以它们可以用来加载和解析数据,或者(甚至更好)将大量数据发送回服务器。
  • 计算密集型活动:任何需要大量计算的工作都是网络工作者的理想选择。密码学是一个很好的例子,游戏的物理引擎也是。
  • 图像处理:如果你有大量来自 canvas 元素的数据需要处理,你可以利用 Web Workers 来处理这些数据。
  • 分而治之:我已经提到过使用 Web Workers 来处理大量数据。如果您可以将所讨论的数据分成更小的部分,那么您可以给每个部分一个自己的 Web Worker 来处理,从而使事情进行得更快。

摘要

在这一章中,我介绍了几个 HTML5 JavaScript APIs。使用这些新的 API,您的应用可以:

  • 与服务器进行更高效、更安全的通信。您的应用现在可以订阅服务器发送的事件流,甚至可以使用 WebSockets 建立与服务器的双向通信,而不仅仅是依赖于XmlHttpRequest。您还可以使用跨文档消息传递来实现脚本来源之间更安全的通信。
  • 在客户端更高效地存储信息。使用新的 Web 存储特性,您的应用可以轻松地存储和检索信息,包括序列化的对象和数据结构。
  • 使用新的拖放 API 高效地实现拖放交互。拖放项目是一种非常常见的用户交互隐喻,现在使用新的 API 更容易实现。
  • 创建和管理线程。使用 Web Workers,您的应用现在可以是多线程的。

使用这些新的 API,您的应用可以更高效、更易于使用,也更易于创建和维护。

在下一章中,你将深入了解 HTML5 最激动人心的特性之一:canvas元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值