鼠标事件是Web交互的基础。点击、拖拽、滚轮、悬停......几乎所有操作都离不开它。
但很多人只会写 onclick,其实鼠标事件远不止这些。本文从基础到实战,讲清楚每个事件怎么用、什么时候用、有哪些坑。
一、八个核心事件对比
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
click |
完整点击(按下+松开) | 按钮、链接、提交 |
dblclick |
双击 | 打开文件、编辑文本 |
mousedown |
鼠标按下瞬间 | 拖拽开始、按住触发 |
mouseup |
鼠标松开瞬间 | 拖拽结束 |
mousemove |
鼠标移动 | 实时跟踪位置、绘图 |
mouseenter |
鼠标进入元素 | 悬停显示提示 |
mouseleave |
鼠标离开元素 | 隐藏提示 |
wheel |
滚轮滚动 | 切换图片、缩放 |
contextmenu |
右键点击 | 自定义右键菜单 |
最常用的四个:click、mousedown、mousemove、wheel。
二、基础写法
javascript
// 方式1:addEventListener(推荐)
document.getElementById('btn').addEventListener('click', function(e) {
console.log('点击了');
});
// 方式2:on事件属性(简单场景可用)
<button onclick="alert('点击了')">点我</button>
// 方式3:内联事件(不推荐,维护性差)
<div onmouseover="this.style.background='red'">悬停变红</div>
结论:优先用 addEventListener,可以绑定多个事件,也方便解绑。
三、事件对象 e 里有什么
javascript
element.addEventListener('click', function(e) {
console.log(e.clientX, e.clientY); // 鼠标相对于视口的坐标
console.log(e.pageX, e.pageY); // 鼠标相对于文档的坐标
console.log(e.target); // 触发事件的元素
console.log(e.button); // 哪个键:0左键,1中键,2右键
});
clientX/Y vs pageX/Y 怎么选?
clientX/Y→ 相对于浏览器窗口,不受滚动影响pageX/Y→ 相对于整个文档,包含滚动距离
做拖拽、定位,用 clientX/Y 更方便。
四、实战:验证码识别页面的鼠标交互
场景1:拖拽滑块旋转图片
这是我们项目中的核心交互------按住滑块左右拖动,图片同步旋转。
javascript
var box = document.getElementsByClassName("sliding_block")[0];
var isDrop = false;
var angle = 0;
// 按下:开始拖拽
box.onmousedown = function(e) {
var e = e || window.event;
var x = e.clientX - box.offsetLeft;
var y = e.clientY - box.offsetTop;
isDrop = true;
};
// 移动:实时更新位置和角度
document.onmousemove = function(e) {
if (!isDrop) return;
var e = e || window.event;
var moveX = e.clientX - x;
// 限制范围 0~360
var minX = 0, maxX = 360;
if (moveX < minX) moveX = minX;
if (moveX > maxX) moveX = maxX;
box.style.left = moveX + "px";
angle = moveX;
$("#baidu_img").attr("style", "transform: rotate(" + angle + "deg);");
$("#msg_angle_span").html(angle);
};
// 松开:结束拖拽
document.onmouseup = function() {
isDrop = false;
};
关键点:
mousedown记录初始位置mousemove实时计算偏移mouseup结束拖拽- 三个事件必须配合使用,缺一不可
场景2:滚轮切换图片
javascript
document.addEventListener('wheel', function(e) {
// 避免在输入框中触发
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
e.preventDefault(); // 必须加,否则页面会跟着滚
if (e.deltaY > 0) {
next_img(); // 滚轮向下 → 下一张
} else {
last_img(); // 滚轮向上 → 上一张
}
}, { passive: false }); // passive: false 是关键
e.deltaY 的含义:
deltaY > 0→ 滚轮向下deltaY < 0→ 滚轮向上deltaX→ 水平滚动(横向滚轮)
场景3:拖拽浮动框
项目中还有个浮动框可以自由拖动:
javascript
var floatingDiv = document.getElementById('floatingDiv');
var initialX = null, initialY = null;
floatingDiv.addEventListener('mousedown', function(e) {
initialX = e.clientX - floatingDiv.getBoundingClientRect().left;
initialY = e.clientY - floatingDiv.getBoundingClientRect().top;
e.preventDefault(); // 防止选中文本
});
document.addEventListener('mousemove', function(e) {
if (initialX === null) return;
var deltaX = e.clientX - initialX;
var deltaY = e.clientY - initialY;
floatingDiv.style.left = deltaX + 'px';
floatingDiv.style.top = deltaY + 'px';
});
document.addEventListener('mouseup', function() {
initialX = null;
initialY = null;
});
五、mouseenter vs mouseover 的区别
| 事件 | 冒泡 | 触发时机 | 推荐场景 |
|---|---|---|---|
mouseover |
✅ 冒泡 | 进入子元素也会触发 | 需要事件冒泡时 |
mouseenter |
❌ 不冒泡 | 只在进入自身时触发 | 悬停效果优先用这个 |
同理:mouseleave vs mouseout,也是 mouseleave 更常用。
javascript
// ❌ 不推荐:进入子元素会反复触发
div.addEventListener('mouseover', function() {
this.style.background = 'yellow';
});
// ✅ 推荐:只在进入/离开 div 时触发一次
div.addEventListener('mouseenter', function() {
this.style.background = 'yellow';
});
div.addEventListener('mouseleave', function() {
this.style.background = '';
});
六、五个常见坑
坑1:忘了 e.preventDefault()
右键会弹出浏览器菜单,滚轮会滚动页面,拖拽会选中文本。
解决:在 mousedown 或 wheel 里加 e.preventDefault()。
坑2:mousemove 性能问题
mousemove 触发频率极高(每秒几十次),里面写重逻辑会卡顿。
解决:用 requestAnimationFrame 节流。
javascript
var ticking = false;
document.addEventListener('mousemove', function(e) {
if (!ticking) {
requestAnimationFrame(function() {
// 在这里写业务逻辑
ticking = false;
});
ticking = true;
}
});
坑3:拖拽时元素被选中
拖拽过程中文字被蓝框选中,体验很差。
解决:CSS 全局禁用选中。
css
.sliding_block {
user-select: none;
-webkit-user-select: none;
}
(你的代码里已经加了,做得很好 ✅)
坑4:事件绑定在错误的元素上
mousemove 绑在滑块上,结果拖出滑块就失效了。
解决:mousemove 和 mouseup 绑在 document 上,不是绑在滑块上。
javascript
// ✅ 正确
box.onmousedown = function() { ... };
document.onmousemove = function() { ... }; // 绑在 document 上
document.onmouseup = function() { ... }; // 绑在 document 上
// ❌ 错误
box.onmousemove = function() { ... }; // 鼠标移出滑块就失效
坑5:用 onclick 绑定多个事件
javascript
// ❌ 后面的会覆盖前面的
btn.onclick = function() { alert(1); };
btn.onclick = function() { alert(2); }; // 只会弹出 2
// ✅ 用 addEventListener 可以绑定多个
btn.addEventListener('click', function() { alert(1); });
btn.addEventListener('click', function() { alert(2); }); // 两个都会执行
七、鼠标事件设计原则
| 原则 | 说明 |
|---|---|
拖拽用 mousedown + mousemove + mouseup |
三件套,缺一不可 |
mousemove 绑在 document 上 |
防止拖出元素失效 |
滚轮用 wheel + preventDefault |
{ passive: false } 必须加 |
悬停用 mouseenter/mouseleave |
不冒泡,不会反复触发 |
| 第一行判断是否在输入框 | 避免打字时误触发 |
总结
鼠标事件的核心就四句话:
- 点击用
click,拖拽用mousedown+mousemove+mouseup - 滚轮用
wheel,记得preventDefault()+{ passive: false } mousemove绑在document上,不是绑在元素上- 第一行判断是否在输入框中
掌握这四点,你就能搞定90%的鼠标交互需求。