2、UI事件类型:鼠标事件、键盘事件、触摸事件、窗口事件、自定义事件
事件类型,说白了就是用户与界面交互的「语言」。不同的设备、不同的操作,对应着不同的事件类型。我刚开始做UI开发时,总觉得事件类型背下来就行,后来踩了坑才明白——理解每种事件的触发时机和特性,比死记硬背重要得多。
咱们把UI事件分成五大类来聊:鼠标、键盘、触摸、窗口,还有自定义事件。每一类都有自己的脾气。
2.1 鼠标事件
鼠标事件是最经典的UI事件类型。从桌面端到Web端,鼠标事件几乎无处不在。我个人习惯把鼠标事件分成三个层级:
| 层级 | 事件 | 触发时机 |
|---|---|---|
| 基础 | click / dblclick | 单击/双击 |
| 移动 | mousedown / mousemove / mouseup | 按下/移动/释放 |
| 进出 | mouseenter / mouseleave / mouseover / mouseout | 进入/离开区域 |
这里有个坑,我经常看到新人搞混 mouseenter 和 mouseover。简单说:mouseenter 不冒泡,mouseover 冒泡。如果你在父元素上监听 mouseover,子元素进出也会触发,这往往不是你想要的。
// 鼠标事件监听示例
element.addEventListener('mousedown', (e) => {
console.log('鼠标按下', e.clientX, e.clientY);
});
element.addEventListener('mouseup', (e) => {
console.log('鼠标释放', e.clientX, e.clientY);
});
2.2 键盘事件
键盘事件处理的是按键输入。你想想看,用户按下一个键,其实经历了三个阶段:
- keydown:按键按下瞬间触发
- keypress:字符输入时触发(已废弃,但老项目里还能见到)
- keyup:按键释放时触发
我建议你重点关注 keydown 和 keyup。为什么?因为 keypress 无法捕获功能键(比如 Ctrl、Shift、方向键),而且不同浏览器实现有差异。
e.ctrlKey、e.shiftKey、e.altKey 这些属性,比自己去解析 keyCode 靠谱得多。
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault(); // 阻止浏览器默认保存行为
console.log('触发自定义保存逻辑');
}
});
嗯,这里要注意:e.key 返回的是按键的字符值,比如 'a'、'Enter';而 e.code 返回的是物理按键位置,比如 'KeyA'、'Enter'。两者区别很大,我曾在国际键盘布局下吃过亏——用户按了不同位置的键,e.key 一样但 e.code 不同。
2.3 触摸事件
移动端时代,触摸事件是必修课。触摸事件的核心是「多点触控」,这和鼠标事件有本质区别。
| 事件 | 触发时机 | 关键属性 |
|---|---|---|
| touchstart | 手指触摸屏幕 | touches / targetTouches |
| touchmove | 手指在屏幕滑动 | changedTouches |
| touchend | 手指离开屏幕 | changedTouches |
| touchcancel | 系统中断触摸 | — |
我建议你记住一个原则:触摸事件和鼠标事件不要混用。移动端浏览器会同时触发触摸事件和鼠标事件(为了兼容),如果你两个都监听,逻辑会执行两次。
e.preventDefault() 阻止鼠标事件的触发。
element.addEventListener('touchstart', (e) => {
const touch = e.touches[0];
console.log('触摸位置', touch.clientX, touch.clientY);
});
element.addEventListener('touchmove', (e) => {
e.preventDefault(); // 阻止页面滚动
const touch = e.touches[0];
// 处理滑动逻辑
});
2.4 窗口事件
窗口事件和浏览器窗口本身相关,不依赖具体的UI元素。这类事件往往被忽视,但实际项目中非常关键。
- resize:窗口大小变化
- scroll:页面滚动
- load:页面加载完成
- beforeunload:页面即将关闭
- visibilitychange:页面可见性变化
我个人觉得 visibilitychange 是窗口事件里最实用的。为什么?因为用户切换标签页时,你的应用可能还在跑动画、发请求,白白浪费资源。
visibilitychange 事件里暂停视频播放、停止轮询请求、暂停动画循环。页面重新可见时再恢复。这个优化能让你的应用更省电、更流畅。
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('页面不可见,暂停资源消耗');
// 暂停动画、停止轮询
} else {
console.log('页面可见,恢复操作');
// 恢复动画、重新请求数据
}
});
还有 resize 事件,我建议做防抖处理。窗口变化时事件触发频率极高,不做防抖的话,性能会受影响。
2.5 自定义事件
内置事件不够用怎么办?自己造。自定义事件是UI事件驱动架构的「粘合剂」,它让组件之间可以解耦通信。
创建自定义事件有两种方式:
- 使用 Event 构造函数:简单场景
- 使用 CustomEvent 构造函数:需要传递数据时
// 方式一:简单事件
const event = new Event('userLogin');
document.dispatchEvent(event);
// 方式二:带数据的事件
const customEvent = new CustomEvent('dataLoaded', {
detail: { userId: 123, name: '张三' }
});
document.dispatchEvent(customEvent);
// 监听自定义事件
document.addEventListener('dataLoaded', (e) => {
console.log('数据加载完成', e.detail);
});
bubbles: true。我遇到过组件嵌套时事件传不上去的问题,就是因为忘了设置冒泡。
自定义事件的好处是:你可以在任何地方触发,在任何地方监听。说白了,它就是一个全局的消息总线。但别滥用,否则事件满天飞,调试起来很痛苦。
2.6 事件类型关系图
下面这张图展示了五种事件类型的关系和典型应用场景:
2.7 事件类型选择建议
说了这么多,到底该用哪种事件?我总结了几条经验:
| 场景 | 推荐事件类型 | 原因 |
|---|---|---|
| 桌面端点击按钮 | click | 简单直接,兼容性好 |
| 拖拽功能 | mousedown + mousemove + mouseup | 需要精确控制按下、移动、释放 |
| 快捷键操作 | keydown | 能捕获组合键,响应快 |
| 移动端滑动 | touchstart + touchmove + touchend | 原生触摸支持,性能好 |
| 页面可见性变化 | visibilitychange | 比 focus/blur 更准确 |
| 组件间通信 | 自定义事件 | 解耦,灵活 |