click事件细节
这是前一个月被反馈的问题,当时没有时间研究,今天稍有时间研究汇总下
一个小问题
click事件是鼠标点击某个元素的时候触发的吗?
这么问还不够细……
是鼠标点下触发的吗?
是鼠标松开时触发的?
还是鼠标一次按下+松开再触发。
这个问题很好回答,相信很多人有这样一种检验:一些二逼的产品经理把一些点击的交互放在非常不寻常的位置,这导致我们经常点击到错误的区域——好的,我们细化这个过程:
-->我们需要执行某个无脑操作,鼠标在我们直觉认为的地方左键按下(并没有松开)
-->然后大脑忽然参与了,“二逼,这个地方点击并不能达到你的期望,并且会跳到一个操作时间很长的页面,以公司这破网速,你再回到此页面需要250秒!”
-->然后我们的右手(此处忽略左撇子的感受!)拖动鼠标到一个无关的地方,释放按下的左键,“好险,又避开了产品经理的陷阱!”
一次连贯的click事件就被我们摧毁了。
和别的事件结合会发生什么事
就像我们打断鼠标的点击事件一样,在同一元素上触发click事件和一些别的事件,会造成意外的情况。
因为click包含mousedown和mouseup,那么我们就要特别注意那些mousedown时触发的事件:mousedown,focus,focusin。
假如这些mousedown时就会触发的事件是把鼠标移到别的位置,例如弹出一个遮挡层——鼠标与原来的元素之间的联系就断开了,就算鼠标的坐标没有发生变化,但是此时再松开鼠标——还有卵用!你只是在遮挡上来了一发mouseup!
细节之处
模拟select元素遇到的问题
方案一
我时常看到一些人嫌select元素不好用,要自己模拟一个select。
代码会这样写
/**
* 模拟一个下拉列表
*/
function DropdownList(){
var container = $("<div class='select'></div>");
var curitem = $("<input readonly='readonly' class='curitem'>");
var list = $("<ul style='display:none'><li>选项1</li><li>选项2</li></ul>");
//事件
curitem
.focus(function(){
list.css({
"display":"block"
})
})
.blur(function(){
list.css({
"display":"none"
})
})
list.find("li").click(function(e){
var value = $(e.target).html();
curitem.val(value);
})
container.append(curitem);
container.append(list);
return container;
}
他们使用一个input来呈现已选中的项的值,用一个ul存放下拉列表的值,通过input的焦点事件来显隐ul。给li元素绑定click事件来选中。
然而:click事件并没有什么卵用!
因为我们click一个li的时候,放慢动作:
-->mousedown到了li上 --> input 失去焦点,blur了 --> ul隐藏了 -->此时™释放了mouseup
你的click被沉默了!
解决办法:只要把选择事件从click换成 mousedown即可。
方案二
一些同学在上面一步就垮掉了,愤怒地说道:“尼玛的,傻逼设计的js,傻逼设计的dom,本屌的思路如此行云流水,竟然会出现此路不通!”
他们会执行方案二:
/**
* 模拟一个下拉列表
* 使用一个div和ul
*/
function DropdownList2(){
var container = $("<div class='select'></div>");
//这里不再用input元素,而是一个div元素,他们不再依赖focus和blur机制
var curitem = $("<div class='curitem'></div>");
curitem.css({
"border":"1px solid #ddd",
"height":"30px",
"width":"100px"
})
var list = $("<ul style='display:none'><li>选项1</li><li>选项2</li></ul>");
//事件
curitem
.click(function(){
list.css({
"display":"block"
})
})
list.find("li").click(function(e){
var value = $(e.target).html();
curitem.html(value);
list.css({
"display":"none"
})
})
container.append(curitem);
container.append(list);
return container;
}
然而坑爹的是,当ul显示出来之后,点击页面的其他地方,ul隐藏不了。于是……
在body上绑定了一个click事件,click之后隐藏ul……
有一些模拟化编程基础的同学是忍受不了这种事情的。
我的解决方法是使用focuseout,它有一些兼容性问题我这里不讨论:
focusout和blur的概念是一样的,但它是冒泡的。有人说,这里是个div元素没有focus和blur事件,你冒个卵子,我只能说too young too simple!
需要背景知识的去看这篇文章,很棒的,说说focus /focusin /focusout /blur 事件
我们直奔主题了:
我们将上面的代码改造成如下
/**
* 模拟一个下拉列表
* 使用一个div和ul
*/
function DropdownList2(){
var container = $("<div class='select' tabindex='0'></div>");//这里使用一个tabindex是为了让div在非ie环境下具有fouseout事件
//这里不再用input元素,而是一个div元素,他们不再依赖focus和blur机制
var curitem = $("<div class='curitem'></div>");
curitem.css({
"border":"1px solid #ddd",
"height":"30px",
"width":"100px"
})
var list = $("<ul style='display:none;width:200px;border:1px solid #ddd;'><li>选项1</li><li>选项2</li></ul>");
//事件
curitem
.click(function(){
list.css({
"display":"block"
})
})
list.find("li").click(function(e){
var value = $(e.target).html();
curitem.html(value);
list.css({
"display":"none"
})
})
container.focusout(function(e){
console.log("container focusout")
list.css({
"display":"none"
})
})
container.append(curitem);
container.append(list);
return container;
}
上面的代码在chrome,firfox下没有问题,但是在ie下是有问题的:li的click之后触发的首先是container的focusout事件,然后list直接被隐藏了,click又被沉默了!——因为ie下li和div天然就可触发focuseout事件,所以我们放下慢动作:
ie中(别的浏览器我就不说了):
-->首先你click了用来显示选中项的curitem,然后ul显示出来了——此时焦点在curitem上
-->然后你选中了一项(mousedown):焦点到了ul中的此项上-->curitem上触发了focusout事件,冒泡到container上-->container.focusout隐藏了ul-->你在ul之外释放mouseup
这个现象说明了一个元素的focusout事件包括冒泡上去的focusout事件,执行完了,才会执行这个元素上的click事件!
想一想,为什么要这么设计?
为什么一个先触发的事件,需要完成其整个冒泡之后,再触发后一个事件?