引子
想直接看实现的可跳过引子
在完成公司的项目的时候,某块交互要求点击小箭头出现下拉列表,而点击其他地方的时候,这个下拉列表消失,就像这样:

$(".box").on("click", function(e){
$(this).find('.list-box').show()
...
...
$(document).one("click", function(){
$(".list-box").hide()
})
e.stopPropagation()
});
$(".list-box").on("click", function(e){
e.stopPropagation()
···
···
});
上面这段代码基本可以实现需求,但是有时候还需要将绑定在 document
上的处理函数先提出来,绑定更改为:
$(document).off('click', handler).one('click', handler)
的形式,这是为了防止多次在document
上绑定同一个事件。
在一次无聊翻看 element-ui
关于 el-button
的源码的时候,发现其中绑定在 el-button
上的 click
事件函数长这个样子:
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
而 el-button
却是有这样的点击行为的:

el-button
在点击之后发现了样式的更改,既然不是通过 javascript
来控制,那就是通过 css
来控制的了。于是我发现了这样的 css
代码:
.el-button:focus, .el-button:hover {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
显然,不是由:hover
来控制点击的样式,那只能是:focus
来控制的了。然而我在div
上增加:focus
时,发现并没有效果,el-button
源码中关于 template
的代码中也并没有多余属性:
<template>
<button
class="el-button"
@click="handleClick"
...省略
>
...省略
</button>
</template>
不过可以看出 el-button
是 button
标签,也许是 button
标签自带可以使用 :focus
的功能,于是我去MDN
中查询了有关:focus
的描述:
CSS伪类 :focus表示获得焦点的元素(如表单输入)。当用户点击或触摸元素或通过键盘的 “tab” 键选择它时会被触发。
我注意到获得焦点的元素这个关键词,并百度之后, 一个 html
中的全局属性: tabindex
浮现了出来, 我们看看MDN
对它的描述:
tabindex 全局属性 指示其元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航(通常使用Tab键,因此得名)。
tabindex
可以让元素被聚焦,而:focus
可以实现聚焦时的样式,所以利用css
实现点击出现下拉列表的条件就具备了。实际上并不是点击出现了这个效果,而是点击这个元素时,这个元素被聚焦了,出现了它聚焦时的样式
实现
以下是html代码,在想要实现能被聚焦的元素上添加 "tabindex" = -1
这属性就行,我们尽量使用 "tabindex" = -1
这个属性,详见 MDN:
<div class="component-once-show">
<span class="label">这是一段文字</span>
<!--小箭头-->
<span class="arrow-icon" tabindex="-1">
</span>
<!--下拉列表-->
<div class="content-list" tabindex="-1">
<div class="list-item">1</div>
<div class="list-item">2</div>
<div class="list-item">3</div>
<div class="list-item">4</div>
<div class="list-item">5</div>
<div class="list-item">6</div>
</div>
</div>
再放上css代码(其中小箭头是通过:after
的content
模拟的,content
的特殊字符可以参看 html,js,css特殊字符):
/*整个盒子的基本样式*/
.component-once-show{
/*...省略无关样式*/
position: relative;
}
/*小箭头的基本样式*/
.component-once-show .arrow-icon{
margin-left: 12px;
/*聚焦时,有默认的outline,需要去除*/
outline: none;
}
.component-once-show .arrow-icon:after{
/*方向朝下的小箭头*/
content: '\25BC';
font-size: 12px;
cursor: pointer;
}
/*小箭头在聚焦时,小箭头方向朝上*/
.component-once-show .arrow-icon:focus:after{
content: '\25B2';
}
/*小箭头在聚焦时,显示下拉列表*/
.component-once-show .arrow-icon:focus + .content-list{
visibility: visible;
}
/*下拉箭头的基本样式*/
.component-once-show .content-list{
visibility: hidden;
position: absolute;
outline: none;
/*省略无关样式*/
}
/*下拉列表是兄弟元素,在点击下拉列表时如果不希望下拉列表消失,需要为其加上:focus,与:hover,且不可连写*/
.component-once-show .content-list:focus{
visibility: visible;
}
.component-once-show .content-list:hover {
visibility: visible;
}
最终实现效果:

css
完整代码:
.component-once-show{
width: 200px;
height: 30px;
line-height: 30px;
border: 1px solid #eeeeee;
border-radius: 3px;
text-align: center;
box-shadow: 0 0 5px 2px #eeeeee;
position: relative;
}
.component-once-show .arrow-icon{
margin-left: 12px;
outline: none;
}
.component-once-show .arrow-icon:after{
content: '\25BC';
font-size: 12px;
cursor: pointer;
}
.component-once-show .arrow-icon:focus:after{
content: '\25B2';
}
.component-once-show .arrow-icon:focus + .content-list{
visibility: visible;
}
.component-once-show .content-list{
visibility: hidden;
position: absolute;
width: 100%;
border: 0px solid;
border-radius: 3px;
box-shadow: 0 0 5px 2px #eeeeee;
margin-top: 5px;
outline: none;
}
.component-once-show .content-list:focus{
visibility: visible;
}
.component-once-show .content-list:hover {
visibility: visible;
}
.component-once-show .content-list .list-item{
background: #ffffff;
height: 30px;
line-height: 30px;
transition: background .2s ease;
}
.component-once-show .content-list .list-item:hover{
background: #eeeeee;
}
实际意义
这个效果的实际意义我个人感觉还是蛮少的,tabindex
更多的被用于无障碍访问网页。适用这种效果的情况相当少,唯一的好处在于可以实现样式与行为的分离,让点击时不需要考虑样式而更加专注于行为。这个效果当然可以通过 javascript
中 的 addClass
来控制,相对来说代价也不大,而且更加灵活多变。而实现点击其他地方使得下拉列表消失的行为可以通过 tabindex
+ onBlur
事件来实现,而不需要考虑到将事件绑定在document
上的风险。可参看 jQuery 怎么实现点击页面其他地方隐藏菜单? Rezero 的回答。
后记
文章中若有不当之处,望指正。