我正在参加「掘金·启航计划」
最近在摸鱼时,有同事问了一个关于纯CSS实现手风琴的问题,心里想着:这都不会?菜鸡!没想到这个问题还挺有意思,之前没怎么注意过,盘算了半天终于实现了,感觉很有意思,给大家一下......
手风琴实现
先写页面结构:
<ul class="list">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
然后通过SCSS添加样式,考虑到不使用SCSS的开发者,这里贴一下CSS,因为本论坛文章有代码量限制,所以这里贴张图,具体的代码可以去下方《码上掘金》链接内部感受:
如果要添加hover手风琴效果,直接这样写:
.list li {
width: 200px;
transition: all .4s ease;
}
.list li:hover {
width: 600px;
flex-shrink: 0;
}
上述代码中在 hover 效果中添加了 flex-shrink 属性,主要是保证元素放大效果,如果不写的话就需要使用其它方式让出自己意外的兄弟元素缩小,相比其它方式而言这种写法是简单的。
看效果:
到这里是很简单的,这时候同事提出了需求:我想让最后一个元素默认保持最大,这该怎么实现,我心里想:你直接给它设置成被hover的状态不久好了么?像这样:
.list li:last-child, .list li:hover {
width: 600px;
flex-shrink: 0;
}
然后他微笑着说:你去试试。我已经察觉到不对劲了,试试就试试,果然效果出了问题,就是最后一个元素始终是最大的。这就导致 hover 到其它元素时,容器内只剩两个放大的元素。🤔怎么才能让其它 li 元素被 hover 时让最后一个元素缩小呢?
我开始去百度查找,没有找到相似用例,然后想着应该是可以解决的,就开始探索之旅
历程
:not
一开始看到这个非此即彼的关系,我立刻想到了使用非选择器,用它选择到没有被 hover 的元素,修改它们的宽度。但是实践起来就知道这是脱裤子放屁 —— 多此一举。大家可以将该代码放到码上掘金中试验一下:
.list li:not(:hover){
width: 100px;
}
我的目的是在某个 li 标签被 hover 时让最后一个元素缩小,但是按照上面的写法,如果该选择器生效,那么所有没有被 hover 的元素都会缩小,但是一开始就没有元素被 hover,思路有问题。
~选择器
通用选择器会选择到某个元素之后的同级元素,因为默认是最后一个元素处于展开的状态,那么我只要让其它元素被 hover 时选择到最后一个元素,不就可以控制它了么,哇,立马去写:
.list li:hover ~ :last-child {
width: 100px;
}
哈哈哈哈,效果有了,立马喊过来同事,开始炫耀我的技巧,受到表扬了呢,我以为这就结束了,没想到他还是说出了我在炫耀时有意避开的问题:“如果一开始展开的不是最后一个元素呢?”,我:“...😓”
好吧,继续探索,选择除自己以外的所有兄弟元素,emm...,从来不记得有这么个CSS选择器,想去百度,但是觉得如果我可以百度到,同事应该也可以啊,就停下来想了想,又想到了 :not。要让某个子元素在其它元素不存在 hover 状态的情况下出现样式,然后具有该状态时销毁样式。🤔因为是同级元素没有办法使用排它,但是父元素是可以选择到所有子元素的,那么可以通过父元素选择到除了激活元素之外的所有元素,那岂不是 —— 解题了?
/* 利用父元素选择除了被hover的元素之外的元素 */
.list li:not(:hover){
width: 100px;
flex-shrink: 1;
}
然后发现最开始不就是这么选的么,不就是让所有元素缩小么,绕一圈怎么绕回来了,刚准备emo,就想到可以通过 :not 选择器屏蔽某个预展开的元素,其它元素本来就因为 flex 布局的因素会缩小,我只是要更改那个展开的元素就行了,所以改为:
.list li:nth-child(2):not(:hover){
width: 200px;
flex-shrink: 1;
}
然后发现好像和最开始没有什么差别,所有元素都是初始状态,默认展开的元素权重低于上方代码不会被展开。额,还差什么呢?对,激活状态。只有当某个子元素被激活时才需要将默认展开的元素还原,也就是说需要父元素感知子元素被 hover 的状态。
emmm...,我忽然间想到 —— 当子元素被 hover 时,父类其实也是有被 hover 状态的,这不是冒泡事件么。对不起,是我菜了,所以当某个子元素被添加了状态时,父元素理应也会有相同的状态,那么 —— (o゜▽゜)o☆[BINGO!]
.list:hover li:nth-child(2):not(:hover){
width: 200px;
flex-shrink: 1;
}
测试了一下,嗯,是想要的效果,就是来来回回让我想了快一个小时,就显得有点菜,不过,不影响我去同事面前装🏆:哼,这你都想不出来,真👎。
总结
虽然是个小小的需求,但是当时确实触碰到了我的知识盲区,在百度搜索答案的过程中,我甚至学习到了一些新的CSS选择API,例如::is、:where、:has 等等,因为我一开始就奔着找如果选择出自己以外的其它兄弟元素去的,也一直在疑惑为什么没有类似的选择器,最后才明白原来这是个很奇葩的需求,本来就可以通过父元素选择到所有的子元素,加上 :not 伪类就可以了,我一开始也这么写过,属实是陷入局中了。
工作中很多时候也是这样,希望各位掘友以后遇到这种情况可以拨云见日,跳出局外,然后一招制胜,轻松的解决问题,祝愿各位怎么写代码都不会有 bug,头发越来越密~