超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

JavaScript 常用事件之鼠标事件

鼠标事件是目前最常用的事件,与鼠标操作相关的事件主要有: mousedown, mouseup, click, dblclick, contextmenu, mousemove, mouseover, mouseout

mousedown, mouseup, click

用户点击元素触发的事件顺序为:

  1. mousedown,用户在该元素按下鼠标按钮;
  2. mouseup,用户在此元素上释放按下的鼠标按钮;
  3. click,在此元素上检测到一个 mousedown 和一个 mouseup

注意,有时用户使用鼠标执行某些操作,但并不会触发 click 事件。例如用户在链接上按下鼠标按钮,然后将鼠标从链接上移开,然后释放鼠标按钮,此时只触发了 mousedown 事件。类似地,如果用户按下鼠标按钮,则将鼠标移动到链接上,然后释放鼠标按钮,此时触发了此元素的 mouseup 事件。这两种情况下下都不会触发点击事件。

dblclick

dblclick 事件很少使用。即使使用它,也应该确保不要在同一个 HTML 元素上注册 onclickondblclick 事件处理程序。毕竟,当用户双击元素时,click 事件将发生在 dblclick 之前。

注意,取消点击事件不会影响触发 dblclick 事件。

双击事件会有一个副作用,就是双击文本会选中文本,查看更多

contextmenu

click 行为类似,区别在于点击鼠标左键触发 click 事件,点击鼠标右键触发该事件。

注意,该事件和 click 事件类似,也能触发 mousedownmouseup 事件,并且这两个事件先于 contextmenu 事件触发。

mouseover、mouseout 与 mouseenter、mouseleave

这两组事件是成对出现,并且行为比较相似,放在一起说。

mouseovermouseenter 都是鼠标进入元素的时候触发的,不同点在于,对于如下的结构:

<div class="lv1">
<div class="lv2">
<div class="lv3"></div>
</div>
</div>

当鼠标进入 lv3 的时候,会触发单一的 mouseover,并且 mouseover 事件会冒泡,从 lv3 -> lv2 -> lv1 -> ... -> root 这个顺序开始冒泡,如果中间事件被阻止冒泡,则后面元素不能触发这个事件;而 mouseenter 事件不会冒泡,不能取消,相反 lv1、lv2、lv3 都会触发各自的 mouseenter 事件,同时,鼠标从其后代元素移动到自身元素不会再次触发此事件。这意味着,由于层次较深,所发送的 mouseenter 事件的数量可能非常多,容易造成重大的性能问题,在这种情况下,最好监听 mouseover 事件。

mouseoutmouseleave 都是在鼠标离开元素的时候触发,并且它们的行为分别很类似于 mouseovermouseenter:当指针离开元素或离开其后代之一(即使指针仍在元素内)时,mouseout 将被触发,并且冒泡;当指针已经退出元素及其所有后代时,此元素的所有祖先元素触发各自的 mouseleave 事件。

看个示例:

// html
<ul id="test">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>

// js
var test = document.getElementById("test");

// this handler will be executed only once when the cursor moves over the unordered list
test.addEventListener("mouseenter", function( event ) {
console.log("mouseenter");
}, false);

// this handler will be executed every time the cursor is moved over a different list item
test.addEventListener("mouseover", function( event ) {
console.log("mouseover");
}, false);

// this handler will be executed only once when the cursor moves out of the unordered list
test.addEventListener("mouseleave", function( event ) {
console.log("mouseleave");
}, false);

// this handler will be executed every time the cursor is moved over a different list item
test.addEventListener("mouseout", function( event ) {
console.log("mouseout");
}, false);

mousemove

当在元素上方移动鼠标时,会触发 mousemove 事件。

即使用户将鼠标移动一个像素,也会触发 mousemove 事件。这会非常的影响系统的性能,最好对此事件优化去抖,同时在不使用的时候移除事件的监听,具体查看本文“其他事件 —— resize 和 scroll” 一节。

wheel

当鼠标中键滚动时,触发该事件。基本用不到,有兴趣可以去啃一下 W3C 规范 Wheel Events

select

当文本被选中的时候,select 事件会触发。并非每个元素都能触发这个事件,在 Html5 规范里,只能在 inputtextarea 上触发该事件。

该事件不提供获得选取内容的接口,如果想要获得选取的内容,可以使用 [document/window].getSelection().toString()

区分点击按键

鼠标事件的 which 属性区分了点击的哪个按键:

  • event.which == 1: 左键
  • event.which == 2: 中键
  • event.which == 3: 右键

注意 通常情况下并不会用到这个属性,因为点击左键触发 click 事件,点击右键触发 contextmenu 事件,但是它们都会触发 mousedown 和 `mouseup 事件,如果使用这两个事件,可能需要注意一下是否需要区分点击的按键。

鼠标按键组合事件

鼠标事件同时也会包含按键的相关信息,常用的控制按键包括:

  • altKey: 当鼠标事件触发时,如果 Alt 键被按下,则返回 true;
  • ctrlKey: 当鼠标事件触发时,如果 Ctrl 键被按下,则返回 true;
  • metaKey: Mac 上特殊属性,等价于 Windows 上的 ctrlKey,当在 Mac 上鼠标事件被触发时,如果 Cmd 键被按下,则返回 true ;
  • shiftKey: 当鼠标事件触发时,如果 Shift 键被按下,则返回 true;

使用控制按键,可以组合出更加丰富的鼠标事件,比如下面是一个支持多选的列表,按住 ctrl 点击选项则多选,否则为单选:

<ul id="list">
<li>li1</li>
<li>li2</li>
<li>li3</li>
<li>li4</li>
<li>li5</li>
</ul>

<script>
var selected = [];
document.getElementById('list').addEventListener('click', function(event) {
if (event.ctrlKey) {
event.target.style.color = "red";
} else {
var liEles = document.getElementsByTagName('li');
for (var i = 0; i < liEles.length; i ++) {
liEles[i].style.color = "black";
}
event.target.style.color = "red";
}
});
</script>

运行结果如下:

不按 ctrl,单点 li1: li1 变红

不按 ctrl,单点 li2:li2 变红,li3 为黑色

按住 ctrl 点 li3,多选:li2,li3 变红

不按 ctrl,单点 li4:li4 变红,其他为黑色

注意,Mac 下不存在 Ctrl 键,需要使用 Cmd 代替,可以很容易使代码支持 Mac:

if (event.ctrlKey || event.metaKey) {
event.target.style.color = "red";
}

坐标属性

这里是鼠标事件中和坐标相关的属性:

  • offsetX/Y: 点击位置相对于所处元素左上角的位置;
  • clientX/Y: 点击位置相对于浏览器内容区域左上角的位置;
  • screenX/Y: 点击位置相对于屏幕左上角的位置;
  • pageX/Y: 点击位置相对整张页面左上角的位置,pageX/YclientX/Y 一般情况下会相同,只有出现滚动条时才不一样。

这几个坐标值也没有什么难度,就是很容易混淆,可以现用现查,不再详细说明。

应用:滑动选择器

之前项目有个需求,需要滑动选择内容,项目要求鼠标在点击单元格开始选择,然后在多个单元格上滑动,直到按键松开选择结束,这实际上就是 mousedown/mouseover/mouseup 这三个事件的综合应用,这里来看一下如何实现:

首先初始化选择区域:

function DragSelector(container) {
this._container = $(container);
}

DragSelector.prototype.init = function(row, column, afterSelected) {
this._row = row;
this._column = column;
var index = 0;
for (var i = 0; i < row; i ++) {
var ulHtml = '<ul>';
for (var j = 0; j < column; j ++) {
ulHtml += '<li data-value="' + index + '"></li>';
index ++;
}
ulHtml += '</ul>';
this._container.append(ulHtml);
}
this._afterSelected = afterSelected;
return this.bind();
}

DragSelector.prototype.bind = function() {
var self = this;
self._selected = [];
// bind events
};

然后是重点,绑定事件,这个可以分解为多个任务:

1.使用 mousedown 处理点击后开始选择:

// self.draging 标识开始选择,将
$(this._container).on('mousedown', 'li', function() {
if (!self.draging) {
self._draging = true;
self._selected.push($(this).data('value'));
$(this).addClass('selected');
}
})

2.使用 mouseover 处理滑动到元素后开始选择:

$(this._container).on('mouseover', 'li', function() {
if (self._draging) {
self._selected.push($(this).data('value'));
$(this).addClass('selected');
}
});

3.使用 mouseup 处理按键松开后结束选择:

$(this._container).on('mouseup', 'li', function() {
if (self._draging) {
self._draging = false;
self._afterSelected(self._selected);
$(this).addClass('selected');
}
});

这样就是一个简略的滑动选择器,当然没有进行复杂的处理,比如数据去重,比如反选等操作,仅做简单演示。

See the Pen NXZNeV by tcatche (@tcatche) on CodePen.

参考