DOM 事件
事件的触发用于通知“对变化感兴趣”的代码以影响代码的执行。这些事件可能来自用户的交互(例如:使用鼠标或调整窗口大小)、底层环境状态的改变(例如:电池电量不足或操作系统的媒体事件)和其他的情况。
每个事件都使用继承自 Event 接口的对象来表示,可以包括额外的自定义成员属性及函数,以获取事件发生时相关的更多信息。本文为每个事件都提供了一个表格(靠近顶部的区域),其中包含了指向相关事件接口的链接和其他相关的信息。事件 > 基于 Event 的接口中完整地列出了不同的事件类型。
本文提供了你可能感兴趣的主要事件类型(动画、剪切板、worker 等等)的索引,以及实现这些事件的主要的类。
事件索引
| 事件类型 | 描述 | 文档 |
|---|---|---|
| 动画 |
与 Web 动画 API 相关的事件。 用于响应动画状态的改变(例如:动画的开始或结束)。 |
在
Document、Window、HTMLElement 上触发的事件。
|
| 异步数据获取 | 与数据获取相关的事件。 |
在
AbortSignal、XMLHttpRequest、FileReader 上触发的事件。
|
| 剪切板 |
与剪切板 API 相关的事件。 用于在内容被剪切、复制或粘贴时发出通知。 |
在
Document、Element、Window 上触发的事件。
|
| 合成(Composition) |
与文本输入相关的事件;“间接”输入文本(而不是直接使用常规的键盘输入)。 例如,使用语音转文字的引擎,或使用特殊的组合键以将键入的内容表示成另一种语言中的字符。 |
在
Element 上触发的事件。
|
| CSS 过渡 |
与 CSS 过渡相关的事件。 当 CSS 过渡开始、停止或取消等事件发生时发出通知。 |
在
Document、HTMLElement、Window 上触发的事件。
|
| 数据库 |
与数据库操作相关的事件:打开、关闭、事务、错误,等等。 |
在
IDBDatabase、IDBOpenDBRequest、IDBRequest、IDBTransaction 上触发的事件。
|
| DOM 突变 |
与文档对象模型(DOM)层次结构和节点的修改相关的事件。 |
警告: 突变事件已被弃用。请使用 Mutation Observer 代替。 |
| 拖放、滚轮 |
与使用 HTML 拖放 API 和滚轮事件相关的事件。 拖动和滚轮事件派生自鼠标事件。虽然它们在使用鼠标滚轮和拖放时触发,但它们也可以和其他合适的硬件一起使用。 |
拖放事件在
滚轮事件在
|
| 焦点 | 与元素获得和失去焦点相关的事件。 |
在
Element、Window 上触发的事件。
|
| 表单 |
与创建、重置和提交表单相关的事件。 |
在
HTMLFormElement 上触发。
|
| 全屏 |
与 全屏 API 相关的事件。 用于在全屏和窗口模式切换或切换期间发生错误时发送通知。 |
在
Document、Element 上触发的事件。
|
| 游戏手柄(Gamepad) |
与游戏手柄 API 相关的事件。 |
在
Window 上触发的事件。
|
| 手势 |
建议使用 Touch 事件来实现手势。 |
此外还有一些非标准的手势事件:
|
| History |
与历史记录 API 相关的事件。 |
在
Window 上触发的事件。
|
| HTML 元素内容显示管理 |
与显示或文本元素状态的改变相关的事件。 |
在
HTMLDetailsElement、HTMLDialogElement、HTMLSlotElement 上触发的事件。
|
| 输入 |
与 HTML input 元素(如: |
在
HTMLElement、HTMLInputElement 上触发的事件。
|
| 键盘 |
与使用键盘相关的事件。 用于在按键向上、向下或仅仅只是按下时发出通知。 |
在
Document、Element 上触发的事件。
|
| 加载/卸载文档 | 在加载或卸载文档时触发的事件。 |
|
| 清单 |
与安装渐进式 web 应用清单相关的事件。 |
在
Window 上触发的事件
|
| 媒体 |
与媒体使用(包括媒体捕捉与媒体流 API、Web 音频 API、画中画 API,等等)相关的事件。 |
在
ScriptProcessorNode、HTMLMediaElement、AudioTrackList、AudioScheduledSourceNode、MediaRecorder、MediaStream、MediaStreamTrack、VideoTrackList、HTMLTrackElement、OfflineAudioContext、TextTrack、TextTrackList、Element/audio,
Element/video 上触发的事件。
|
| 消息传递 |
与窗口从另一个浏览上下文接受消息相关的事件。 |
在
Window 上触发的事件。
|
| 鼠标 |
与计算机鼠标相关的事件。 用于在鼠标单机、双击、释放、按下、右击、移入移除元素/文本、选择等动作时发出通知。 指针事件为鼠标事件提供了与硬件无关的代替方案。拖放和滚轮事件派生自鼠标事件。 |
在
Element 上触发的事件。
|
| 网络连接 | 与获得和失去网络连接相关的事件。 |
在
在
|
| 支付 |
与支付请求 API 相关的事件。 |
在
|
| 性能 |
与任何性能相关的规范分组为性能 API 的相关事件。 |
在
|
| 指针 |
与指针事件 API 相关的事件。 提供来自指针设备(包括鼠标、触控、笔/触控笔)的与硬件无关的通知。 |
在
Document、HTMLElement 上触发的事件。
|
| 打印 | 与打印相关的事件。 |
在
Window 上触发的事件。
|
| Promise rejection |
在任意 JavaScript promise 被拒绝时,发送到全局脚本上下文的事件。 |
在
Window 上触发的事件。
|
| 网络套接字 |
与 WebSocket API 相关的事件。 |
在
Websocket 上触发的事件。
|
| SVG | 与 SVG 图像相关的事件。 |
在
|
| 文本选择 |
与选择文本相关的选择 API 事件。 |
事件( |
| 触控 |
与触摸事件 API 相关的事件。 在与触控屏交互(例如:用手指或触控笔)时发出通知。与 Force Touch API 无关。 |
在
Document、Element 上触发的事件。
|
| 虚拟现实 |
与 WebXR 设备 API 相关的事件。 |
在
XRSystem、XRSession、XRReferenceSpace 上触发的事件。
|
| RTC(实时通信) |
与 WebRTC API 相关的事件。 |
在
RTCDataChannel、RTCDTMFSender、RTCIceTransport、RTCPeerConnection 上触发的事件。
|
| 服务器发送事件 |
与服务器发送事件 API 相关的事件。 |
在
EventSource 上触发的事件。
|
| 语音合成 |
与 Web 语音 API 相关的事件。 |
在
SpeechSynthesisUtterance 上触发的事件。
|
| Worker |
与 Web Worker API、Service Worker API、Broadcast Channel API 和 Channel Messaging API 相关的事件。 用于相应新消息和消息发送的错误。Service worker 还可以收到其他事件的通知,包括推送通知、用户单击显示的通知 push 订阅失效、从内容索引中删除条目,等等。 |
在
ServiceWorkerGlobalScope、DedicatedWorkerGlobalScope、SharedWorkerGlobalScope、WorkerGlobalScope、Worker、BroadcastChannel、MessagePort 上触发的事件。
|
创建和派发事件
除了由内置接口触发的事件外,你还可以自己创建和派发 DOM 事件。这类事件通常称为合成事件,以区别于由浏览器触发的事件。
创建自定义事件
可以使用 Event 构造函数按如下方式来创建事件:
const event = new Event("build");
// 监听事件。
elem.addEventListener("build", (e) => {
/* … */
});
// 派发事件。
elem.dispatchEvent(event);
此代码示例使用了 EventTarget.dispatchEvent() 方法。
添加自定义数据——CustomEvent()
要向事件对象添加更多数据,可以使用 CustomEvent 接口,并通过 detail 属性传递自定义数据。例如,可以这样创建事件:
const event = new CustomEvent("build", { detail: elem.dataset.time });
这样你就可以在事件监听器中访问额外的数据:
function eventHandler(e) {
console.log(`时间为:${e.detail}`);
}
添加自定义数据——继承 Event
Event 接口也可以被子类化。这对于代码复用、处理更复杂的自定义数据,甚至为事件添加方法都特别有用。
class BuildEvent extends Event {
#buildTime;
constructor(buildTime) {
super("build");
this.#buildTime = buildTime;
}
get buildTime() {
return this.#buildTime;
}
}
这段代码定义了一个具有只读属性和固定事件类型的 BuildEvent 类。
然后可以按如下方式创建该事件:
const event = new BuildEvent(elem.dataset.time);
然后,可以使用自定义属性在事件监听器中访问这些附加数据:
function eventHandler(e) {
console.log(`时间为:${e.buildTime}`);
}
事件冒泡
通常情况下,我们希望从子元素触发事件,并让父元素捕获该事件;此外,还可以选择性地将数据包含在事件中:
<form>
<textarea></textarea>
</form>
const form = document.querySelector("form");
const textarea = document.querySelector("textarea");
// 创建一个新事件,允许冒泡,并在“detail”属性中传递任何你想要的数据
const eventAwesome = new CustomEvent("awesome", {
bubbles: true,
detail: { text: () => textarea.value },
});
// form 元素监听自定义的“awesome”事件,然后输出传递的 text() 方法的结果
form.addEventListener("awesome", (e) => console.log(e.detail.text()));
// 当用户输入时,form 内的 textarea 派发/触发事件,以自身为起点
textarea.addEventListener("input", (e) => e.target.dispatchEvent(eventAwesome));
动态创建和派发事件
元素可以监听尚未创建的事件:
<form>
<textarea></textarea>
</form>
const form = document.querySelector("form");
const textarea = document.querySelector("textarea");
form.addEventListener("awesome", (e) => console.log(e.detail.text()));
textarea.addEventListener("input", function () {
// 动态创建并派发/触发事件
// 注意:此外,我们还使用了“函数表达式”(而不是“箭头函数表达式”),这样“this”将代表该元素
this.dispatchEvent(
new CustomEvent("awesome", {
bubbles: true,
detail: { text: () => textarea.value },
}),
);
});
触发内置事件
此示例演示如何使用 DOM 方法模拟复选框的点击操作(即通过编程方式生成点击事件)。查看示例运行效果。
function simulateClick() {
const event = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
const cb = document.getElementById("checkbox");
const cancelled = !cb.dispatchEvent(event);
if (cancelled) {
// 某个处理器调用了 preventDefault。
alert("已取消");
} else {
// 没有处理器调用 preventDefault。
alert("未取消");
}
}
注册事件处理器
有两种推荐的方法来注册处理器。可以通过将事件处理器代码分配给目标元素的相应 onevent 属性,或者使用 addEventListener() 方法将处理器注册为元素的监听器,从而使事件处理器代码在事件触发时运行。无论哪种方式,处理器都会收到一个符合 Event 接口(或派生接口)的对象。主要区别在于,可以使用事件监听器方法可以添加(或删除)多个事件处理器。
警告:不建议使用第三种方法,即使用 HTML onevent 属性设置事件处理器的方法!这种方法会增加标记的复杂度,降低代码的可读性,并增加调试难度。更多信息请参阅内联事件处理器。
使用 onevent 属性
按照惯例,触发事件的 JavaScript 对象都有相应的“onevent”属性(通过在事件名称前加上“on”来命名)。当事件被触发时,这些属性会被调用以运行相关的处理器代码,也可以由你自己的代码直接调用。
要设置事件处理器代码,只需将其分配给相应的 onevent 属性即可。每个元素中的每个事件只能分配一个事件处理器。如有需要,可以通过将另一个函数分配给同一属性来替换处理器。
以下示例展示了如何使用 onclick 属性为 click 事件设置 greet() 函数。
const btn = document.querySelector("button");
function greet(event) {
console.log("greet:", event);
}
btn.onclick = greet;
请注意,表示事件的对象会作为第一个参数传递给事件处理器。此事件对象实现或派生自 Event 接口。
EventTarget.addEventListener
在元素上设置事件处理器最灵活的方法是使用 EventTarget.addEventListener 方法。这种方法允许为元素分配多个监听器,并且如果需要,可以使用 EventTarget.removeEventListener 移除已注册的监听器。
备注:通过添加和移除事件处理器,你可以实现例如让同一个按钮在不同情况下执行不同操作。此外,在更复杂的程序中,清理旧的或未使用的事件处理器可以提高运行效率。
以下示例展示了如何将 greet() 函数设置为 click 事件的监听器/事件处理器(如果需要,可以使用匿名函数表达式代替命名函数)。请再次注意,事件会作为第一个参数传递给事件处理器。
const btn = document.querySelector("button");
function greet(event) {
console.log("greet:", event);
}
btn.addEventListener("click", greet);
该方法还可以接受额外的参数/选项,用于控制事件捕获和移除的方式。更多信息请参见 EventTarget.addEventListener 的参考文档。
使用 AbortSignal
事件监听器的一个显著特性是能够使用中止信号同时清理多个事件处理器。
这是通过向所有你希望一起移除的事件处理器的 addEventListener() 调用传递相同的 AbortSignal 来实现的。然后你可以调用拥有该 AbortSignal 的控制器的 abort(),这将移除所有使用该信号添加的事件处理器。例如,添加一个可以使用 AbortSignal 移除的事件处理器:
const controller = new AbortController();
btn.addEventListener(
"click",
(event) => {
console.log("greet:", event);
},
{ signal: controller.signal },
); // 向此处理器传递 AbortSignal
然后就可以像这样移除这个事件处理器:
controller.abort(); // 移除与此控制器关联的任何/所有事件处理器
多个事件处理器的交互
onevent IDL 属性(例如 element.onclick = ...)和 HTML onevent 内容属性(例如 <button onclick="...">)都指向同一个单一处理器槽。由于 HTML 在 JavaScript 能够访问同一元素之前已加载完毕,因此通常 JavaScript 会覆盖 HTML 中指定的内容。使用 addEventListener() 添加的处理器是独立的。使用 onevent 不会移除或替换使用 addEventListener() 添加的监听器,反之亦然。
当事件被派发时,监听器会分阶段调用。共有两个阶段:捕获和冒泡。在捕获阶段,事件从最高祖先元素开始,沿 DOM 树向下移动,直到到达目标元素。在冒泡阶段,事件沿相反方向移动。事件监听器默认在冒泡阶段监听,可以通过在 addEventListener() 中指定 capture: true 来使其在捕获阶段监听。在同一个阶段内,监听器将按照它们被注册的顺序运行。onevent 处理器在首次变为非 null 时即被注册;后续的重新赋值仅会改变其回调函数,不会改变其在顺序中的位置。
调用 Event.stopPropagation() 可以阻止在传播链后续环节中调用其他元素上的监听器。Event.stopImmediatePropagation() 还会阻止调用同一元素上剩余的监听器。
规范
| Specification |
|---|
| DOM> # events> |
| HTML> # events-2> |