JavaScript 鼠标事件详解:从基础触发到跨浏览器兼容处理
本文深入解析 JavaScript 中的鼠标事件机制,涵盖 mousedown、mouseup、click 及 dblclick 的触发顺序与差异。重点探讨 mouseover/mouseout 的事件冒泡特性及其带来的定位难题,详细讲解如何利用 relatedTarget、fromElement 和 toElement 获取鼠标来源与去向,并提供跨浏览器兼容代码。最后介绍微软引入的 mouseenter/mouseleave 事件以解决冒泡困扰,适用于构建复杂的交互式导航菜单。
鼠标事件概览与触发机制
首先梳理常见的鼠标事件类型,包括 mousedown、mouseup 与 click、dblclick、mousemove,以及 mouseover 和 mouseout。随后将解析 relatedTarget、fromElement 和 toElement 等事件属性。最后介绍微软特有的 mouseenter 和 mouseleave 事件。
关于浏览器兼容性问题,可参考浏览器兼容性列表进行查阅。
例子
此处提供一个示例,有助于理解后续内容。
在链接上注册 mousedown、mouseup、click 和 dblclick 事件。可在下方文本框或对话框中查看。(请在原文里尝试:
http://www.quirksmode.org/js/events_mouse.htm)Mousedown,mouseup,click
当用户在元素上点击时,至少会触发三个事件,其触发顺序如下:
1、mousedown:当用户在该元素上按下鼠标按键时触发。
2、mouseup:当用户在该元素上松开鼠标按键时触发。
3、click:当检测到该元素上同时发生了 mousedown 和 mouseup 时触发。
通常情况下,mousedown 和 mouseup 比 click 更具实用性。部分浏览器不允许读取 onclick 的事件信息,且有时用户执行鼠标操作时 click 事件并未随之触发。
假设用户在链接上按下鼠标键,随后将鼠标移出链接范围并松开按键,此时该链接仅触发 mousedown 事件。反之,若用户在点击后移动鼠标至链接上方并松开,则仅触发 mouseup 事件。在这两种情况下,均不会触发 click 事件。
这是否构成问题取决于用户的具体行为。但除非你明确只需要处理 click 事件,否则建议注册 onmousedown 和 onmouseup 事件。
若使用弹出警示框,浏览器可能会丢失事件发生的轨迹及触发次数,导致逻辑混乱。因此建议避免使用警示框。
Dblclick
dblclick 事件较少使用。若需使用,切勿将 onclick 和 dblclick 的事件处理程序注册在同一 HTML 元素上。若两者同时注册,几乎无法准确判断用户的实际意图。
总之,当用户在元素上双击时,click 事件总是先于 dblclick 触发。此外,在 Netscape 浏览器中,第二次 click 总是会在 dblclick 之前被单独处理。在此场景下使用警示框存在风险。
因此,确保 click 和 dblclick 的处理逻辑相互独立,可避免许多复杂情况。
Mousemove
mousemove 事件的性能考量
mousemove 事件运行良好,但需注意其可能消耗大量系统资源。用户每移动一个像素,mousemove 就会触发一次。即使未执行具体操作,长而复杂的函数也会耗费大量时间,影响网站效率,尤其在老旧设备上表现明显。
最佳实践是仅在需要时注册 onmousemove 事件,并在不需要时尽快移除:
代码如下:
element.onmousemove = doSomething;
// later
element.onmousemove = null; Mouseover和mouseout
mouseover/mouseout 与事件冒泡
参考此示例,将事件类型替换为 mouseover 并尝试运行。该示例仅在 ev3 上添加了 onmouseover 事件处理程序。然而,你会注意到不仅 ev3 会触发事件,ev4 或 span 上也会触发。在 Mozilla 1.3 版本之前,当鼠标进入文本区域时均会触发。
原因在于事件冒泡。用户在 ev4 上触发了 mouseover 事件。由于该元素上没有 onmouseover 事件处理程序,但在 ev3 上存在,因此当事件冒泡至 ev3 时,处理程序得以执行。
尽管当前设置完全正确,但仍存在一个问题,即目标元素判定。假设鼠标进入 ev4:
-----------------------------------------
| This is div id="ev3" |
| ----------------------------- |
| | This is div id="ev4" | |
| | -------- <-------- |
| | | span | | |
| | -------- | |
| ----------------------------- |
-----------------------------------------
<--------: mouse movement
此时事件的 target/srcElement 为 ev4,即事件发生的元素,因为鼠标移动到了其上方。但当发生以下情况时:
-----------------------------------------
| This is div id="ev3" |
| ----------------------------- |
| | This is div id="ev4" | |
| | -------- | |
| | | span | | |
| | | --------> | |
| | -------- | |
| ----------------------------- |
-----------------------------------------
-------->: mouse movement
事件的 target/srcElement 依然相同。在此情况下,鼠标依然进入 ev4。然而,你可能希望根据鼠标是从 ev3 还是从 SPAN 进入而执行不同的操作。因此,我们需要知晓鼠标的来源。
relatedTarget, fromElement, toElement
W3C 规范在 mouseover 和 mouseout 事件中引入了 relatedTarget 属性。在 mouseover 事件中,它表示鼠标来自哪个元素;在 mouseout 事件中,它表示鼠标去往哪个元素。
微软也提供了包含以下信息的两个属性:
1、fromElement:指鼠标来之前的元素,在 mouseover 场景下较为有用。
2、toElement:表示鼠标将要去的那个元素,在 mouseout 场景下较为有用。
在我们的第一个例子中,relatedTarget/fromElement 包含对 ev3 的引用;在第二个例子中则是 SPAN。现在你可以知晓鼠标的来源了。
跨浏览器获取鼠标来源与去向
跨浏览器的代码
因此,若想在 mouseover 场景中知晓鼠标来源,可参考以下代码:
代码如下:
function doSomething(e) {
if (!e) var e = window.event;
var relTarg = e.relatedTarget || e.fromElement;
} 若在 mouseout 场景中想知道鼠标的去向,可参考以下代码:
代码如下:
function doSomething(e) {
if (!e) var e = window.event;
var relTarg = e.relatedTarget || e.toElement;
} 处理鼠标离开容器层的逻辑
鼠标离开一个层
在基于层的导航菜单中,你可能需要知晓鼠标何时离开层,以便关闭该层。因此,你给这个层的 onmouseout 注册了一个事件处理程序。然而,事件冒泡会导致当鼠标离开任意子层时都触发该 onmouseout。
--------------
| Layer |.onmouseout = doSomething;
| -------- |
| | Link | ----> We want to know about this mouseout
| -------- |
| -------- |
| | Link | |
| | ----> | but not about this one
| -------- |
--------------
---->: mouse movement
另一种停止方法是,当你把鼠标移入该层并经过链接时,浏览器会在该层上注册一个 mouseout 事件。这令人困惑(因为鼠标依然在层内),但所有浏览器均能正常工作。
那么,如何在鼠标真正离开层时让 mouseout 发生呢?
代码如下:
function doSomething(e) {
if (!e) var e = window.event;
var tg = (window.event) ? e.srcElement : e.target;
if (tg.nodeName != 'DIV') return;
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
while (reltg != tg && reltg.nodeName != 'BODY')
reltg= reltg.parentNode
if (reltg== tg) return;
// Mouseout took place when mouse actually left layer
// Handle event
} 首先获取事件的 target,即鼠标离开的元素。如果 target 不是 DIV(层),则结束函数,因为鼠标并未真正离开层。
如果 target 是层,我们无法确定鼠标是离开了层还是进入了层内的链接。因此需进一步检查事件的 relatedTarget/toElement,即鼠标移向的元素。
我们读取该元素,然后通过 DOM 树向上遍历,直到找到事件的 target(即 DIV)或 BODY 元素。
如果我们遇到的 target 是层的子元素,说明鼠标并未离开层,此时停止函数运行。
当函数通过所有验证后,我们即可确定鼠标确实离开了层,从而执行相应动作(通常是隐藏该层)。
mouseenter 与 mouseleave 事件
Mouseenter 和 mouseleave
微软还提供了一种解决方案,添加了两个新事件:mouseenter 和 mouseleave。除不响应事件冒泡外,它们与 mouseover 和 mouseout 基本相同。它们将注册了事件的元素视为一个整体,对于发生在块内的
mouseover 和 mouseout 不做反应。
因此,这两个事件也解决了我们的问题:它们仅对绑定的元素做出 mouseover/out 反应。
目前,这两个事件仅被版本在 5.5 以上的 IE 支持。或许其他浏览器未来会借鉴此特性。
结尾
至此,Event 的介绍已接近尾声。祝好运!
原文地址:http://www.quirksmode.org/js/events_mouse.html
我的Twitter:@rehawk
D3.js 处理大规模数据点的性能优化策略
2026-11-05 15:37:25
JS实现网页摄像头实时扫码:结合getUserMedia与jsQR教程
2026-11-05 15:27:28
D3.js v5 迁移到 v7 的 API 变更与注意事项
2026-11-05 11:57:17
使用 animate.js 实现多元素协同的序列动画
2026-11-04 22:27:53
JS拖拽排序时怎么添加平滑动画效果?FLIP技术实战
2026-11-04 10:12:22
