Web 页面上的交互千变万化,但归结起来,几乎都源于几类核心事件。掌握这些常用事件类型,就能处理绝大多数的用户操作场景。下面按照事件来源分类,介绍最常用的事件、它们的关键属性和典型应用。
15.5.1 鼠标事件
鼠标事件是最早被广泛使用的一类交互事件,除了基础的点击,还包含移动、进入、离开等行为。
| 事件名 | 触发时机 | 常用场景 |
|--------|---------|----------|
| click | 鼠标单击(按下并释放) | 按钮点击、链接跳转、选项切换 |
| dblclick | 鼠标双击 | 编辑态激活(如表格双击编辑) |
| mousedown | 鼠标按键按下 | 拖拽开始、自定义按钮按下效果 |
| mouseup | 鼠标按键释放 | 拖拽结束 |
| mousemove | 鼠标在元素内移动 | 拖拽跟随、鼠标坐标展示 |
| mouseenter | 鼠标进入元素(不冒泡) | 悬浮菜单展开 |
| mouseleave | 鼠标离开元素(不冒泡) | 悬浮菜单收起 |
| mouseover | 鼠标移动到元素或其子元素上(冒泡) | 需配合 mouseout 判定进出 |
| mouseout | 鼠标离开元素或其子元素(冒泡) | 同上 |
| contextmenu | 右键菜单触发 | 自定义右键菜单 |
关键属性:
clientX/clientY:鼠标相对于浏览器可视区域左上角的坐标。pageX/pageY:鼠标相对于页面文档左上角的坐标(含滚动距离)。button:按下的鼠标按键(0 左键,1 中键,2 右键)。
实用提醒:
mousemove事件触发频率非常高,不要在回调中做计算密集或频繁 DOM 渲染操作,可以结合节流或requestAnimationFrame。- 需要区分鼠标“进入元素”和“在元素内部移动”时,用
mouseenter/mouseleave比mouseover/mouseout更简单,因为前者不冒泡且不计子元素边界。
// 获取鼠标在页面中的坐标
document.addEventListener('mousemove', (e) => {
console.log(`页面坐标: (${e.pageX}, ${e.pageY})`);
});
// 拖拽示例(简易版)
const box = document.getElementById('draggable');
box.addEventListener('mousedown', (e) => {
const shiftX = e.clientX - box.offsetLeft;
const shiftY = e.clientY - box.offsetTop;
const onMouseMove = (ev) => {
box.style.left = ev.clientX - shiftX + 'px';
box.style.top = ev.clientY - shiftY + 'px';
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', onMouseMove);
}, { once: true });
});
15.5.2 键盘事件
键盘事件主要用于处理文本输入、快捷键、游戏控制等。
| 事件名 | 触发时机 | 说明 |
|--------|---------|------|
| keydown | 键盘按键按下 | 按住不放会持续触发,可识别所有按键 |
| keypress | 字符键按下(已废弃) | 不推荐使用,用 keydown 或 input 替代 |
| keyup | 键盘按键释放 | 常用于组合键触发结束后 |
关键属性:
key:按键的当前值(如'a'、'Enter'、'ArrowUp'),是最直观的属性,推荐优先使用。code:按键的物理键位码(如'KeyA'、'Digit1'),不受键盘布局影响。ctrlKey/shiftKey/altKey/metaKey:布尔值,表示对应的修饰键是否同时按下(metaKey在 macOS 上是 Command 键,Windows 上是 Windows 键)。
实用提醒:
- 对输入框的文本变化,更推荐使用
input事件(见下一节),它不但覆盖键盘输入,还处理粘贴、剪切、语音输入等。 - 全局快捷键需要监听
keydown,并在回调中检查修饰键组合。
// 监听 Ctrl+S 保存快捷键
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault(); // 阻止浏览器默认保存网页行为
saveDocument();
}
});
15.5.3 表单事件
表单是与用户数据交互的核心通道,表单事件可以帮助实时验证、控制提交流程。
| 事件名 | 触发时机 | 常用场景 |
|--------|---------|----------|
| input | 表单元素的值发生变化(实时) | 实时字符计数、搜索建议、即时校验 |
| change | 表单元素值改变并且失去焦点 | 下拉菜单选择、复选框变化、文件选取后 |
| submit | 表单提交(点击按钮或回车) | 前端校验、阻止默认发送、改用 Ajax 提交 |
| reset | 表单重置 | 恢复默认值的二次确认 |
| focus | 元素获得焦点 | 显示输入提示、改变边框样式(不冒泡,可用 focusin) |
| blur | 元素失去焦点 | 触发校验、隐藏提示 |
事件对象要点:
event.target.value:获取当前表单元素的值。event.preventDefault():阻止表单的默认提交行为(页面刷新),以便使用异步请求。
// 实时显示输入字数
const textarea = document.querySelector('textarea');
const counter = document.getElementById('counter');
textarea.addEventListener('input', (e) => {
counter.textContent = e.target.value.length;
});
// 使用 Ajax 提交表单
document.getElementById('myForm').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
fetch('/api/submit', { method: 'POST', body: formData });
});
警告: input 事件无法用键盘事件全面替代,因为用户可能右键粘贴、拖拽输入,甚至语音输入,这些都不会触发 keydown,但会触发 input。
15.5.4 滚动事件
滚动事件常用于实现吸顶导航、无限滚动、滚动动画等效果。
- 事件名:
scroll - 触发对象:
window(页面滚动)或任何有滚动条的容器元素
关键属性:
window.scrollY/window.scrollX:页面在垂直/水平方向的滚动距离。element.scrollTop/element.scrollLeft:元素当前滚动距离。
性能警告:scroll 事件触发极其频繁(每帧都可能触发多次),直接在回调中操作 DOM 或计算布局会导致明显卡顿。务必使用节流(throttle)或 requestAnimationFrame 进行优化。
// 吸顶效果示例(使用 requestAnimationFrame 优化)
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
const nav = document.getElementById('mainNav');
if (window.scrollY > 200) {
nav.classList.add('sticky');
} else {
nav.classList.remove('sticky');
}
ticking = false;
});
ticking = true;
}
});
15.5.5 加载事件
加载事件关注页面生命周期和资源就绪状态,通常在初始化脚本时用到。
| 事件名 | 触发对象 | 触发时机 |
|--------|---------|----------|
| DOMContentLoaded | document | HTML 解析完毕,DOM 树已构建,但外部资源(图片、样式表)可能尚未加载 |
| load | window | 整个页面及所有外部资源(图片、CSS、脚本等)均已加载完成 |
| beforeunload | window | 页面即将卸载(关闭或跳转),可用于提示用户未保存更改 |
| unload | window | 页面已卸载,一般用于清理(发分析上报),但此时很多异步操作不可靠 |
| error | 元素/窗口 | 脚本加载失败、图片加载失败等 |
选择规则:
- 绝大多数 DOM 操作脚本应在
DOMContentLoaded之后执行,速度更快,不用等待图片等大资源。 - 如果代码依赖图片尺寸、CSS 样式计算值,则需等待
load事件。 - 外部脚本如果带有
async或defer属性,执行时机受影响,应结合实际位置决定是否需要监听事件。
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM 已就绪,可安全操作元素');
});
window.addEventListener('beforeunload', (e) => {
// 标准做法需要同时设置 returnValue 和返回字符串
e.preventDefault();
e.returnValue = ''; // 现代浏览器通常忽略自定义消息,显示默认提示
});
15.5.6 触摸事件
移动设备没有鼠标,取而代之的是触摸屏。触摸事件让网页能够响应手指操作,实现手势交互。
| 事件名 | 触发时机 |
|--------|---------|
| touchstart | 手指触摸到屏幕 |
| touchmove | 手指在屏幕上滑动 |
| touchend | 手指离开屏幕 |
| touchcancel | 触控被打断(如来电、通知弹窗等) |
事件对象特殊属性:
touches:当前屏幕上所有触摸点的列表。targetTouches:当前事件目标元素上的触摸点列表。changedTouches:引发本次事件的触摸点列表(用于获取手指离开时的坐标)。
每个触摸点对象包含 clientX、clientY、pageX、pageY 等坐标信息。
移动端点击延迟与穿透问题:
- 早期浏览器存在 300ms 的点击延迟(用于区分双击缩放),现在多数浏览器已通过
<meta name="viewport" content="width=device-width">移除该延迟。 - 如果同时注册了
touchstart和click,要注意click会在touchend之后几百毫秒触发,可能造成“点透”现象。一般优先使用touchstart或使用 FastClick 类方案,或直接采用pointer事件(见下文)。
// 简单滑动检测
let startX;
element.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
});
element.addEventListener('touchend', (e) => {
const endX = e.changedTouches[0].clientX;
const diff = endX - startX;
if (Math.abs(diff) > 50) {
console.log(diff > 0 ? '右滑' : '左滑');
}
});
补充:指针事件(Pointer Events)
现代浏览器提供了一个统一的事件模型——指针事件(Pointer Events),将鼠标、触摸、触控笔统一抽象为 pointerdown、pointermove、pointerup 等事件。它能自动处理多点触控和鼠标按钮区分,日益成为推荐方案。
element.addEventListener('pointerdown', (e) => {
console.log(e.pointerType); // 'mouse', 'pen', 'touch'
});
除非需要兼容很旧的浏览器,否则在新项目中可以优先考虑指针事件,减少为不同输入设备分别绑定事件的麻烦。
小结
- 鼠标事件处理点击、移动、拖拽,注意坐标体系和频繁触发事件。
- 键盘事件用
key属性判断按键,修饰键组合实现快捷键。 - 表单事件中
input实时响应值变化,submit拦截默认提交流程。 - 滚动事件必须节流,用
requestAnimationFrame优化。 - 加载事件在合适的生命周期节点初始化脚本。
- 触摸事件攻克移动端手势,优先考虑统一的指针事件。
掌握这些常用事件类型,再结合前面学习的事件流、事件委托和事件对象,就能为页面构建出精准、高效、跨设备的交互体验。