13.4 事件类型(键盘与文本事件)
一、键盘事件
-
用户在使用键盘时会触发键盘事件。“DOM2 级事件”最初规定了键盘事件,但在最终定稿之前又 删除了相应的内容。结果,对键盘事件的支持主要遵循的是 DOM0 级。
-
“DOM3 级事件”为键盘事件制定了规范,IE9 率先完全实现了该规范。其他浏览器也在着手实现这 一标准,但仍然有很多遗留的问题。
-
有 3 个键盘事件,简述如下。
keydown
、keypress
、keyup
-
虽然所有元素都支持以上 3 个事件,但只有在用户通过文本框输入文本时才最常用到。
-
在用户按了一下键盘上的字符键时,首先会触发 keydown 事件,然后紧跟着是 keypress 事件, 最后会触发 keyup 事件。
- 其中,keydown 和 keypress 都是在文本框发生变化之前被触发的;而 keyup 事件则是在文本框已经发生变化之后被触发的。
- 如果用户按下了一个字符键不放,就会重复触发 keydown 和 keypress 事件,直到用户松开该键为止。
- 如果用户按下的是一个非字符键,那么首先会触发 keydown 事件,然后就是 keyup 事件。如果按 住这个非字符键不放,那么就会一直重复触发 keydown 事件,直到用户松开这个键,此时会触发 keyup 事件。
二、文本事件
- 只有一个文本事件:
textInput
。这个事件是对 keypress 的补充,用意是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发 textInput 事件。
- 根据规范,当用户在可编辑区域中输入字符时,就会触发这个事件。
- keypress 和 textInput 事件的区别:
- 区别之一就是任何可以获得焦点的元素都可以触发 keypress 事件,但只有可编辑区域才能触发 textInput 事件。
- 区别之二是 textInput 事件只会在用户按下能够输入实际字符的键时才会被触发,而 keypress 事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。
- textInput 事件的
data 属性
:由于 textInput 事件主要考虑的是字符,因此它的 event 对象中还包含一个 data 属性,这个属性的值就是用户输入的字符(而非字符编码)。换句话说,用户在没有按上档键的情况下按下了 S 键, data 的值就是"s",而如果在按住上档键时按下该键,data 的值就是"S"。【关注的是实际输入的是什么】 - 以下是一个使用 textInput 事件的例子:
三、键码
1、keyCode 属性
- 在发生 keydown 和 keyup 事件时,event 对象的 keyCode 属性中会包含一个代码,与键盘上一个特定的键对应。
- 对数字字母字符键,keyCode 属性的值与 ASCII 码中对应小写字母或数字的编码相同。因此,数字键 7 的 keyCode 值为 55,而字母 A 键的 keyCode 值为 65——与 Shift 键的状态无关。 DOM 和 IE 的 event 对象都支持 keyCode 属性。请看下面这个例子:
- 下表列出 了所有非字符键的键码。
无论 keydown 或 keyup 事件都会存在的一些特殊情况。在 Firefox 和 Opera 中,按分号键时 keyCode 值为 59,也就是 ASCII 中分号的编码;但 IE 和 Safari 返回 186,即键盘中按键的键码。
三、字符编码
发生 keypress
事件意味着按下的键会影响到屏幕中文本的显示。在所有浏览器中,按下能够插入 或删除字符的键都会触发 keypress 事件;按下其他键能否触发此事件因浏览器而异。
由于截止到 2008 年,尚无浏览器实现“DOM3 级事件”规范,所以浏览器之间的键盘事件并没有多大的差异。
1、 charCode 属性
charCode 属性
:IE9、Firefox、Chrome 和 Safari 的 event 对象都支持一个 charCode 属性,这个属性只有在发生 keypress 事件时才包含值,而且这个值是按下的那个键所代表字符的 ASCII 编码。此时的 keyCode 通常等于 0 或者也可能等于所按键的键码。
- IE8 及之前版本和 Opera 则是在 keyCode 中保存字符的 ASCII 编码。要想以跨浏览器的方式取得字符编码,必须首先检测 charCode 属性是否可用,如果不可用则使 用 keyCode,如下面的例子所示。
- 下面是使用这个方法的示例。
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getCharCode(event));
});
在取得了字符编码之后,就可以使用 String.fromCharCode()
将其转换成实际的字符。
四、DOM3 级变化
1、新增key、keyIdentifier 或 char属性(跨浏览器开发时不推荐使用)
- 尽管所有浏览器都实现了某种形式的键盘事件,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(identifier);
}
});
由于存在跨浏览器问题,因此本书不推荐使用 key、keyIdentifier 或 char。
2、添加了location 的属性(不推荐使用)
- location 的属性: DOM3 级事件还添加了一个名为
location 的属性
,这是一个数值,表示按下了什么位置上的键: 0 表示默认键盘,1 表示左侧位置(例如左位的 Alt 键),2 表示右侧位置(例如右侧的 Shift 键),3 表示 数字小键盘,4 表示移动设备键盘(也就是虚拟键盘),5 表示手柄(如任天堂 Wii 控制器)。
- IE9 支持这 个属性。Safari 和 Chrome 支持名为
keyLocation
的等价属性,但即有 bug——值始终是 0,除非按下了数字键盘(此时,值 为 3);否则,不会是 1、2、4、5。
与 key 属性一样,支持 location 的浏览器也不多,所以在跨浏览器开发中不推荐使用。
3、getModifierState()方法(不推荐使用)
最后是给 event 对象添加了 getModifierState()
方法。这个方法接收一个参数,即等于 Shift、 Control、AltGraph 或 Meta 的字符串,表示要检测的修改键。如果指定的修改键是活动的(也就是 处于被按下的状态),这个方法返回 true,否则返回 false。
实际上,通过 event 对象的 shiftKey、altKey、ctrlKey 和 metaKey 属性已经可以取得类似的属性了。IE9 是唯一支持 getModifierState()方法的浏览器。
六、设备中的键盘事件
任天堂 Wii 会在用户按下 Wii 遥控器上的按键时触发键盘事件。尽管没有办法访问 Wii 遥控器中的所有按键,但还是有一些键可以触发键盘事件。遥控器上的一些键有对应的键码,通过这些键码可以知道用户按下了哪个键。
iOS 版 Safari 和 Android 版 WebKit 在使用屏幕键盘时会触发键盘事件。
七、复合事件(用处不大)
- 复合事件(composition event)是 DOM3 级事件中新添加的一类事件**,用于处理 IME 的输入序列**。
- IME(Input Method Editor,输入法编辑器)可以让用户输入在物理键盘上找不到的字符。例如,使用拉 丁文键盘的用户通过 IME 照样能输入日文字符。IME 通常需要同时按住多个键,但最终只输入一个字符。复合事件就是针对检测和处理这种输入而设计的。
- 有以下三种复合事件。
- 复合事件与文本事件在很多方面都很相似。在触发复合事件时,目标是接收文本的输入字段。但它比文本事件的事件对象多一个
属性 data
,其中包含以下几个值中的一个:
- 与文本事件一样,必要时可以利用复合事件来筛选输入。可以像下面这样使用它们:
IE9+是到 2011 年唯一支持复合事件的浏览器。由于缺少支持,对于需要开发跨浏览器应用的开发 人员,它的用处不大。要确定浏览器是否支持复合事件,可以使用以下代码:
八、变动事件
1、变动事件种类
-
DOM2 级的变动(mutation)事件能在 DOM 中的某一部分发生变化时给出提示。变动事件是为 XML 或 HTML DOM 设计的,并不特定于某种语言。DOM2 级定义了如下变动事件。
-
使用下列代码可以检测出浏览器是否支持变动事件:
var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");
IE8 及更早版本不支持任何变动事件。下表列出了不同浏览器对不同变动事件的支持情况。
由于 DOM3 级事件模块作废了很多变动事件,所以本节只介绍那些将来仍然会得到支持的事件。
2、删除节点
1.DOMNodeRemoved事件
:在使用 removeChild()或 replaceChild()从 DOM 中删除节点时,首先会触发 DOMNodeRemoved事件。这个事件的目标(event.target)是被删除的节点,而 event.relatedNode 属性中包含着对目标节点父节点的引用。
在这个事件触发时,节点尚未从其父节点删除,因此其 parentNode 属性仍然 指向父节点(与 event.relatedNode 相同)。这个事件会冒泡,因而可以在 DOM 的任何层次上面处理它。
DOMNodeRemovedFromDocument 事件
:如果被移除的节点包含子节点,那么在其所有子节点以及这个被移除的节点上会相继触发 DOMNodeRemovedFromDocument 事件【因为这些子节点也被移除了文档】。
但这个事件不会冒泡,所以只有直接指定给其中一个子节点的 事件处理程序才会被调用。这个事件的目标是相应的子节点或者那个被移除的节点,除此之外 event 对象中不包含其他信息。
DOMSubtreeModified 事件
:紧随其后触发的是 DOMSubtreeModified 事件。这个事件的目标是被移除节点的父节点;此时的 event 对象也不会提供与事件相关的其他信息。- 为了理解上述事件的触发过程,下面我们就以一个简单的 HTML 页面为例。
在这个例子中,我们假设要移除<ul>
元素。此时,就会依次触发以下事件。
(1) 在<ul>
元素上触发DOMNodeRemoved
事件。relatedNode 属性等于 document.body。
(2) 在<ul>
元素上触发DOMNodeRemovedFromDocument
事件。
(3) 在身为<ul>
元素子节点的每个<li>
元素及文本节点上触发DOMNodeRemovedFromDocument
事件。
(4) 在 document.body 上触发DOMSubtreeModified
事件,因为<ul>
元素是 document.body的直接子元素。
- 运行下列代码可以验证以上事件发生的顺序:
以上代码为 document 添加了针对 DOMSubtreeModified 和 DOMNodeRemoved 事件的处理程序, 以便在页面上处理这些事件。由于DOMNodeRemovedFromDocument 不会冒泡,所以我们将针对它的事件处理程序直接添加给了<ul>
元素的第一个子节点(在兼容 DOM 的浏览器中是一个文本节点)。在 置了以上事件处理程序后,代码从文档中移除了<ul>
元素。
3、插入节点
- 在使用 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
事件。