欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

13.事件(3)

程序员文章站 2022-07-11 11:40:39
...
  • 键盘与文本事件:用户在使用键盘时会触发键盘事件。“DOM2 级事件”最初规定了键盘事件,但在最终定稿之前又删除了相应的内容。结果,对键盘事件的支持主要遵循的是DOM0 级。

“DOM3 级事件”为键盘事件制定了规范,IE9 率先完全实现了该规范。其他浏览器也在着手实现这一标准,但仍然有很多遗留的问题。

有3 个键盘事件,简述如下。

  1. keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
  2. keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下Esc 键也会触发这个事件。Safari 3.1 之前的版本也会在用户按下非字符键时触发keypress事件。
  3. keyup:当用户释放键盘上的键时触发。

虽然所有元素都支持以上3 个事件,但只有在用户通过文本框输入文本时才最常用到。

只有一个文本事件:textInput。这个事件是对keypress 的补充,用意是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发textInput 事件。

在用户按了一下键盘上的字符键时,首先会触发keydown 事件,然后紧跟着是keypress 事件,最后会触发keyup 事件。其中,keydown 和keypress 都是在文本框发生变化之前被触发的;而keyup事件则是在文本框已经发生变化之后被触发的。如果用户按下了一个字符键不放,就会重复触发keydown 和keypress 事件,直到用户松开该键为止。

如果用户按下的是一个非字符键,那么首先会触发keydown 事件,然后就是keyup 事件。如果按住这个非字符键不放,那么就会一直重复触发keydown 事件,直到用户松开这个键,此时会触发keyup事件。

键盘事件与鼠标事件一样,都支持相同的修改键。而且,键盘事件的事件对象中也有shiftKey、ctrlKey、altKey 和metaKey 属性。IE 不支持metaKey。

1. 键码:在发生keydown 和keyup 事件时,event 对象的keyCode 属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键,keyCode 属性的值与ASCII 码中对应小写字母或数字的编码相同。因此,数字键7 的keyCode 值为55,而字母A 键的keyCode 值为65——与Shift 键的状态无关。DOM 和IE 的event 对象都支持keyCode 属性。请看下面这个例子:

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keyup", function(event){
	event = EventUtil.getEvent(event);
	alert(event.keyCode);
});

在这个例子中,用户每次在文本框中按键触发keyup 事件时,都会显示keyCode 的值。下表列出了所有非字符键的键码。

键码 键码
退格(Backspace) 8 数字小键盘1 97
制表(Tab) 9 数字小键盘2 98
回车(Enter) 13 数字小键盘3 99
上档(Shift) 16 数字小键盘4 100
控制(Ctrl) 17 数字小键盘5 101
Alt 18 数字小键盘6 102
暂停/中断(Pause/Break) 19 数字小键盘7 103
大写锁定(Caps Lock) 20 数字小键盘8 104
退出(Esc) 27 数字小键盘9 105
上翻页(Page Up) 33 数字小键盘+ 107
下翻页(Page Down) 34 数字小键盘及大键盘上的- 109
结尾(End) 35 数字小键盘. 110
开头(Home) 36 数字小键盘/ 111
左箭头(Left Arrow) 37 F1 112
上箭头(Up Arrow) 38 F2 113
右箭头(Right Arrow) 39 F3 114
下箭头(Down Arrow) 40 F4 115
插入(Ins) 45 F5 116
删除(Del) 46 F6 117
左Windows键 91 F7 118
右Windows键 92 F8 119
上下文菜单键 93 F9 120
数字小键盘0 96 F10 121
F11 122 正斜杠 191
F12 123 沉音符(`) 192
数字锁(Num Lock) 144 等于 61
滚动锁(Scroll Lock) 145 左方括号 219
分号(IE/Safari/Chrome中) 186 反斜杠(\) 220
分号(Opera/FF中) 59 右方括号 221
小于 188 单引号 222
大于 190    

无论keydown 或keyup 事件都会存在的一些特殊情况。在Firefox 和Opera 中,按分号键时keyCode值为59,也就是ASCII 中分号的编码;但IE 和Safari 返回186,即键盘中按键的键码。

2. 字符编码:发生keypress 事件意味着按下的键会影响到屏幕中文本的显示。在所有浏览器中,按下能够插入或删除字符的键都会触发keypress 事件;按下其他键能否触发此事件因浏览器而异。由于截止到2008年,尚无浏览器实现“DOM3 级事件”规范,所以浏览器之间的键盘事件并没有多大的差异。

IE9、Firefox、Chrome 和Safari 的event 对象都支持一个charCode 属性,这个属性只有在发生keypress 事件时才包含值,而且这个值是按下的那个键所代表字符的ASCII 编码。此时的keyCode通常等于0 或者也可能等于所按键的键码。IE8 及之前版本和Opera 则是在keyCode 中保存字符的ASCII编码。要想以跨浏览器的方式取得字符编码,必须首先检测charCode 属性是否可用,如果不可用则使用keyCode,如下面的例子所示。

var EventUtil = {
	//省略的代码
	getCharCode: function(event){
		if (typeof event.charCode == "number"){
			return event.charCode;
		} else {
			return event.keyCode;
		}
	},
	//省略的代码
};

这个方法首先检测charCode 属性是否包含数值(在不支持这个属性的浏览器中,值为undefined),如果是,则返回该值。否则,就返回keyCode 属性值。下面是使用这个方法的示例。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
	event = EventUtil.getEvent(event);
	alert(EventUtil.getCharCode(event));
});

在取得了字符编码之后,就可以使用String.fromCharCode()将其转换成实际的字符。

3. DOM3 级变化:尽管所有浏览器都实现了某种形式的键盘事件,DOM3 级事件还是做出了一些改变。比如,DOM3级事件中的键盘事件,不再包含charCode 属性,而是包含两个新属性:key 和char。

其中,key 属性是为了取代keyCode 而新增的,它的值是一个字符串。在按下某个字符键时,key的值就是相应的文本字符(如“k”或“M”);在按下非字符键时, key 的值是相应键的名(如“Shift”或“Down”)。而char 属性在按下字符键时的行为与key 相同,但在按下非字符键时值为null。

IE9 支持key 属性,但不支持char 属性。Safari 5 和Chrome 支持名为keyIdentifier 的属性,在按下非字符键(例如Shift)的情况下与key 的值相同。对于字符键,keyIdentifier 返回一个格式类似“U+0000”的字符串,表示Unicode 值。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var identifier = event.key || event.keyIdentifier;
    if (identifier){
        alert(identifi er);
    }
});

由于存在跨浏览器问题,因此本书不推荐使用key、keyIdentifier 或char。

DOM3 级事件还添加了一个名为location 的属性,这是一个数值,表示按下了什么位置上的键:0 表示默认键盘,1 表示左侧位置(例如左位的Alt 键),2 表示右侧位置(例如右侧的Shift 键),3 表示数字小键盘,4 表示移动设备键盘(也就是虚拟键盘),5 表示手柄(如任天堂Wii 控制器)。IE9 支持这个属性。Safari 和Chrome 支持名为keyLocation 的等价属性,但即有bug——值始终是0,除非按下了数字键盘(此时,值 为3);否则,不会是1、2、4、5。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var loc = event.location || event.keyLocation;
    if (loc){
        alert(loc);
    }
});

与key 属性一样,支持location 的浏览器也不多,所以在跨浏览器开发中不推荐使用。

最后是给event 对象添加了getModifierState()方法。这个方法接收一个参数,即等于Shift、Control、AltGraph 或Meta 的字符串,表示要检测的修改键。如果指定的修改键是活动的(也就是处于被按下的状态),这个方法返回true,否则返回false。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    if (event.getModifierState){
        alert(event.getModifierState("Shift"));
    }
});

实际上,通过event 对象的shiftKey、altKey、ctrlKey 和metaKey 属性已经可以取得类似的属性了。IE9 是唯一支持getModifierState()方法的浏览器。

4. textInput 事件:

“DOM3 级事件”规范中引入了一个新事件,名叫textInput。根据规范,当用户在可编辑区域中输入字符时,就会触发这个事件。这个用于替代keypress 的textInput 事件的行为稍有不同。区别之一就是任何可以获得焦点的元素都可以触发keypress 事件,但只有可编辑区域才能触发textInput事件。区别之二是textInput 事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。

由于textInput 事件主要考虑的是字符,因此它的event 对象中还包含一个data 属性,这个属性的值就是用户输入的字符(而非字符编码)。换句话说,用户在没有按上档键的情况下按下了S 键,data 的值就是"s",而如果在按住上档键时按下该键,data 的值就是"S"。

以下是一个使用textInput 事件的例子:

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){
    event = EventUtil.getEvent(event);
    alert(event.data);
});

在这个例子中,插入到文本框中的字符会通过一个警告框显示出来。

另外,event 对象上还有一个属性,叫inputMethod,表示把文本输入到文本框中的方式。

  • 0,表示浏览器不确定是怎么输入的。
  • 1,表示是使用键盘输入的。
  • 2,表示文本是粘贴进来的。
  • 3,表示文本是拖放进来的。
  • 4,表示文本是使用IME 输入的。
  • 5,表示文本是通过在表单中选择某一项输入的。
  • 6,表示文本是通过手写输入的(比如使用手写笔)。
  • 7,表示文本是通过语音输入的。
  • 8,表示文本是通过几种方法组合输入的。
  • 9,表示文本是通过脚本输入的。

使用这个属性可以确定文本是如何输入到控件中的,从而可以验证其有效性。支持textInput 属性的浏览器有IE9+、Safari 和Chrome。只有IE 支持inputMethod 属性。

5. 设备中的键盘事件:任天堂Wii 会在用户按下Wii 遥控器上的按键时触发键盘事件。尽管没有办法访问Wii 遥控器中的所有按键,但还是有一些键可以触发键盘事件。

13.事件(3)

当用户按下十字键盘(键码为175~178)、减号(170)、加号(174)、1(172)或2(173)键时就会触发键盘事件。但没有办法得知用户是否按下了电源开关、A、B 或主页键。
iOS 版Safari 和Android 版WebKit 在使用屏幕键盘时会触发键盘事件。

  • 复合事件:复合事件(composition event)是DOM3 级事件中新添加的一类事件,用于处理IME 的输入序列。IME(Input Method Editor,输入法编辑器)可以让用户输入在物理键盘上找不到的字符。例如,使用拉丁文键盘的用户通过IME 照样能输入日文字符。IME 通常需要同时按住多个键,但最终只输入一个字符。复合事件就是针对检测和处理这种输入而设计的。有以下三种复合事件。
  1. compositionstart:在IME 的文本复合系统打开时触发,表示要开始输入了。
  2. compositionupdate:在向输入字段中插入新字符时触发。
  3. compositionend:在IME 的文本复合系统关闭时触发,表示返回正常键盘输入状态。

复合事件与文本事件在很多方面都很相似。在触发复合事件时,目标是接收文本的输入字段。但它比文本事件的事件对象多一个属性data,其中包含以下几个值中的一个:

  1. 如果在compositionstart 事件发生时访问,包含正在编辑的文本(例如,已经选中的需要马上替换的文本);
  2. 如果在compositionupdate 事件发生时访问,包含正插入的新字符;
  3. 如果在compositionend 事件发生时访问,包含此次输入会话中插入的所有字符。

与文本事件一样,必要时可以利用复合事件来筛选输入。可以像下面这样使用它们:

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "compositionstart", function(event){
	event = EventUtil.getEvent(event);
	alert(event.data);
});
EventUtil.addHandler(textbox, "compositionupdate", function(event){
	event = EventUtil.getEvent(event);
	alert(event.data);
});
EventUtil.addHandler(textbox, "compositionend", function(event){
	event = EventUtil.getEvent(event);
	alert(event.data);
});

IE9+是到2011 年唯一支持复合事件的浏览器。由于缺少支持,对于需要开发跨浏览器应用的开发人员,它的用处不大。要确定浏览器是否支持复合事件,可以使用以下代码:

var isSupported = document.implementation.hasFeature("CompositionEvent", "3.0");
  • 变动事件:DOM2 级的变动(mutation)事件能在DOM 中的某一部分发生变化时给出提示。变动事件是为XML或HTML DOM设计的,并不特定于某种语言。DOM2 级定义了如下变动事件。
  1. DOMSubtreeModified:在DOM 结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。
  2. DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发。
  3. DOMNodeRemoved:在节点从其父节点中被移除时触发。
  4. DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在DOMNodeInserted 之后触发。
  5. DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved 之后触发。
  6. DOMAttrModified:在特性被修改之后触发。
  7. DOMCharacterDataModified:在文本节点的值发生变化时触发。

使用下列代码可以检测出浏览器是否支持变动事件:

var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");

IE8 及更早版本不支持任何变动事件。下表列出了不同浏览器对不同变动事件的支持情况。

事件 Opera9+ Firefox3+ Safari 3+及Chrome IE9+
DOMSubtreeModified 支持 支持 支持
DOMNodeInserted 支持 支持 支持 支持
DOMNodeRemoved 支持 支持 支持 支持

1. 删除节点:在使用removeChild()或replaceChild()从DOM中删除节点时,首先会触发DOMNodeRemoved事件。这个事件的目标(event.target)是被删除的节点,而event.relatedNode 属性中包含着对目标节点父节点的引用。在这个事件触发时,节点尚未从其父节点删除,因此其parentNode 属性仍然指向父节点(与event.relatedNode 相同)。这个事件会冒泡,因而可以在DOM 的任何层次上面处理它。

如果被移除的节点包含子节点,那么在其所有子节点以及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument 事件。但这个事件不会冒泡,所以只有直接指定给其中一个子节点的事件处理程序才会被调用。这个事件的目标是相应的子节点或者那个被移除的节点,除此之外event对象中不包含其他信息。

紧随其后触发的是DOMSubtreeModified 事件。这个事件的目标是被移除节点的父节点;此时的event 对象也不会提供与事件相关的其他信息。

为了理解上述事件的触发过程,下面我们就以一个简单的HTML 页面为例。

<! DOCTYPE html>
<html>
<head>
	<title>Node Removal Events Example</title>
</head>
<body>
	<ul id="myList">
		<li>Item 1</li>
		<li>Item 2</li>
		<li>Item 3</li>
	</ul>
</body>
</html>

在这个例子中,我们假设要移除<ul>元素。此时,就会依次触发以下事件。

  • 在<ul>元素上触发DOMNodeRemoved 事件。relatedNode 属性等于document.body。
  • 在<ul>元素上触发DOMNodeRemovedFromDocument 事件。
  • 在身为<ul>元素子节点的每个<li>元素及文本节点上触发DOMNodeRemovedFromDocument事件。
  • 在document.body 上触发DOMSubtreeModified 事件,因为<ul>元素是document.body的直接子元素。

运行下列代码可以验证以上事件发生的顺序。

EventUtil.addHandler(window, "load", function(event){
var list = document.getElementById("myList");
EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
	alert(event.type);
	alert(event.target);
});
EventUtil.addHandler(document, "DOMNodeRemoved", function(event){
	alert(event.type);
	alert(event.target);
	alert(event.relatedNode);
});
EventUtil.addHandler(list.firstChild, "DOMNodeRemovedFromDocument", function(event){
	alert(event.type);
	alert(event.target);
});
list.parentNode.removeChild(list);
});

以上代码为document 添加了针对DOMSubtreeModified 和DOMNodeRemoved 事件的处理程序,以便在页面上处理这些事件。由于DOMNodeRemovedFromDocument 不会冒泡,所以我们将针对它的事件处理程序直接添加给了<ul>元素的第一个子节点(在兼容DOM 的浏览器中是一个文本节点)。在设置了以上事件处理程序后,代码从文档中移除了<ul>元素。

2. 插入节点:在使用appendChild()、replaceChild()或insertBefore()向DOM中插入节点时,首先会触发DOMNodeInserted 事件。这个事件的目标是被插入的节点,而event.relatedNode 属性中包含一个对父节点的引用。在这个事件触发时,节点已经被插入到了新的父节点中。这个事件是冒泡的,因此可以在DOM 的各个层次上处理它。

紧接着,会在新插入的节点上面触发DOMNodeInsertedIntoDocument 事件。这个事件不冒泡,因此必须在插入节点之前为它添加这个事件处理程序。这个事件的目标是被插入的节点,除此之外event 对象中不包含其他信息。

最后一个触发的事件是DOMSubtreeModified,触发于新插入节点的父节点。

我们仍以前面的HTML 文档为例,可以通过下列JavaScript 代码来验证上述事件的触发顺序。

EventUtil.addHandler(window, "load", function(event){
	var list = document.getElementById("myList");
	var item = document.createElement("li");
	item.appendChild(document.createTextNode("Item 4"));
	EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
		alert(event.type);
		alert(event.target);
	});
	EventUtil.addHandler(document, "DOMNodeInserted", function(event){
		alert(event.type);
		alert(event.target);
		alert(event.relatedNode);
	});
	EventUtil.addHandler(item, "DOMNodeInsertedIntoDocument", function(event){
		alert(event.type);
		alert(event.target);
	});
	list.appendChild(item);
});

以上代码首先创建了一个包含文本"Item 4"的新<li>元素。由于DOMSubtreeModified 和DOMNodeInserted 事件是冒泡的,所以把它们的事件处理程序添加到了文档中。在将列表项插入到其父节点之前,先将DOMNodeInsertedIntoDocument 事件的事件处理程序添加给它。最后一步就是使用appendChild()来添加这个列表项;此时,事件开始依次被触发。首先是在新<li>元素项上触发DOMNodeInserted 事件,其relatedNode 是<ul>元素。然后是触发新<li>元素上的DOMNodeInsertedIntoDocument 事件,最后触发的是<ul>元素上的DOMSubtreeModified 事件。

  • HTML5 事件:DOM 规范没有涵盖所有浏览器支持的所有事件。很多浏览器出于不同的目的——满足用户需求或解决特殊问题,还实现了一些自定义的事件。HTML5 详尽列出了浏览器应该支持的所有事件。本节只讨论其中得到浏览器完善支持的事件,但并非全部事件。(其他事件会在本书其他章节讨论。)

1. contextmenu 事件:Windows 95 在PC 中引入了上下文菜单的概念,即通过单击鼠标右键可以调出上下文菜单。不久,这个概念也被引入了Web 领域。为了实现上下文菜单,开发人员面临的主要问题是如何确定应该显示上下文菜单(在Windows 中,是右键单击;在Mac 中,是Ctrl+单击),以及如何屏蔽与该操作关联的默认上下文菜单。为解决这个问题,就出现了contextmenu 这个事件,用以表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义的菜单。

由于contextmenu 事件是冒泡的,因此可以为document 指定一个事件处理程序,用以处理页面中发生的所有此类事件。这个事件的目标是发生用户操作的元素。在所有浏览器中都可以取消这个事件:在兼容DOM的浏览器中,使用event.preventDefalut();在IE 中,将event.returnValue 的值设置为false。因为contextmenu 事件属于鼠标事件,所以其事件对象中包含与光标位置有关的所有属性。通常使用contextmenu 事件来显示自定义的上下文菜单,而使用onclick 事件处理程序来隐藏该菜单。以下面的HTML 页面为例。

<!DOCTYPE html>
<html>
<head>
	<title>ContextMenu Event Example</title>
</head>
<body>
	<div id="myDiv">Right click or Ctrl+click me to get a custom context menu.
Click anywhere else to get the default context menu.</div>
	<ul id="myMenu" style="position:absolute;visibility:hidden;background-color:silver">
		<li><a href="http://www.nczonline.net">Nicholas’ site</a></li>
		<li><a href="http://www.wrox.com">Wrox site</a></li>
		<li><a href="http://www.yahoo.com">Yahoo!</a></li>
	</ul>
</body>
</html>

//这里的<div>元素包含一个自定义的上下文菜单。其中,<ul>元素作为自定义上下文菜单,
//并且在初始时是隐藏的。实现这个例子的JavaScript 代码如下所示。
EventUtil.addHandler(window, "load", function(event){
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "contextmenu", function(event){
        event = EventUtil.getEvent(event);
            EventUtil.preventDefault(event);
            var menu = document.getElementById("myMenu");
            menu.style.left = event.clientX + "px";
            menu.style.top = event.clientY + "px";
            menu.style.visibility = "visible";
        });
    EventUtil.addHandler(document, "click", function(event){
        document.getElementById("myMenu").style.visibility = "hidden";
    });
});

在这个例子中,我们为<div>元素添加了oncontextmenu 事件的处理程序。这个事件处理程序首先会取消默认行为,以保证不显示浏览器默认的上下文菜单。然后,再根据event 对象clientX 和clientY 属性的值,来确定放置<ul>元素的位置。最后一步就是通过将visibility 属性设置为"visible"来显示自定义上下文菜单。另外,还为document 添加了一个onclick 事件处理程序,以便用户能够通过鼠标单击来隐藏菜单(单击也是隐藏系统上下文菜单的默认操作)。

虽然这个例子很简单,但它却展示了Web 上所有自定义上下文菜单的基本结构。只需为这个例子中的上下文菜单添加一些CSS 样式,就可以得到非常棒的效果。

支持contextmenu 事件的浏览器有IE、Firefox、Safari、Chrome 和Opera 11+。

2. beforeunload 事件:之所以有发生在window 对象上的beforeunload 事件,是为了让开发人员有可能在页面卸载前阻止这一操作。这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面。但是,不能彻底取消这个事件,因为那就相当于让用户无法离开当前页面了。为此,这个事件的意图是将控制权交给用户。显示的消息会告知用户页面行将被卸载(正因为如此才会显示这个消息),询问用户是否真的要关闭页面,还是希望继续留下来。

13.事件(3)

为了显示这个弹出对话框,必须将event.returnValue 的值设置为要显示给用户的字符串(对IE 及Fiefox 而言),同时作为函数的值返回(对Safari 和Chrome 而言),如下面的例子所示。

EventUtil.addHandler(window, "beforeunload", function(event){
    event = EventUtil.getEvent(event);
    var message = "I'm really going to miss you if you go.";
    event.returnValue = message;
    return message;
});

IE 和Firefox、Safari 和Chrome 都支持beforeunload 事件,也都会弹出这个对话框询问用户是否真想离开。Opera 11 及之前的版本不支持beforeunload 事件。 

3. DOMContentLoaded 事件:如前所述,window 的load 事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。而DOMContentLoaded 事件则在形成完整的DOM 树之后就会触发,不理会图像、JavaScript 文件、CSS 文件或其他资源是否已经下载完毕。与load 事件不同,DOMContentLoaded 支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。

要处理DOMContentLoaded 事件,可以为document 或window 添加相应的事件处理程序(尽管这个事件会冒泡到window,但它的目标实际上是document)。来看下面的例子。

EventUtil.addHandler(document, "DOMContentLoaded", function(event){
    alert("Content loaded");
});

DOMContentLoaded 事件对象不会提供任何额外的信息(其target 属性是document)。

IE9+、Firefox、Chrome、Safari 3.1+和Opera 9+都支持DOMContentLoaded 事件,通常这个事件既可以添加事件处理程序,也可以执行其他DOM 操作。这个事件始终都会在load 事件之前触发。

对于不支持DOMContentLoaded 的浏览器,我们建议在页面加载期间设置一个时间为0 毫秒的超时调用,如下面的例子所示。

setTimeout(function(){
    //在此添加事件处理程序
}, 0);

这段代码的实际意思就是:“在当前JavaScript 处理完成后立即运行这个函数。”在页面下载和构建期间,只有一个JavaScript 处理过程,因此超时调用会在该过程结束时立即触发。至于这个时间与DOMContentLoaded 被触发的时间能否同步,主要还是取决于用户使用的浏览器和页面中的其他代码。

为了确保这个方法有效,必须将其作为页面中的第一个超时调用;即便如此,也还是无法保证在所有环境中该超时调用一定会早于load 事件被触发。

4. readystatechange 事件:IE 为DOM文档中的某些部分提供了readystatechange 事件。这个事件的目的是提供与文档或元素的加载状态有关的信息,但这个事件的行为有时候也很难预料。支持readystatechange 事件的每个对象都有一个readyState 属性,可能包含下列5 个值中的一个。

  • uninitialized(未初始化):对象存在但尚未初始化。
  • loading(正在加载):对象正在加载数据。
  • loaded(加载完毕):对象加载数据完成。
  • interactive(交互):可以操作对象了,但还没有完全加载。
  • complete(完成):对象已经加载完毕。

这些状态看起来很直观,但并非所有对象都会经历readyState 的这几个阶段。换句话说,如果某个阶段不适用某个对象,则该对象完全可能跳过该阶段;并没有规定哪个阶段适用于哪个对象。显然,这意味着readystatechange 事件经常会少于4 次,而readyState 属性的值也不总是连续的。

对于document 而言,值为"interactive"的readyState 会在与DOMContentLoaded 大致相同的时刻触发readystatechange 事件。此时,DOM树已经加载完毕,可以安全地操作它了,因此就会进入交互(interactive)阶段。但与此同时,图像及其他外部文件不一定可用。下面来看一段处理readystatechange 事件的代码。

EventUtil.addHandler(document, "readystatechange", function(event){
    if (document.readyState == "interactive"){
        alert("Content loaded");
    }
});

这个事件的event 对象不会提供任何信息,也没有目标对象。

在与load 事件一起使用时,无法预测两个事件触发的先后顺序。在包含较多或较大的外部资源的页面中,会在load 事件触发之前先进入交互阶段;而在包含较少或较小的外部资源的页面中,则很难说readystatechange 事件会发生在load 事件前面。

让问题变得更复杂的是,交互阶段可能会早于也可能会晚于完成阶段出现,无法确保顺序。在包含较多外部资源的页面中,交互阶段更有可能早于完成阶段出现;而在页面中包含较少外部资源的情况下,完成阶段先于交互阶段出现的可能性更大。因此,为了尽可能抢到先机,有必要同时检测交互和完成阶段,如下面的例子所示。

EventUtil.addHandler(document, "readystatechange", function(event){
    if (document.readyState == "interactive" || document.readyState == "complete"){
        EventUtil.removeHandler(document, "readystatechange", arguments.callee);
        alert("Content loaded");
    }
});

对于上面的代码来说,当readystatechange 事件触发时,会检测document.readyState 的值,看当前是否已经进入交互阶段或完成阶段。如果是,则移除相应的事件处理程序以免在其他阶段再执行。注意,由于事件处理程序使用的是匿名函数,因此这里使用了arguments.callee 来引用该函数。然后,会显示一个警告框,说明内容已经加载完毕。这样编写代码可以达到与使用DOMContentLoaded十分相近的效果。

支持readystatechange 事件的浏览器有IE、Firfox 4+和Opera。

虽然使用readystatechange 可以十分近似地模拟DOMContentLoaded 事件,但它们本质上还是不同的。在不同页面中,load 事件与readystatechange 事件并不能保证以相同的顺序触发。

另外, <script>(在IE 和Opera 中)和<link>(仅IE 中)元素也会触发readystatechange事件,可以用来确定外部的JavaScript 和CSS 文件是否已经加载完成。与在其他浏览器中一样,除非把动态创建的元素添加到页面中, 否则浏览器不会开始下载外部资源。基于元素触发的readystatechange 事件也存在同样的问题, 即readyState 属性无论等于"loaded" 还是"complete"都可以表示资源已经可用。有时候,readyState 会停在"loaded"阶段而永远不会“完成”;有时候,又会跳过"loaded"阶段而直接“完成”。于是,还需要像对待document 一样采取相同的编码方式。例如,下面展示了一段加载外部JavaScript 文件的代码。

EventUtil.addHandler(window, "load", function(){
	var script = document.createElement("script");
	EventUtil.addHandler(script, "readystatechange", function(event){
		event = EventUtil.getEvent(event);
		var target = EventUtil.getTarget(event);
		if (target.readyState == "loaded" || target.readyState == "complete"){
			EventUtil.removeHandler(target, "readystatechange", arguments. callee);
			alert("Script Loaded");
		}
	});
	script.src = "example.js";
	document.body.appendChild(script);
});

这个例子为新创建的<script>节点指定了一个事件处理程序。事件的目标是该节点本身,因此当触发readystatechange 事件时, 要检测目标的readyState 属性是不是等于"loaded" 或"complete"。如果进入了其中任何一个阶段,则移除事件处理程序(以防止被执行两次),并显示一个警告框。与此同时,就可以执行已经加载完毕的外部文件中的函数了。

同样的编码方式也适用于通过<link>元素加载CSS 文件的情况,如下面的例子所示。

EventUtil.addHandler(window, "load", function(){
	var link = document.createElement("link");
	link.type = "text/css";
	link.rel= "stylesheet";
	EventUtil.addHandler(script, "readystatechange", function(event){
		event = EventUtil.getEvent(event);
		var target = EventUtil.getTarget(event);
		if (target.readyState == "loaded" || target.readyState == "complete"){
			EventUtil.removeHandler(target, "readystatechange", arguments. callee);
			alert("CSS Loaded");
		}
	});
	link.href = "example.css";
	document.getElementsByTagName("head")[0].appendChild(link);
});

同样,最重要的是要一并检测readyState 的两个状态,并在调用了一次事件处理程序后就将其移除。

5. pageshow 和pagehide 事件:Firefox 和Opera 有一个特性,名叫“往返缓存”(back-forward cache,或bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOM 和JavaScript 的状态;实际上是将整个页面都保存在了内存里。如果页面位于bfcache 中,那么再次打开该页面时就不会触发load 事件。尽管由于内存中保存了整个页面的状态,不触发load 事件也不应该会导致什么问题,但为了更形象地说明bfcache 的行为,Firefox 还是提供了一些新事件。

第一个事件就是pageshow,这个事件在页面显示时触发,无论该页面是否来自bfcache。在重新加载的页面中,pageshow 会在load 事件触发后触发;而对于bfcache 中的页面,pageshow 会在页面状态完全恢复的那一刻触发。另外要注意的是,虽然这个事件的目标是document,但必须将其事件处理程序添加到window。来看下面的例子。

(function(){
    var showCount = 0;
    EventUtil.addHandler(window, "load", function(){
        alert("Load fired");
    });
    EventUtil.addHandler(window, "pageshow", function(){
    	showCount++;
    	alert("Show has been fired " + showCount + " times.");
	});
})();

这个例子使用了私有作用域,以防止变量showCount 进入全局作用域。当页面首次加载完成时,showCount 的值为0。此后,每当触发pageshow 事件,showCount 的值就会递增并通过警告框显示出来。如果你在离开包含以上代码的页面之后,又单击“后退”按钮返回该页面,就会看到showCount每次递增的值。这是因为该变量的状态,乃至整个页面的状态,都被保存在了内存中,当你返回这个页面时,它们的状态得到了恢复。如果你单击了浏览器的“刷新”按钮,那么showCount 的值就会被重置为0,因为页面已经完全重新加载了。

除了通常的属性之外,pageshow 事件的event 对象还包含一个名为persisted 的布尔值属性。如果页面被保存在了bfcache 中,则这个属性的值为true;否则,这个属性的值为false。可以像下面这样在事件处理程序中检测这个属性。

(function(){
	var showCount = 0;
	EventUtil.addHandler(window, "load", function(){
		alert("Load fired");
	});
	EventUtil.addHandler(window, "pageshow", function(){
		showCount++;
		alert("Show has been fired " + showCount + " times. Persisted? " + event.persisted);
	});
})();

通过检测persisted 属性,就可以根据页面在bfcache 中的状态来确定是否需要采取其他操作。

与pageshow 事件对应的是pagehide 事件,该事件会在浏览器卸载页面的时候触发,而且是在unload 事件之前触发。与pageshow 事件一样,pagehide 在document 上面触发,但其事件处理程序必须要添加到window 对象。这个事件的event 对象也包含persisted 属性,不过其用途稍有不同。来看下面的例子。

EventUtil.addHandler(window, "pagehide", function(event){
    alert("Hiding. Persisted? " + event.persisted);
});

有时候,可能需要在pagehide 事件触发时根据persisted 的值采取不同的操作。对于pageshow事件,如果页面是从bfcache 中加载的,那么persisted 的值就是true;对于pagehide 事件,如果页面在卸载之后会被保存在bfcache 中,那么persisted 的值也会被设置为true。因此,当第一次触发pageshow 时,persisted 的值一定是false,而在第一次触发pagehide 时,persisted 就会变成true(除非页面不会被保存在bfcache 中)。

支持pageshow 和pagehide 事件的浏览器有Firefox、Safari 5+、Chrome 和Opera。IE9 及之前版本不支持这两个事件。

指定了onunload 事件处理程序的页面会被自动排除在 bfcache 之外,即使事件处理程序是空的。原因在于,onunload 最常用于撤销在onload 中所执行的操作,而跳过onload 后再次显示页面很可能就会导致页面不正常。

6. hashchange 事件:HTML5 新增了hashchange 事件,以便在URL 的参数列表(及URL 中“#”号后面的所有字符串)发生变化时通知开发人员。之所以新增这个事件,是因为在Ajax 应用中,开发人员经常要利用URL 参数列表来保存状态或导航信息。

必须要把hashchange 事件处理程序添加给window 对象,然后URL 参数列表只要变化就会调用它。此时的event 对象应该额外包含两个属性:oldURL 和newURL。这两个属性分别保存着参数列表变化前后的完整URL。例如:

EventUtil.addHandler(window, "hashchange", function(event){
    alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL);
});

支持hashchange 事件的浏览器有IE8+、Firefox 3.6+、Safari 5+、Chrome 和Opera 10.6+。在这些浏览器中,只有Firefox 6+、Chrome 和Opera 支持oldURL 和newURL 属性。为此,最好是使用location对象来确定当前的参数列表。

EventUtil.addHandler(window, "hashchange", function(event){
    alert("Current hash: " + location.hash);
});

使用以下代码可以检测浏览器是否支持hashchange 事件:

var isSupported = ("onhashchange" in window); //这里有bug

如果IE8 是在IE7 文档模式下运行,即使功能无效它也会返回true。为解决这个问题,可以使用以下这个更稳妥的检测方式:

var isSupported = ("onhashchange" in window) && 
(document.documentMode === undefined || document.documentMode > 7);
  • 设备事件:智能手机和平板电脑的普及,为用户与浏览器交互引入了一种新的方式,而一类新事件也应运而生。设备事件(device event)可以让开发人员确定用户在怎样使用设备。W3C 从2011 年开始着手制定一份关于设备事件的新草案(http://dev.w3.org/geo/api/spec-source-orientation.html),以涵盖不断增长的设备类型并为它们定义相关的事件。本节会同时讨论这份草案中涉及的API 和特定于浏览器开发商的事件。

1. orientationchange 事件:苹果公司为移动Safari 中添加了orientationchange 事件,以便开发人员能够确定用户何时将设备由横向查看模式切换为纵向查看模式。移动Safari 的window.orientation 属性中可能包含3 个值:0 表示肖像模式,90 表示向左旋转的横向模式(“主屏幕”按钮在右侧),-90 表示向右旋转的横向模式(“主屏幕”按钮在左侧)。相关文档中还提到一个值,即180 表示iPhone 头朝下;但这种模式至今尚未得到支持。图13-10 展示了window.orientation 的每个值的含义。

13.事件(3)

只要用户改变了设备的查看模式,就会触发orientationchange 事件。此时的event 对象不包含任何有价值的信息,因为唯一相关的信息可以通过window.orientation 访问到。下面是使用这个事件的典型示例。

EventUtil.addHandler(window, "load", function(event){
    var div = document.getElementById("myDiv");
    div.innerHTML = "Current orientation is " + window.orientation;
    EventUtil.addHandler(window, "orientationchange", function(event){
        div.innerHTML = "Current orientation is " + window.orientation;
    });
});

在这个例子中,当触发load 事件时会显示最初的方向信息。然后,添加了处理orientationchange事件的处理程序。只要发生这个事件,就会有表示新方向的信息更新页面中的消息。

所有iOS 设备都支持orientationchange 事件和window.orientation 属性。

由于可以将orientationchange 看成window 事件,所以也可以通过指定<body>元素的onorientationchange 特性来指定事件处理程序。

2. MozOrientation 事件:Firefox 3.6 为检测设备的方向引入了一个名为MozOrientation 的新事件。(前缀Moz 表示这是特定于浏览器开发商的事件,不是标准事件。)当设备的加速计检测到设备方向改变时,就会触发这个事件。但这个事件与iOS 中的orientationchange 事件不同,该事件只能提供一个平面的方向变化。由于MozOrientation 事件是在window 对象上触发的,所以可以使用以下代码来处理。

EventUtil.addHandler(window, "MozOrientation", function(event){
    //响应事件
});

此时的event 对象包含三个属性:x、y 和z。这几个属性的值都介于1 到-1 之间,表示不同坐标轴上的方向。在静止状态下,x 值为0,y 值为0,z 值为1(表示设备处于竖直状态)。如果设备向右倾斜,x 值会减小;反之,向左倾斜,x 值会增大。类似地,如果设备向远离用户的方向倾斜,y 值会减小,向接近用户的方向倾斜,y 值会增大。z 轴检测垂直加速度度,1 表示静止不动,在设备移动时值会减小。(失重状态下值为0。)以下是输出这三个值的一个简单的例子。

EventUtil.addHandler(window, "MozOrientation", function(event){
    var output = document.getElementById("output");
    output.innerHTML = "X=" + event.x + ", Y=" + event.y + ", Z=" + event.z +"<br>";
});

只有带加速计的设备才支持MozOrientation 事件,包括Macbook、Lenovo Thinkpad、WindowsMobile 和Android 设备。请大家注意,这是一个实验性API,将来可能会变(可能会被其他事件取代)。

3. deviceorientation 事件:本质上,DeviceOrientation Event 规范定义的deviceorientation 事件与MozOrientation 事件类似。它也是在加速计检测到设备方向变化时在window 对象上触发,而且具有与MozOrientation 事件相同的支持限制。不过,deviceorientation 事件的意图是告诉开发人员设备在空间中朝向哪儿,而不是如何移动。

设备在三维空间中是靠x、y 和z 轴来定位的。当设备静止放在水平表面上时,这三个值都是0。x轴方向是从左往右,y 轴方向是从下往上,z 轴方向是从后往前。

13.事件(3)

触发deviceorientation 事件时,事件对象中包含着每个轴相对于设备静止状态下发生变化的信息。事件对象包含以下5 个属性。

  • alpha:在围绕z 轴旋转时(即左右旋转时),y 轴的度数差;是一个介于0 到360 之间的浮点数。
  • beta:在围绕x 轴旋转时(即前后旋转时),z 轴的度数差;是一个介于180 到180 之间的浮点数。
  • gamma:在围绕y 轴旋转时(即扭转设备时),z 轴的度数差;是一个介于90 到90 之间的浮点数。
  • absolute:布尔值,表示设备是否返回一个绝对值。
  • compassCalibrated:布尔值,表示设备的指南针是否校准过。

13.事件(3)

下面是一个输出alpha、beta 和gamma 值的例子。

EventUtil.addHandler(window, "deviceorientation", function(event){
    var output = document.getElementById("output");
    output.innerHTML = "Alpha=" + event.alpha + ", Beta=" + event.beta +
    ", Gamma=" + event.gamma + "<br>";
});

通过这些信息,可以响应设备的方向,重新排列或修改屏幕上的元素。要响应设备方向的改变而旋转元素,可以参考如下代码。

EventUtil.addHandler(window, "deviceorientation", function(event){
    var arrow = document.getElementById("arrow");
    arrow.style.webkitTransform = "rotate(" + Math.round(event.alpha) + "deg)";
});

这个例子只能在移动WebKit 浏览器中运行,因为它使用了专有的webkitTransform 属性(即CSS标准属性transform 的临时版)。元素“arrow”会随着event.alpha 值的变化而旋转,给人一种指南针的感觉。为了保证旋转平滑,这里的CSS3 变换使用了舍入之后的值。

到2011 年,支持deviceorientation 事件的浏览器有iOS 4.2+中的Safari、Chrome 和Android 版WebKit。

4. devicemotion 事件:DeviceOrientation Event 规范还定义了一个devicemotion 事件。这个事件是要告诉开发人员设备什么时候移动,而不仅仅是设备方向如何改变。例如,通过devicemotion 能够检测到设备是不是正在往下掉,或者是不是被走着的人拿在手里。

触发devicemotion 事件时,事件对象包含以下属性。

  • acceleration:一个包含x、y 和z 属性的对象,在不考虑重力的情况下,告诉你在每个方向上的加速度。
  • accelerationIncludingGravity:一个包含x、y 和z 属性的对象,在考虑z 轴自然重力加速度的情况下,告诉你在每个方向上的加速度。
  • interval:以毫秒表示的时间值,必须在另一个devicemotion 事件触发前传入。这个值在每个事件中应该是一个常量。
  • rotationRate:一个包含表示方向的alpha、beta 和gamma 属性的对象。

如果读取不到acceleration、accelerationIncludingGravity 和rotationRate 值,则它们的值为null。因此,在使用这三个属性之前,应该先检测确定它们的值不是null。例如:

EventUtil.addHandler(window, "devicemotion", function(event){
    var output = document.getElementById("output");
    if (event.rotationRate !== null){
        output.innerHTML += "Alpha=" + event.rotationRate.alpha + ", Beta=" +
        event.rotationRate.beta + ", Gamma=" +
        event.rotationRate.gamma;
    }
});

与deviceorientation 事件类似,只有iOS 4.2+中的Safari、Chrome 和Android 版WebKit 实现了devicemotion 事件。

  • 触摸与手势事件:iOS 版Safari 为了向开发人员传达一些特殊信息,新增了一些专有事件。因为iOS 设备既没有鼠标也没有键盘,所以在为移动Safari 开发交互性网页时,常规的鼠标和键盘事件根本不够用。随着Android中的WebKit 的加入,很多这样的专有事件变成了事实标准,导致W3C 开始制定Touch Events 规范(参见https://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html)。以下介绍的事件只针对触摸设备。

1. 触摸事件:包含iOS 2.0 软件的iPhone 3G 发布时,也包含了一个新版本的Safari 浏览器。这款新的移动Safari提供了一些与触摸(touch)操作相关的新事件。后来,Android 上的浏览器也实现了相同的事件。触摸事件会在用户手指放在屏幕上面时、在屏幕上滑动时或从屏幕上移开时触发。具体来说,有以下几个触摸事件。

  1. touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
  2. touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault()可以阻止滚动。
  3. touchend:当手指从屏幕上移开时触发。
  4. touchcancel:当系统停止跟踪触摸时触发。关于此事件的确切触发时间,文档中没有明确说明。

上面这几个事件都会冒泡,也都可以取消。虽然这些触摸事件没有在DOM 规范中定义,但它们却是以兼容DOM 的方式实现的。因此,每个触摸事件的event 对象都提供了在鼠标事件中常见的属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和metaKey。

除了常见的DOM属性外,触摸事件还包含下列三个用于跟踪触摸的属性。

  1. touches:表示当前跟踪的触摸操作的Touch 对象的数组。
  2. targetTouchs:特定于事件目标的Touch 对象的数组。
  3. changeTouches:表示自上次触摸以来发生了什么改变的Touch 对象的数组。

每个Touch 对象包含下列属性。

  1. clientX:触摸目标在视口中的x 坐标。
  2. clientY:触摸目标在视口中的y 坐标。
  3. identifier:标识触摸的唯一ID。
  4. pageX:触摸目标在页面中的x 坐标。
  5. pageY:触摸目标在页面中的y 坐标。
  6. screenX:触摸目标在屏幕中的x 坐标。
  7. screenY:触摸目标在屏幕中的y 坐标。
  8. target:触摸的DOM 节点目标。

使用这些属性可以跟踪用户对屏幕的触摸操作。来看下面的例子。

function handleTouchEvent(event){
	//只跟踪一次触摸
	if (event.touches.length == 1){
		var output = document.getElementById("output");
		switch(event.type){
			case "touchstart":
				output.innerHTML = "Touch started (" + event.touches[0].clientX +
				"," + event.touches[0].clientY + ")";
				break;
			case "touchend":
				output.innerHTML += "<br>Touch ended (" +
				event.changedTouches[0].clientX + "," +
				event.changedTouches[0].clientY + ")";
				break;
			case "touchmove":
				event.preventDefault(); //阻止滚动
				output.innerHTML += "<br>Touch moved (" +
				event.changedTouches[0].clientX + "," +
				event.changedTouches[0].clientY + ")";
				break;
		}
	}
}
EventUtil.addHandler(document, "touchstart", handleTouchEvent);
EventUtil.addHandler(document, "touchend", handleTouchEvent);
EventUtil.addHandler(document, "touchmove", handleTouchEvent);

以上代码会跟踪屏幕上发生的一次触摸操作。为简单起见,只会在有一次活动触摸操作的情况下输出信息。当touchstart 事件发生时,会将触摸的位置信息输出到<div>元素中。当touchmove 事件发生时,会取消其默认行为,阻止滚动(触摸移动的默认行为是滚动页面),然后输出触摸操作的变化信息。而touchend 事件则会输出有关触摸操作的最终信息。注意,在touchend 事件发生时,touches集合中就没有任何Touch 对象了,因为不存在活动的触摸操作;此时,就必须转而使用changeTouchs集合。

这些事件会在文档的所有元素上面触发,因而可以分别操作页面的不同部分。在触摸屏幕上的元素时,这些事件(包括鼠标事件)发生的顺序如下:

  1. touchstart
  2. mouseover
  3. mousemove(一次)
  4. mousedown
  5. mouseup
  6. click
  7. touchend

支持触摸事件的浏览器包括iOS 版Safari、Android 版WebKit、bada 版Dolfin、OS6+中的BlackBerryWebKit、Opera Mobile10.1+和LG 专有OS 中的Phantom 浏览器。目前只有iOS 版Safari 支持多点触摸。桌面版Firefox 6+和Chrome 也支持触摸事件。

2. 手势事件:iOS 2.0 中的Safari 还引入了一组手势事件。当两个手指触摸屏幕时就会产生手势,手势通常会改变显示项的大小,或者旋转显示项。有三个手势事件,分别介绍如下。

  1. gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
  2. gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
  3. gestureend:当任何一个手指从屏幕上面移开时触发。

只有两个手指都触摸到事件的接收容器时才会触发这些事件。在一个元素上设置事件处理程序,意味着两个手指必须同时位于该元素的范围之内,才能触发手势事件(这个元素就是目标)。由于这些事件冒泡,所以将事件处理程序放在文档上也可以处理所有手势事件。此时,事件的目标就是两个手指都于其范围内的那个元素。

触摸事件和手势事件之间存在某种关系。当一个手指放在屏幕上时,会触发touchstart 事件。如果另一个手指又放在了屏幕上,则会先触发gesturestart 事件,随后触发基于该手指的touchstart事件。如果一个或两个手指在屏幕上滑动,将会触发gesturechange 事件。但只要有一个手指移开,就会触发gestureend 事件,紧接着又会触发基于该手指的touchend 事件。

与触摸事件一样,每个手势事件的event 对象都包含着标准的鼠标事件属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和metaKey。此外,还包含两个额外的属性:rotation 和scale。其中,rotation 属性表示手指变化引起的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转(该值从0 开始)。而scale属性表示两个手指间距离的变化情况(例如向内收缩会缩短距离);这个值从1 开始,并随距离拉大而增长,随距离缩短而减小。

下面是使用手势事件的一个示例。

function handleGestureEvent(event){
    var output = document.getElementById("output");
    switch(event.type){
        case "gesturestart":
        	output.innerHTML = "Gesture started (rotation=" + event.rotation + 
",scale=" + event.scale + ")";
			break;
		case "gestureend":
			output.innerHTML += "<br>Gesture ended (rotation=" + event.rotation + 
",scale=" + event.scale + ")";
			break;
		case "gesturechange":
			output.innerHTML += "<br>Gesture changed (rotation=" + event.rotation + 
",scale=" + event.scale + ")";
			break;
	}
}
document.addEventListener("gesturestart", handleGestureEvent, false);
document.addEventListener("gestureend", handleGestureEvent, false);
document.addEventListener("gesturechange", handleGestureEvent, false);

与前面演示触摸事件的例子一样,这里的代码只是将每个事件都关联到同一个函数中,然后通过该
函数输出每个事件的相关信息。

触摸事件也会返回rotation 和scale 属性,但这两个属性只会在两个手指与屏幕保持接触时才会发生变化。一般来说,使用基于两个手指的手势事件,要比管理触摸事件中的所有交互要容易得多。