引子
想直接看实现的可跳过引子
在完成公司的项目的时候,某块交互要求点击小箭头出现下拉列表,而点击其他地方的时候,这个下拉列表消失,就像这样:
$(".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 的回答。
后记
文章中若有不当之处,望指正。