纯 css 实现一个点击出现下拉列表的效果

4,242 阅读5分钟

引子

想直接看实现的可跳过引子

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

点击出现下拉列表
其中,点击其他地方,然后实现下拉列表消失的代码是参照知乎上找的一段代码【侵删】(公司用的jquery):

$(".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 点击效果
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-buttonbutton 标签,也许是 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代码(其中小箭头是通过:aftercontent模拟的,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;
}
    

最终实现效果:

focus+tabindex实现点击出现下拉列表
可以看到还是有瑕疵,在点击下拉列表时,虽然下拉列表不会消失,但是小箭头方向改变了,由于css不能获取它之前的兄弟元素,所以无法控制小箭头的样式。不过整体上还是实现了下拉出现下拉列表的功能。 下面给出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 的回答。

后记

文章中若有不当之处,望指正。