第17章 事件
第17章 事件
JavaScript与HTML的交互是通过事件来处理的。可以使用仅在事件发生时执行的listener(也叫handler)来订阅事件。该模型在传统软件工程中被称为“观察者模式”,它允许页面行为(用JavaScript定义)和页面外观(用HTML和CSS定义)之间的松耦合。
事件流
当开始开发第四代Web浏览器(IE 4和Netscape Communicator 4)时,浏览器开发团队遇到了一个有趣的问题:页面的哪一部分拥有特定事件?要理解该问题,请考虑在一张纸上形成一系列同心圆。当你将手指放在中心时,它不仅在纸上的一个圆圈内,而且在所有的圆圈内。两个开发团队都以相同的方式查看浏览器事件。他们得出结论,当你单击一个按钮时,你不仅在单击该按钮,而且还单击了它的容器以及整个页面。
事件流描述了页面上事件接收的顺序,有趣的是,IE和Netscape开发团队提出了几乎完全相反的事件流概念。 IE支持事件冒泡流程,而Netscape Communicator支持事件捕获流程。
事件冒泡
IE事件流称为事件冒泡,因为事件始于最具体的元素(文档树中可能的最深点),然后向上流向最不具体的节点(文档)。如下所示:
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
当点击页面上的<div>元素时,点击事件的顺序为:
<div>
<body>
<html>
document
点击事件首先在<div>上触发,这是被单击的元素。然后,单击事件沿DOM树上升,沿其路径触发每个节点,直到到达文档对象。如图所示:
所有现代浏览器都支持事件冒泡,尽管其实现方式有所不同。IE 5.5和更早版本会跳过<html>直接到达document,现代浏览器会继续将事件冒泡到window对象。
事件捕获
Netscape Communicator团队提出了另一种事件流,称为事件捕获。事件捕获的理论是,最不具体的节点(document)应首先接收事件,而最具体的节点应最后接收事件。事件捕获的真正目的是在事件到达预期目标之前对其进行拦截。如果将前面的示例用于事件捕获,点击<div>元素触发的点击事件顺序如下:
document
<html>
<body>
<div>
通过事件捕获,click事件首先被文档接收,然后继续沿DOM树向下移动到事件的实际目标:<div>元素。如图所示:
尽管这是Netscape Communicator唯一的事件流模型,但是所有现代浏览器都支持事件捕获。尽管DOM2事件规范指出事件应从文档开始,但实际上所有这些事件实际上都是在窗口级别开始的。
由于较旧的浏览器缺乏支持,因此通常不使用事件捕获。一般建议是自由使用事件冒泡,同时在特殊情况下保留事件捕获。
DOM事件流
DOM2事件指定的事件流具有三个阶段:捕获阶段,目标阶段,冒泡阶段。事件捕获首先发生,必要时提供拦截事件的机会。接下来,实际目标接收事件。最后阶段是冒泡,它允许对该事件做出最终响应。考虑到之前使用的简单HTML示例,点击<div>触发的事件顺序如下:
在DOM事件流中,实际目标(<div>元素)未在捕获阶段获取事件,这意味着捕获阶段从document到<html>再到<body>结束。下一个阶段是目标阶段,在<div>上触发。就事件处理而言,这被认为是冒泡阶段的一部分(稍后讨论)。然后,冒泡阶段发生,事件传播回文档。
大多数支持DOM事件流的浏览器都实现了一个怪癖。即使DOM2事件规范表明捕获阶段未达到事件目标,现代浏览器仍会在捕获阶段在目标上触发事件。最终结果是有两个机会可以在目标上处理事件。
事件处理程序
事件是用户或浏览器本身执行的某些操作。这些事件的名称包括单击、加载和鼠标悬停。响应事件而调用的函数称为事件处理程序(或事件侦听器)。事件处理程序的名称以“on”开头,因此click事件的事件处理程序称为 onclick,而load事件的事件处理程序称为onload。分配事件处理程序可以通过多种方式来完成。
HTML事件处理程序
可通过给每个支持事件的特定元素分配带有事件处理程序名称的HTML特性。该特性的值应该是一些要执行的JavaScript代码。例如,要在单击按钮时执行一些JavaScript,可使用如下代码:
<input type="button" value="Click Me" onclick="console.log('Clicked')"/>
单击此按钮时,将输出一条消息。通过指定onclick特性并分配一些JavaScript代码作为值来定义此交互。请注意,因为 JavaScript代码是一个属性值,所以在不转义的情况下,不能使用HTML语法字符 (例如,“&”号,双引号,小于或大于)。在这种情况下,为了避免使用HTML实体,使用了单引号而不是双引号。要使用双引号,可将代码更改为以下内容:
<input type="button" value="Click Me" onclick="console.log("Clicked")" />
HTML中定义的事件处理程序可以包含要执行的精确操作,也可以调用页面上其他位置定义的脚本。如下所示:
<script>
function showMessage() {
console.log("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
在这段代码中,单击按钮时将调用showMessage()。 showMessage()函数在单独的<script>元素中定义,当然也可以包含在外部文件中。作为事件处理程序执行的代码可以访问全局作用域内的所有内容。
以这种方式分配的事件处理程序具有一些独特的方面。首先,创建一个函数并将其包装为特性值。该函数有一个叫event的特殊局部变量,它是事件对象(在本章稍后讨论):
<input type="button" value="Click Me" onclick="console.log(event.type)">
<!-- 输出为:click -->
这样就可以访问事件对象,而无需自己定义它,也不需要将其从函数封装的参数列表中拿出。
命名函数内部的this值等于事件的目标元素 ,如以下示例所示:
<input type="button" value="Click Me" onclick="console.log(this.value)">
<!-- 输出为:Click Me -->
在这个动态创建的函数中,可以像访问局部变量一样访问文档的成员和元素本身。该函数通过使用with来扩展作用域链:
function() {
with(document) {
with(this) {
// 特性值
}
}
}
这意味着事件处理程序可以轻松访问自己的属性,下面的代码与上例功能相同:
<input type="button" value="Click Me" onclick="console.log(value)">
<!-- 输出:Click Me -->
如果元素是表单输入元素,则作用域链还包含父表单元素的条目,从而使该函数等效于以下内容:
function() {
with(document) {
with(this.form) {
with(this) {
// 特性值
}
}
}
}
这种扩充使事件处理程序代码可以访问相同表单的其他成员 ,而无需引用表单元素本身。下面的示例演示了这种成员访问模式:
<form method="post">
<input type="text" name="username" value="">
<input type="button" value="输出用户名" onclick="console.log(username.value)">
</form>
在此示例中,单击按钮将显示来自文本框的文本。请注意,它只是直接引用username。
在HTML中分配事件处理程序有一些弊端。首先是时间问题:在事件处理程序代码准备就绪之前,HTML元素可能会出现在页面上并与用户进行交互。在上一个示例中,一种可能的情况发生在事件处理代码准备好之前HTML元素就出现在页面上并与用户发生交互。如果用户在定义showMessage()之前单击按钮,则会发生错误。因此,大多数HTML事件处理程序都包含在try-catch块中,让它们悄然失败,如以下示例所示:
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex) {}">
另一个缺点是事件处理程序函数中作用域链的扩展可能导致在不同浏览器中产生不同的结果。JavaScript引擎之间遵循的标识符解析规则略有不同,因此访问不合格对象成员的结果可能会导致错误。
使用HTML分配事件处理程序的最后一个缺点是,它将HTML与JavaScript紧密耦合。如果需要更改事件处理程序,则可能需要在两个位置更改代码:HTML和JavaScript。这是许多开发人员避免使用HTML事件处理程序,而喜欢使用JavaScript分配事件处理程序的主要原因。
DOM0事件处理程序
在JavaScript中分配事件处理程序的传统方式是将函数分配给事件处理程序属性。这是第四代Web浏览器中引入的事件处理程序分配方法,由于其简单性和跨浏览器支持,它仍然保留在所有现代浏览器中。要使 JavaScript分配事件处理程序,必须先获取对要执行操作的对象的引用。
每个元素(以及window和document)都具有事件处理程序属性,这些属性通常都是小写的,例如onclick。通过将属性设置为等于函数来分配事件处理程序,如以下示例所示:
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log("Clicked");
};
在这里,从文档中获取一个按钮,并分配了一个onclick事件处理程序。请注意,事件处理程序需等待此代码运行后才能分配,因此,如果该代码显示在页面中按钮的代码之后,则可能会在一段时间内单击按钮将不执行任何操作。
使用DOM0分配事件处理程序时,事件处理程序被视为元素的方法 。因此,事件处理程序在element的作用域内运行,这意味着this值等于element。如下所示:
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log(this.id); // "myBtn"
};
单击按钮后,此代码将显示元素的ID。使用this.id获取ID。可以使用this从事件处理程序中访问元素的任何属性或方法。 以这种方式添加的事件处理程序旨在用于事件流的冒泡阶段 。
可以通过将事件处理程序属性的值设置为null来删除通过DOM0分配的事件处理程序,如下所示:
btn.onclick = null; // 移除事件处理程序
一旦将事件处理程序设置为null后,单击该按钮后将不再执行任何操作。
注意:如果使用HTML分配了事件处理程序,则onclick属性上的值是一个函数,其中包含HTML特性中指定的代码。也可以通过将属性设置为null来删除这些事件处理程序。
DOM2事件处理程序
DOM2事件定义了两种方法来处理事件处理程序的分配和删除:addEventListener()和removeEventListener()。这些方法存在于所有DOM节点上,并接受三个参数:要处理的事件名称,事件处理函数和一个布尔值,指示是在捕获阶段(true)还是在冒泡阶段(false)调用事件处理程序。
要为按钮上的click事件添加事件处理程序,可以使用以下代码:
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this); // 箭头函数定义handler,则this为window
}, false);
这段代码将onclick事件处理程序添加到将在冒泡阶段触发的按钮(因为最后一个参数为false)。与DOM0添加的方法一样,事件处理程序在其附加的元素的作用域内运行。使用DOM2方法添加事件处理程序的主要优点是可以添加多个事件处理程序:
let btn = document.getElementById("myBtn");
btn.addEventListener("click", function() {
console.log(this.id); // myBtn 需使用命名函数
}, false);
btn.addEventListener("click", () => {
console.log("Hello world!");
}, false);
事件处理程序将按添加顺序触发,因此第一个输出元素的ID,第二个输出消息“Hello world!”。
通过addEventListener()添加的事件处理程序只能通过使用removeEventListener()并传入与添加处理程序时相同的参数来删除。这意味着无法删除使用addEventListener()添加的匿名函数:
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);
// 其他代码
btn.removeEventListener("click", function() { // 不管用!
console.log(this.id);
}, false);
在此示例中,使用addEventListener()将匿名函数添加为事件处理程序。对removeEventListener()的调用似乎相同的参数,但实际上,第二个参数与addEventListener()中使用的参数完全不同。传递给removeEventListener()的事件处理函数必须与addEventListener()中使用的事件处理函数相同:
let btn = document.getElementById("myBtn");
let handler = function() {
console.log(this.id);
};
btn.addEventListener("click", handler, false);
// 其他代码
btn.removeEventListener("click", handler, false); // 正常
这个重写的示例按预期方式工作,因为addEventListener()和removeEventListener()使用了相同的函数。
在大多数情况下,事件处理程序会添加到事件流的冒泡阶段,因为这提供了最广泛的跨浏览器支持。如果需要在事件到达目标阶段之前对其进行拦截,则最好在捕获阶段附加事件处理程序。如果没有必要,建议避免事件捕获。
跨浏览器事件处理程序
为了以跨浏览器的方式处理事件,许多开发人员最终要么使用JavaScript库来抽象化浏览器差异,要么编写自定义代码以使用最合适的事件处理方法。为确保事件处理代码以更加兼容的方式工作,需要将它放在冒泡阶段。
创建的第一种方法称为addHandler(),其工作是使用DOM0方法、DOM2方法或IE方法来添加事件,具体取决于可用的方法。该方法附加到名为EventUtil的对象上,在本章中将使用该对象来帮助处理跨浏览器的差异。 addHandler()方法接受三个参数:要作用的元素,事件的名称和事件处理函数。
addHandler()的对应项是removeHandler(),它接受相同的三个参数。此方法的工作是使用任何可用的方法删除以前添加的事件处理程序,如果没有其他方法可用,则默认为DOM0。
下面是EventUtil的完整代码:
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
两种方法都先检查传入的元素上是否存在DOM2方法。如果存在DOM2方法,则用之,并传入事件类型和事件处理函数,以及第三个参数:false(指示冒泡阶段)。如果IE方法可用,它将用作第二个选项。请注意,事件类型必须以“on”为前缀,才能在IE8及更早版本中工作。最后的方法是使用DOM0方法(在现代浏览器中,代码永远不会到达此处)。请注意使用括号表示法将属性名称分配给事件处理程序或null。
utility对象用法如下:
let btn = document.getElementById("myBtn");
let handler = function() {
console.log("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
// 其他代码
EventUtil.removeHandler(btn, "click", handler);
addHandler()和removeHandler()方法不能使所有浏览器的所有功能均等化,例如IE作用域问题,但它们确实允许无缝添加和删除事件处理程序。同样要记住,对DOM0的支持仅限于每个事件一个事件处理程序。幸运的是,浏览器不再使用DOM0,因此没有影响。
Event对象
当触发与DOM相关的事件时,所有相关信息都会收集并存储在event对象上。如触发事件的元素,发生的事件的类型以及与该特定事件可能相关的任何其他数据;由鼠标操作引起的事件生成有关鼠标位置的信息,而由键盘操作引起的事件生成有关keyDown的信息。所有浏览器都支持事件对象,但方式不同。
DOM事件对象
在兼容DOM的浏览器中,event对象作为唯一的参数传递给事件处理程序。不管方法用于赋给事件处理程序、DOM0还是DOM2,都会传入event对象。以下示例说明了在处理程序内部引用event对象的两种示例:
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.type); // "click"
};
btn.addEventListener("click", (event) => {
console.log(event.type); // "click"
}, false);
在此示例中,两个事件处理程序都打印一条消息,指示使用event.type属性触发的事件类型。此属性始终包含触发的事件类型,例如“click”(与传递给addEventListener()和removeEventListener()的值相同)。
当事件处理程序使用HTML特性来赋值时,event对象作为一个叫event的变量使用:
<input type="button" value="Click Me" onclick="console.log(event.type)">
以这种方式提供的event对象可以使HTML特性事件处理程序执行与JavaScript函数相同的操作。
event对象包含与特定事件相关的属性和方法。可用的属性和方法因触发的事件类型而异,但是event所有的成员均在下表中列出:
属性或方法 | 类型 | 读/写 | 描述 |
---|---|---|---|
bubbles | Boolean | 读 | 指示是否是冒泡事件 |
cancelable | Boolean | 读 | 指示是否可以取消事件的默认行为 |
currentTarget | Element | 读 | 事件绑定的元素 |
defaultPrevented | Boolean | 读 | (在DOM3事件中添加) |
detail | Integer | 读 | 与事件有关的额外信息 |
eventPhase | Integer | 读 | 调用事件处理程序的阶段:1表示捕获阶段,2表示目标阶段,3表示冒泡阶段 |
preventDefault() | Function | 读 | 取消事件的默认行为。如果cancelable为true,则可以使用此方法 |
stopImmediatePropagation() | Function | 读 | 立即取消进一步的事件捕获或事件冒泡,并阻止任何其他事件处理程序被调用。(已在DOM3事件中添加。) |
stopPropagation() | Function | 读 | 取消进一步的事件捕获或事件冒泡。 |
target | Element | 读 | 事件触发的元素 |
trusted | Boolean | 读 | 如果为true,则指示事件是否由浏览器生成。如果为false,则表示事件是开发人员使用JavaScript创建的。(已在DOM3事件中添加。) |
type | String | 读 | 触发的事件类型 |
View | AbstractView | 读 | 与事件关联的抽象视图。当事件发生时等于window对象 |
在事件处理程序内部,this对象始终等于currentTarget的值,而target仅包含事件的实际目标。如果将事件处理程序直接赋给预期的目标,则this,currentTarget和target都具有相同的值。这是一个演示两个属性与this等效的示例:
// <button id="myBtn">测试</button>
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.currentTarget === this); // true
console.log(event.target === this); // true
};
此代码检查与this相对的currentTarget和target的值。因为单击事件的目标是按钮,所以这三个都相等。如果事件处理程序存在于按钮的父节点(例如document.body)上,则值将不同。考虑以下示例,其中在document.body上设置了单击处理程序:
document.body.onclick = function(event) {
console.log(event.currentTarget === document.body); // true
console.log(this === document.body); // true
console.log(event.target === document.getElementById("myBtn")); // true 仅点击相应
按钮才为true
};
在此示例中,单击按钮时,this和currentTarget都等于document.body,因为这是事件处理程序注册的地方。但是target属性等于button元素本身,因为那是click事件的真正目标。由于按钮本身未分配事件处理程序,因此单击事件会冒泡至处理该事件的document.body。
当需要分配一个函数来处理多个事件时,type属性非常有用。这是使用event.type的示例:
let btn = document.getElementById("myBtn");
let handler = function(event) {
switch (event.type) {
case "click":
console.log("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
在此例中,定义了一个叫handler的函数来处理三个不同的事件:单击,鼠标悬停和鼠标移出。单击该按钮时,它应该输出一条消息,如前面的示例中所示。当鼠标移到按钮上时,背景色应变为红色,而当鼠标离开按钮时,背景色应恢复为默认值。使用event.type属性,该函数能够确定发生了哪个事件,然后做出适当的反应。
preventDefault()方法用于防止特定事件的默认操作。例如,链接的默认行为是单击时导航到其href特性中指定的URL。如果要防止发生这种导航,则onclick事件处理程序可以取消该行为,如下所示:
<a id="myLink" href="http://www.bilibili.com">B站</a>
<script>
let link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};
</script>
使用preventDefault()取消的任何事件都将其cancelable属性设置为true。
stopPropagation()方法立即停止通过DOM结构的事件流 ,在事件发生之前取消任何其他事件捕获或事件冒泡。例如,直接添加到按钮上的事件处理程序可以调用stopPropagation()来防止触发document.body上的事件处理程序,如下所示:
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log("Clicked");
event.stopPropagation(); // 点击按钮不会输出Body clicked
};
document.body.onclick = function(event) {
console.log("Body clicked");
};
在此例中,如果没有调用stopPropagation(),则单击该按钮时将输出两条消息。但是,click事件永远不会到达document.body,因此onclick事件处理程序永远不会执行。
eventPhase属性有助于确定当前事件流处于哪个阶段。如果在捕获阶段调用了事件处理程序,则eventPhase为 1 ;如果事件处理程序位于目标阶段,则eventPhase为 2 ;如果事件处理程序处于冒泡阶段,则eventPhase为 3 。以下示例显示了各个eventPhase值:
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.eventPhase); // 2
};
document.body.addEventListener("click", (event) => {
console.log(event.eventPhase); // 1
}, true);
document.body.onclick = (event) => {
console.log(event.eventPhase); // 3
};
单击此例中的按钮时,触发的第一个事件处理程序是在捕获阶段的document.body上。该阶段输出的eventPhase为 1 。接下来,触发按钮身上的事件处理程序,此时eventPhase为 2 。最后一个触发的事件处理程序在eventPhase为 3 时是在document.body的冒泡阶段。只要eventPhase为 2 ,则this、target和currentTarget始终相等。
注意:event对象仅在事件处理程序仍在执行时才存在;一旦所有事件处理程序都已执行,event对象将被销毁。
跨浏览器event对象
尽管DOM和IE的event对象不同,但有足够的相似性以允许跨浏览器的解决方案。IE event对象的所有信息和功能都以不同的形式存在于DOM对象中。这些并行操作使从一个事件模型到另一个事件模型的轻松映射成为可能。前面描述的EventUtil对象可以使用弥补差异的方法进行扩充:
var EventUtil = {
addHandler: function(element, type, handler) {
// 其他代码
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler) {
// 其他代码
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
在此代码中,EventUtil中添加了四个新方法。第一个是getEvent(),它返回对event对象的引用。由于IE中event对象的位置不同,因此,无论使用哪种事件处理程序赋与方式,都可以使用此方法来获取事件对象。要使用此方法,必须假定event对象已传递到事件处理程序并将该变量传递给方法。这是一个使用EventUtil标准化event对象的示例:
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
};
在兼容DOM的浏览器中使用时,event变量将直接传递并返回。在IE中,event参数将是undefined,因此将返回window.event。将此行添加到事件处理程序的开头可确保event对象始终可用,无论使用何种浏览器。
第二个方法是getTarget(),它返回事件的目标。在方法内部,它检查event对象以查看目标属性是否可用,如果有则返回其值;否则,将使用srcElement属性。此方法可以按如下方式使用:
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
let target = EventUtil.getTarget(event);
};
第三个方法是preventDefault(),它停止事件的默认行为。传入事件对象后,将检查是否可以使用preventDefault()方法,如果可以,请调用该方法。如果preventDefault()不可用,则该方法将returnValue设置为false,如以下示例所示:
let link = document.getElementById("myLink");
link.onclick = function(event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
};
此代码可阻止点击链接时在所有主要浏览器中导航到另一个页面。首先使用EventUtil.getEvent()获取event对象,然后将其传递到EventUtil.preventDefault()停止默认行为。
第四个方法stopPropagation()以类似的方式工作。它首先尝试使用DOM方法停止事件流,并在必要时使用cancelBubble。这是一个如何使用stopPropagation()的示例:
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log("Clicked");
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
};
document.body.onclick = function(event) {
console.log("Body clicked");
};
在这里,使用EventUtil.getEvent()获取event对象,然后将其传递到EventUtil.stopPropagation()。请记住,此方法可能会停止事件冒泡,也可能会停止事件冒泡和捕获,具体取决于浏览器。
事件类型
Web浏览器中可能发生多种类型的事件。如前所述,要触发的事件类型决定了有关该事件的可用信息。DOM3事件指定以下事件组:
用户界面(UI)事件 是一般的浏览器事件,可能与BOM有一些交互。
当元素获得或失去焦点时会触发 焦点事件 。
当使用鼠标在页面上执行操作时,将触发 鼠标事件 。
使用鼠标滚轮(或类似设备)时会触发 滚轮事件 。
将文本输入到文档中时,将触发 文本事件 。
当使用键盘在页面上执行操作时,会触发 键盘事件 。
为输入法编辑器(IME)输入字符时,将触发 合成事件 。
除了这些类别之外,HTML5还定义了另一组事件,并且浏览器经常在DOM和BOM上实现专有事件。这些专有事件通常是由开发人员的需求而不是规范驱动的,因此在浏览器中实现的方式可能有所不同。
DOM3事件从DOM2事件重新定义了事件分组,并添加了其他事件定义。所有主要的浏览器都支持DOM2和3事件。
UI事件
UI事件是那些不一定与用户操作有关的事件。这些事件在DOM规范之前以某种形式存在,并且为了向后兼容而保留。 UI事件如下:
DOMActivate 当元素已通过某些用户操作(通过鼠标或键盘)激活时触发。 DOM3事件中不推荐使用此事件。由于跨浏览器的实现差异,请避免使用此事件。
load 当页面完全加载时在window上触发,或在所有帧都已完全加载时在框架集上触发,或图片已完全加载时在<img>元素上触发,或在<object>元素完全加载时触发。
unload 当页面完全卸载后在window上触发,或所有帧完全卸载后在框架集上触发,或在<object>元素完全卸载时触发。
abort 在用户停止下载<object>元素且未完全加载时触发。
error 发生JavaScript错误时在window上触发,或图片不能加载时在<img>元素上触发,或在<object>元素不能加载时触发,或在框架集上触发(如果无法加载一个或多个框架)。
select 当用户在文本框中选择一个或多个字符时触发(<input>或<textarea>)。
resize window 或框架调整大小后触发。
scroll 用户滚动带有滚动条的任何元素时触发。<body>元素包含已加载页面的滚动条。
大多数HTML事件与窗口对象或窗体控件有关。
load事件
load事件可能是JavaScript中最常用的事件。对于window对象,当加载整个页面(包括所有外部资源,例如图像,JavaScript文件和CSS文件)时,将触发load事件。可以通过两种方式定义onload事件处理程序。首先是使用JavaScript,如下所示:
window.addEventListener("load", (event) => {
console.log("Loaded!");
});
分配onload事件处理程序的第二种方法是将onload特性添加到<body>元素,如以下示例所示:
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload="console.log('Loaded!')">
</body>
</html>
一般而言,可以通过<body>元素上的特性来分配发生在window上的任何事件,因为无法在HTML中访问window元素。这确实是一种向后兼容的技术,在所有浏览器中得到很好的支持。但尽可能使用JavaScript方法。
注意:根据DOM2事件,应该在document而非window上触发load事件。但是,为了向后兼容,在所有浏览器都在window上实现了onload。
load事件也会在图像上触发,包括DOM中的图像和非DOM中的图像。可以使用以下代码在文档中的任何图像上直接使用HTML来分配onload事件处理程序:
<img src="images/smile.gif" onload="console.log('Image loaded.')">
此示例在加载给定图像时输出一条消息。也可以使用JavaScript完成此操作,如下所示:
let image = document.getElementById("myImage");
image.addEventListener("load", (event) => {
console.log(event.target.src);
});
在这里,onload事件处理程序是使用JavaScript分配的。尽管没有太多有用的信息,事件对象还是被传入了。事件的目标是<img>元素,因此可以访问和显示其src属性。
创建新的<img>元素时,可以分配事件处理程序以指示何时加载图像。在这种情况下,请务必先分配事件,然后再分配src属性,如以下示例所示:
window.addEventListener("load", () => {
let image = document.createElement("img");
image.addEventListener("load", (event) => {
console.log(event.target.src);
//http://127.0.0.1:8848/WWW/PRO%20Javascript/images/smile.gif
});
document.body.appendChild(image);
image.src = "images/smile.gif";
});
此示例的第一部分是为window分配onload事件处理程序。由于该示例涉及向DOM中添加新元素,因此必须确定页面已加载,因为在完全加载document.body之前尝试对其进行操作可能会导致错误。创建一个新的图像元素,并设置其onload事件处理程序。然后,将图像添加到页面并为其分配src。请注意,无需将元素添加到文档中就可以开始下载图像。一旦设置了src属性,它就会开始。
DOM0 Image对象可以使用相同的技术。在DOM之前,Image对象用于在客户端上预加载图像。可与<img>元素相同的方式使用它,但不能将其添加到DOM树中。如下所示,该示例实例化一个新的Image对象:
window.addEventListener("load", () => {
let image = new Image();
image.addEventListener("load", (event) => {
console.log("Image loaded!");
});
image.src = "images/smile.gif";
});
在这里,使用Image构造函数创建一个新图像,并分配事件处理程序。某些浏览器将Image对象实现为<img>元素,但不是全部都已实现,因此最好将它们分开对待。
还有其他元素也以非标准方式支持load事件。<script>元素触发load事件,让你确定动态加载的JavaScript文件何时已完全加载。与图像不同,JavaScript文件仅在分配了src属性并将元素添加到文档后才开始下载,因此事件处理程序和src属性的分配顺序无关紧要。如下所示:
window.addEventListener("load", () => {
let script = document.createElement("script");
script.addEventListener("load", (event) => {
console.log("Loaded");
});
script.src = "example.js";
document.body.appendChild(script);
});
在大多数浏览器中event对象的target是<script>,IE8之前不支持。
IE和Opera支持<link>元素的load事件,可以确定何时加载样式单。以下示例显示如何设置此事件的侦听器:
window.addEventListener("load", () => {
let link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.addEventListener("load", (event) => {
console.log("css loaded");
});
link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
同<script>一样,在分配了href属性并将<link>元素添加到文档之前,样式表不会开始下载。
unload事件
作为load事件的伙伴方法,当文档完全卸载后,将触发unload事件。从一页导航到另一页时,通常会触发unload事件,并且最常用于清除引用以避免内存泄漏。
与load事件类似,可以通过两种方式分配onunload事件处理程序。首先是使用JavaScript,如下所示:
window.addEventListener("unload", (event) => {
console.log(event.target); //#document 页面跳转时
});
event对象是为此事件生成的,但仅包含与DOM兼容的浏览器的target(设置为document)。
类似于load事件,分配onunload事件处理程序的第二种方法是将一个特性添加到<body>元素,如本示例所示:
<!DOCTYPE html>
<html>
<head>
<title>Unload Event Example</title>
</head>
<body onunload="console.log('Unloaded!')">
</body>
</html>
无论使用哪种方法,都请注意在onunload事件处理程序内部执行的代码。由于卸载事件在所有内容卸载后触发,尝试操纵DOM节点的位置或其外观可能会导致错误。
注意:根据DOM2事件,应该在<body>而非window上触发unload事件。但是,为了向后兼容,在所有浏览器的window中都实现了unload。
resize事件
将浏览器窗口调整为新的高度或宽度时,将触发resize事件。此事件在window上触发,因此可以通过JavaScript或使用<body>元素上的onresize特性来分配事件处理程序。首选使用JavaScript方法,如下所示:
window.addEventListener("resize", (event) => {
console.log(event.target); //Window
});
与window上发生的其他事件类似,event对象被创建,并且其target是DOM兼容的浏览器中的document(测试为window),而IE8和更早版本不提供使用属性。
关于跨浏览器何时触发resize事件,存在一些重要的区别。浏览器调整即使只有一个像素,IE,Safari,Chrome和Opera都会触发resize事件,然后在用户调整浏览器窗口的大小时反复触发。 Firefox仅在用户停止调整浏览器大小之后才触发resize事件。由于存在这些差异,因此应避免在事件处理程序中使用代码来频繁的处理此事件,因为该代码将被频繁执行并在浏览器中引起明显的运行缓慢。
注意:当最小化或最大化浏览器窗口时,也会触发resize事件。
scroll事件
虽然滚动事件发生在window上,它实际上是指相应页面级元素中的更改。在怪癖模式下,使用<body>元素的scrollLeft和scrollTop可以观察到更改;在标准模式下,更改发生在除Safari(safari为<body>上)以外的所有浏览器的<html>元素上:
window.addEventListener("scroll", (event) => {
if (document.compatMode == "CSS1Compat") {
console.log(document.documentElement.scrollTop);
} else {
console.log(document.body.scrollTop);
}
});
这段代码分配了一个事件处理程序,该事件处理程序根据渲染模式输出页面的垂直滚动位置。由于3.1之前的Safari版本不支持document.compatMode,因此旧版本会导致第二种情况。
同resize事件一样,scroll事件会在文档滚动时反复发生,因此最好使事件处理程序尽可能简单。
焦点事件
当页面的元素接收或失去焦点时,将触发焦点事件。这些事件与document.hasFocus()和document.activeElement属性协同工作,以洞悉用户如何浏览页面。有六个焦点事件:
focus 当元素获得焦点时触发 此事件不会冒泡 ,并且在所有浏览器中都支持。
blur 当元素失去焦点时触发。 此事件不会冒泡 ,并且在所有浏览器中都支持。
focusin 当元素获得焦点时触发。这是HTML事件focus的冒泡版本。
focusout 当元素失去焦点时触发。这是HTML事件blur的通用版本。
DOMFocusIn 当元素获得焦点时触发。这是HTML事件focus的冒泡版本。 Opera是唯一支持此事件的主要浏览器。 DOM3事件不赞成使用DOMFocusIn,而使用focusin。
DOMFocusOut 当元素失去焦点时触发。这是HTML事件blur的通用版本。 Opera是唯一支持此事件的主要浏览器。 DOM3级事件不赞成使用DOMFocusOut,而推荐使用focusout。
该组的两个主要事件是focus和blur,自JavaScript诞生以来,浏览器就都支持focus和blur。这些事件的最大问题之一是它们不会冒泡。这导致IE和包含了focusin和focusout,Opera包含DOMFocusIn和DOMFocusOut。 IE的方法已在DOM3事件中标准化。
当焦点从页面上的一个元素移到另一个元素时,事件的顺序如下:
元素失去焦点触发focusout
元素获得焦点触发focusin
元素失去焦点触发blur
元素获得焦点触发focus
元素失去焦点触发DOMFocusOut
元素获得焦点触发DOMFocusIn
blur,DOMFocusOut和focusout的事件目标是失去焦点的元素,而focus,DOMFocusIn和focusin的事件目标是获得焦点的元素。
鼠标和滚轮事件
鼠标事件是Web上最常用的事件组,因为鼠标是主要的导航设备。在DOM3事件中定义了九个鼠标事件:
click 当用户单击鼠标主键(通常为左键)或用户按Enter键时触发。可以使用键盘和鼠标执行
onclick 事件处理程序。
dblclick 当用户双击鼠标主键(通常为左键)时触发。此事件未在DOM2事件中定义,但得到了很好的支持,因此在DOM3事件中已标准化。
mousedown 用户按下任何鼠标按键时触发。无法通过键盘触发此事件。
mouseenter 当鼠标光标位于元素外部然后将其移动到元素(或后代元素)边界内时触发。 当光标在后代元素间或后代与父元素间移动时,此事件不会冒泡,也不会触发 。 mouseenter事件未在DOM2事件中定义,但已在DOM3事件中添加。
<div id="p" style="background-color:red;width:7.5rem;height:4.5rem;margin-left:
3.75rem;"
onmouseenter="console.log('parent enter')">
<div id="c" style="background-color: greenyellow;width: 4.5rem;height: 7.5rem;">
</div>
</div>
mouseleave 当鼠标光标悬停在某个元素(或其子元素)上,然后将其移动到该元素(及子元素)的边界之外时触发。当光标在后代元素间或后代与父元素间移出时,此事件不会冒泡,也不会触发。 mouseleave事件未在DOM2事件中定义,但已在DOM3事件中添加。
mousemove 在光标围绕元素移动时重复触发。无法通过键盘触发此事件。
mouseout 当鼠标光标悬停在某个元素上然后用户将其移动到另一个元素上时触发。 移出子元素,子元素到父元素,父元素到子元素都可以触发该事件 。无法通过键盘触发此事件。
mouseover 当鼠标光标位于元素外部然后将其移动到元素边界内时触发。移动到子元素,子元素到父元素,父元素到子元素都可以触发该事件。无法通过键盘触发此事件。
mouseup 用户释放鼠标按钮时触发。无法通过键盘触发此事件。
页面上的所有元素都支持鼠标事件。除mouseenter和mouseleave之外的所有鼠标事件都冒泡,并且它们都可以取消,这会影响浏览器的默认行为。
由于事件之间存在关系,取消鼠标事件的默认行为也会影响其他事件。
仅当在同一元素上触发mousedown事件并随后触发mouseup事件时,才可以触发click事件。如果取消mousedown或mouseup,则不会触发click事件。同样,它需要两个click事件来引发dblclick事件。如果有什么阻止这两个click事件触发(取消其中一个单击事件或取消mousedown或mouseup),则dblclick事件将不会触发。
这四个鼠标事件始终按以下顺序触发:
mousedown
mouseup
click
mousedown
mouseup
click
dblclick
click和dblclick都依赖其他事件才能触发,而mousedown和mouseup不受其他事件的影响。
还有一个鼠标事件子集,称为滚轮事件。 滚轮事件实际上只是一个事件,mousewheel,它监视鼠标滚轮或类似设备(如Mac触控板)的交互。
客户端坐标
鼠标事件都发生在浏览器视口内的特定位置。此信息存储在event对象的clientX和clientY属性中。这些属性指示事件发生时鼠标光标在视口中的位置,并且被所有浏览器支持。如图所示:
可以通过以下方式获取鼠标事件的客户端坐标:
<div id="myDiv" style="width: 100%;height: 1800px;">div</div>
<script>
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`客户端坐标: ${event.clientX}, ${event.clientY}`);
});
</script>
这些坐标不考虑页面的滚动位置,因此这些数字不代表光标在页面上的位置。
页面坐标
客户坐标提供有关事件在视口中何处发生的信息,页面坐标通过event对象的pageX和pageY属性告知事件在页面上的何处。这些属性指示鼠标光标在页面上的位置,因此坐标从页面本身的左上角开始算起,而不是视口。
可以通过以下方式获取鼠标事件的页面坐标:
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`页面坐标: ${event.pageX}, ${event.pageY}`);
});
当页面不滚动时,pageX和pageY的值与clientX和clientY相同。
屏幕坐标
鼠标事件不仅与浏览器窗口有关,而且与整个屏幕有关。通过使用screenX和screenY属性,可以确定鼠标相对于整个屏幕的位置。如图所示:
可以通过以下方式获取鼠标事件的屏幕坐标:
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`屏幕坐标: ${event.screenX}, ${event.screenY}`);
// 仅能获取div范围内的屏幕坐标
});
修正键
即使鼠标事件主要是通过使用鼠标触发的,某些键盘键的状态对于确定要采取的操作也可能很重要。修正键Shift,Ctrl,Alt和Meta(win键)通常用于更改鼠标事件的行为。DOM指定四个属性来指示这些修正键的状态:shiftKey,ctrlKey,altKey和metaKey。这些属性中的每一个都包含一个布尔值,如果按住该键,则将其设置为true;如果未按下该键,则将其设置为false。发生鼠标事件时,可以通过检查这些属性来确定各种键的状态。如下示例,该示例在触发click事件时检查修正键状态:
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
let keys = new Array();
if (event.shiftKey) {
keys.push("shift");
}
if (event.ctrlKey) {
keys.push("ctrl");
}
if (event.altKey) {
keys.push("alt");
}
if (event.metaKey) {
keys.push("meta");
}
console.log("Keys: " + keys.join(","));
// 每次点击可同时输出四个键(需同时按住)
});
相关元素
对于mouseover和mouseout事件,还有其他与这些事件相关的元素。这两个事件都涉及将鼠标光标从一个元素的边界内移动到另一元素的边界内。 对于mouseover事件,该事件的主要目标是获得光标的元素,而相关元素是失去光标的元素。同样,对于mouseout,主要目标是丢失光标的元素,而相关元素是获得光标的元素 。如下所示:
<!DOCTYPE html>
<html>
<head>
<title>Related Elements Example</title>
</head>
<body>
<div id="myDiv" style="background-color:red;height:100px;width:100px;"></div>
</body>
</html>
此页面只渲染单个<div>,如果鼠标光标从<div>上移到外面,将在<div>上触发mouseout事件,其相关元素是<body>元素;同理,如果鼠标光标从外面移动到<div>上,将在<body>上触发mouseover事件,其相关元素为<div>。
DOM通过event对象上的relatedTarget属性提供有关相关元素的信息。该属性仅包含mouseover和mouseout事件的值。对于其他事件此值为null。IE8和更早版本不支持relatedTarget属性,但可以使用其他属性提供对相关元素的类似访问。当mouseover事件触发时,IE提供一个fromElement属性,该属性包含相关元素。当mouseout事件触发时,IE提供一个包含相关元素的toElement属性(IE9支持所有属性)。可以将跨浏览器方法获取相关元素添加到EventUtil中,如下所示:
var EventUtil = {
// ...
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget;
} else if (event.toElement) {
return event.toElement;
} else if (event.fromElement) {
return event.fromElement;
} else {
return null;
}
},
//...
}
EventUtil.getRelatedTarget()用法如下:
let div = document.getElementById("myDiv");
div.addEventListener("mouseout", (event) => {
let target = event.target;
let relatedTarget = EventUtil.getRelatedTarget(event);
console.log(
`Moused out of ${target.tagName} to ${relatedTarget.tagName}`);
});
按钮
仅当在元素上单击主鼠标按键时(或在键盘上按下Enter键时),才触发click事件,因此不需要按钮信息。对于mousedown和mouseup事件,事件对象上有一个button属性,该属性指示按下或释放的按钮。 DOM button属性具有以下三个可能的值:主鼠标按钮为 0 ,鼠标中键(通常是滚轮按钮)为 1 ,辅助鼠标按钮为 2 。在传统设置中,鼠标的主要按钮是鼠标左键,辅助的按钮是鼠标右键。
其他事件信息
DOM2事件规范提供了event对象的detail属性,以提供有关事件的其他信息。对于鼠标事件,detail包含一个数字,该数字指示在给定位置单击了多少次。单击被认为是在相同像素位置发生的mousedown事件,然后是mouseup事件。detail的值从 1 开始,并在每次单击时增加。如果将鼠标在mousedown和mouseup之间移动,则detail将设置回 0 。
鼠标滚轮事件
IE 6首先实现了mousewheel事件。从那时起,Opera,Chrome和Safari就开始使用它。当用户与鼠标滚轮互动时,mousewheel事件会触发。此事件在每个元素上触发,并在文档(在IE8中)和最新浏览器上的窗口中冒泡。 mousewheel事件的event对象包含有关鼠标事件的所有标准信息以及一个名为wheelDelta的附加属性。
当鼠标滚轮向鼠标前方滚动时,wheelDelta是 120 的正数倍;当鼠标滚轮向鼠标后部滚动时,wheelDelta是 120 的负整数倍。
可以将onmousewheel事件处理程序分配给页面或文档上的任何元素,以处理所有鼠标滚轮交互。如下所示:
document.addEventListener("mousewheel", (event) => {
console.log(event.wheelDelta); // 向前+120 向后-120
});
注意:mousewheel事件已添加到HTML5中,以反映其在大多数浏览器中的流行性和可用性。
触控设备支持
运行iOS或Android的触摸设备的实现方式非常有趣,因为当然没有鼠标可以与之交互。在开发触摸设备时,请记住以下几点:
完全不支持dblclick事件。双击浏览器窗口将放大,并且无法覆盖该行为。
轻击可点击元素会触发mousemove事件。如果此操作导致内容更改,则不会触发其他事件;如果屏幕没有变化,则按顺序触发mousedown,mouseup和click事件。点击不可点击的元素时,不会触发任何事件。可点击元素定义为在被点击时具有默认操作的元素(例如链接)或分配了onclick事件处理程序的元素。
mousemove事件还会触发mouseover和mouseout事件。
当两个手指在屏幕上并且由于手指移动而滚动页面时,将触发mousewheel事件和scroll事件。
辅助功能问题
如果残障用户(尤其是使用屏幕阅读器的用户)必须能够访问您的Web应用程序或网站,则在使用鼠标事件时应格外小心。如前所述,可以使用键盘上的Enter键来触发click事件,但是其他鼠标事件不支持键盘。以下是使用鼠标事件进行可访问性的一些技巧:
使用click执行代码。有些人建议使用onmousedown执行代码时,应用程序感觉更快,这对有视力的用户是正确的。但是,对于屏幕阅读器,此代码不可访问,因为无法触发mousedown事件。
避免使用onmouseover向用户显示新选项。同样,屏幕阅读器无法触发此事件。如果确实必须以这种方式显示新选项,请考虑添加键盘快捷键以显示相同的信息。
避免使用dblclick执行重要操作。键盘无法触发此事件。
键盘和文本事件
当用户与键盘交互时,将触发键盘事件。 DOM2事件最初指定了键盘事件,但是在规范最终确定之前被删除。结果,基于原始DOM0的实现在很大程度上支持键盘事件。
DOM3事件提供了有关键盘事件的规范,该规范最初在IE9中完全实现。其他浏览器也已开始着手实现该标准,但是仍然有许多旧版实现。
共有三个键盘事件,如下所述:
keydown 用户按下键盘上的某个键时触发,并在按住该键时重复触发。
keypress 当用户按下键盘上的一个键并产生一个字符并在按住该键时反复触发时触发。Esc键也会触发此事件。DOM3事件不赞成使用keypress事件,而推荐使用textInput事件。
keyup 当用户释放键盘上的键时触发。
只有一个文本事件,称为textInput。此事件是keypress的增强版,旨在向用户显示之前将输入文本拦截。在将文本插入文本框之前,将触发textInput事件。
当用户在键盘上按一次字符键时,会先触发keydown事件,然后再触发keypress事件,再触发keyup事件。请注意,在对文本框进行任何更改之前都会触发keydown和keypress,而在对文本框进行更改之后会触发
keyup事件。如果按下并按住某个字符键,则会反复触发keydown和keypress,直到释放该键时才会停止。对于非字符键,在键盘上按一下单个键会导致触发keydown事件,然后触发keyup事件。如果按住非字符键,则会重复触发keydown事件,直到释放该键为止,然后触发keyup事件。
键码
对于keydown和keyup事件,事件对象的keyCode属性填充有映射到键盘上特定键的代码。对于字母数字键,keyCode与该键上小写字母或数字的ASCII值相同,因此 7 键的keyCode为 55 ,而A键的keyCode为 65 ,而与Shift键的状态无关。DOM和IE的event对象都支持keyCode属性。这是对keyCode属性的示例检查示例:
<input type="text" name="666" id="myText" />
<script>
let textbox = document.getElementById("myText");
textbox.addEventListener("keyup", (event) => {
console.log(event.keyCode);
});
</script>
注意:键盘事件与鼠标事件支持相同的修正键。 shiftKey,ctrlKey,altKey和metaKey属性都可用于键盘事件。
字符码
发生按键事件时,这意味着按键会影响屏幕上文本的显示。所有浏览器都会为插入或删除字符的键触发keypress事件;其他键取决于浏览器。由于DOM3事件规范只是刚刚开始实施,因此跨浏览器存在重大的实现差异。
浏览器在event对象上支持一个名为charCode的属性,该属性仅为keypress事件填充,并包含与所按下键相关的字符的ASCII码。IE8和更早版本以及Opera都使用keyCode来传达字符的ASCII码。要以跨浏览器的方式获取charCode,因此必须首先检查是否使用了charCode属性,如果未使用,则使用keyCode,如以下示例所示:
var EventUtil = {
// ...
getCharCode: function(event) {
if (typeof event.charCode == "number") {
return event.charCode;
} else {
return event.keyCode;
}
},
// ...
};
有了字符代码后,就可以使用String.fromCharCode()方法将其转换为实际字符。
获取字符用event.key,获取字符码用event.charCode。
textInput事件
DOM3事件规范引入了一个称为textInput的事件,该事件在将字符输入到可编辑区域时触发。设计为keypress的替代,textInput事件的行为有所不同。一个区别是,keypress可以在可以具有焦点的任何元素上触发,而textInput仅在可编辑区域上触发。另一个区别是textInput仅对导致插入新字符的键触发,而keypress对以任何方式(包括Backspace)影响文本的键触发。
因为textInput事件主要对字符感兴趣,所以它在事件对象上提供了一个data属性,该属性包含插入的字符(而不是字符代码)。 data的值始终是插入的确切字符,因此,如果在不按CapsLock的情况下按下S键,则数据为“s”,否则为“S”。如下所示:
let textbox = document.getElementById("myText");
textbox.addEventListener("textInput", (event) => {
console.log(event.data); //键入啥就输出啥,包括中文
});
event对象上还有另一个叫inputMethod的属性,它指示如何将文本输入到控件中。可能的值为:
0 表示浏览器无法确定输入方式。
1 表示使用了键盘。
2 表示粘贴了文本。
3 表示作为拖动操作的一部分放置了文本。
4 表示使用IME输入文本。
5 表示通过选择表单中的一个选项来输入文本。
6 表示文本是通过手写输入的(例如使用手写笔)。
7 表示文本是通过语音命令输入的。
8 表示文本是通过多种方法输入的。
9 表示文本是通过脚本输入的。
合成事件
合成首先在DOM3事件中引入,以处理通常在IME上发现的复杂输入序列。 IME允许用户输入物理键盘上找不到的字符。例如,使用拉丁键盘的用户仍可以在计算机中输入日语字符。 IME通常要求一次按下多个键,而同时只能输入一个字符。合成事件有助于检测并使用此类输入。有三个合成事件:
compositionstart 当IME的文本合成系统打开时触发,指示输入即将开始。
compositionupdate 将新字符插入输入字段时触发。
compositionend 在关闭文字合成系统时触发,表示返回到常规键盘输入。
合成事件(Composition events)在许多方面类似于文本事件。当合成事件触发时,目标是输入栏接受文本。唯一的额外事件属性是data,其中包含以下之一:
在compositionstart期间访问时,包含正在编辑的文本。
在compositionupdate中访问时,包含要插入的新字符。
在compositionend期间访问时,包含在此composition期间输入的所有内容。与文本事件一样,必要时可以使用合成事件过滤输入。这些事件可以这样使用:
let textbox = document.getElementById("myText");
textbox.addEventListener("compositionstart", (event) => {
console.log(event.data);
});
textbox.addEventListener("compositionupdate", (event) => {
console.log(event.data);
});
textbox.addEventListener("compositionend", (event) => {
console.log(event.data);
});
// 当输入汉字“南风”时输出如下:
// n
// n
// na
// na
// nan
// nan
// nan'f
// nan'f
// nan'fe
// nan'fe
// nan'fen
// nan'fen
// nan'feng
// nan'feng
// 南风
// 南风
// 南风
HTML5事件
DOM规范未涵盖所有浏览器支持的所有事件。许多浏览器已根据用户需求或特定用例实现了用于各种目的的自定义事件。 HTML5列出了浏览器应支持的所有事件的详尽列表。本节讨论HTML5中的几个事件,这些事件得到浏览器的很好支持。请注意,这并不是浏览器支持的所有事件的详尽列表。(其他事件将在本书中进行讨论。)
contextmenu事件
先这样,后续内容过于繁杂,先不翻译了。