
壹 ❀ 引
故事的起因是这样的,某一个同事在封装了一个TableList组件,用于做表格视图渲染,但出于研发经验考虑上,他可能觉得表格若出滚动条可能会引发某些不可预估的小问题(毕竟一个基础组件会被用于很多地方),省掉滚动条也避免很多宽度不够的适配问题,于是选择了暴力做法,直接在组件样式中隐藏了双向滚动条…
// 滚动条有两个方向,width隐藏纵向,height隐藏横向.scrollbar::-webkit-scrollbar {width: 0;height: 0;}
但事实上对于纵向都还好,用户能感知到有内容被页面遮住了,即便看不到滚动条,也可以利用鼠标滚轮控制上下滚动。可是鼠标又不能横向滚动,这让没触摸板的PC用户怎么玩?于是就有客户提单,说XX页面的XX功能横向缺少滚动条无法滚动(鼠标没滚动条拖动),内容看不全。看似挺简单的问题,引发了一系列的坑,以及触碰到了我对于滚动条的知识盲区,那么本文做个简单的记录。
贰 ❀ 隐藏的滚动条如何恢复默认
我将通过一个例子来演示这个过程,比如下面这段代码展示了一个滚动条被隐藏的表格,因为我电脑有触摸板,所以还是可以通过手势进行拖拽:
<div id="test" class="scroll">
<table border="1">
<tr>
<th>Month</th>
<th>Savings</th>
<th>User</th>
</tr>
<tr>
<td>January</td>
<td>$100</td>
<td>Echo</td>
</tr>
</table>
</div>
#test::-webkit-scrollbar {
width: 0;
height: 0;
}
div.scroll {
margin: 20px auto;
height: 100px;
width: 130px;
overflow: auto;
}

既然滚动条是因为宽高都没了,那我让这个样式不生效不就好了,直接复写样式进行覆盖:
#test::-webkit-scrollbar {
width: unset;
height: unset;
}
刷新页面,然后让我产生了两个疑惑点,第一,滚动条并未出现,第二,样式怎么没有覆盖的痕迹:

正常来说,样式覆盖是会有中划线表示这条样式没起作用,比如我们覆盖宽度,你会发现生效的样式在上,被覆盖的样式被划掉了

难道用unset不行?或者权重不够?带着疑惑我将unset替换成具体的数值,升值在后面加上了!important:
#test::-webkit-scrollbar {
width: 12px !important;
height: 12px !important;
}
刷新页面,滚动条依旧没出现,样式也没有出现中划线,这下确实是有点触碰我知识盲区了,毕竟以前大部分场景都是做隐藏滚动条的活,这下来个还原滚动条的操作居然把我整不会了…
带着疑惑我去问了下前端大佬,毕竟他阿里8年开发经验,我想着总比我见多识广,结果他又把我想到的可能性又重试了一遍,然后一脸疑惑的看着我,也蒙圈了….然后他说了一句我非常认可以及极有帮助的话,当你对一个问题非常疑惑的时候,那说明你不够了解它,这时候你就应该去补充对应的知识,而不是继续盲目的尝试。于是我打开了::-webkit-scrollbar的MDN,也了解到除了能让滚动条隐藏外,其实还有定义滚动条滑块,滚动轨道等等滚动条属性:
::-webkit-scrollbar— 整个滚动条.::-webkit-scrollbar-button— 滚动条上的按钮 (上下箭头).::-webkit-scrollbar-thumb— 滚动条上的滚动滑块.::-webkit-scrollbar-track— 滚动条轨道.::-webkit-scrollbar-track-piece— 滚动条没有滑块的轨道部分.::-webkit-scrollbar-corner— 当同时有垂直滚动条和水平滚动条时交汇的部分.::-webkit-resizer— 某些元素的corner部分的部分样式(例:textarea的可拖动按钮).
处于好奇,我在样式中又添加了如下样式:
// 为滚动滑块添加背景色
#test::-webkit-scrollbar-thumb{
background-color: #e4393c;
}

奇迹出现了,现在多了一个红色的滑动块,我们可以拖动滚动条了,我突然恍然大悟!!在我设置unset时会不会滚动条其实存在了,但它无色无形我才没能感知到它的存在。
于是我去除了滑动块的颜色,查看了DOM结构,发现确实有滚动条的站位,尝试拖动,果然存在。那么现在我们可以得出两条结论:

- 滚动条一旦被隐藏,复原时需要重写它所有的属性样式,不然它会有站位但不可见。
- 我们前面的
unset样式覆盖其实有生效,滚动条样式覆盖很特殊,它不会有删除线。
结论虽然有了,但仍然不符合我的预期,我是希望取消隐藏后滚动条自己就恢复默认,但现在很明显我得把所有样式都复写一遍,这是我不愿意做的,当然到这里也要说一句,千万不要在一个高度复用的基础组件中写这种不可逆转的样式,真的很折磨人!!
叁 ❀ 让隐藏滚动条成为组件的可选配置
由于复写滚动条样式过于麻烦,这里我不得不想想其它更优的做法,此时脑袋中就蹦出了选择器:not(),由于组件是嵌入在一个个不同页面的,所以不同页面总会有一些特化的class,于是我就想到通过特化class配合:not()来让此页面的组件不要生效滚动条。但我有很长一段时间没写样式了,所以对于not选择器居然有点陌生,这里做个简单复习:
<ul>
<li class="new">1</li>
<li>2</li>
<li>3</li>
</ul>
li:not(.new) {
color: orange;
}

这应该是一个最基础的not选择器例子了,从li开始选择,但是不包括class是new的li,所以除了1之外,其余都是橘色。
我们来看第二个例子,可能这个例子会轻量颠覆你对于此选择器的认知:
<body>
<p>我是p标签</p>
<span class="span">我是span标签</span>
</body>
:not(p){
color: #e4393c;
}

哎?奇了怪了,我们样式明明写的是除了p标签之外的所有元素颜色是红色,那为什么p元素也是红色,查看一眼控制台的样式属性,inherited from body,啥意思?p标签的颜色继承于body,也就是说样式确确实实是过滤了p,但是它会对p标签的父级body生效,而颜色是可以继承的,最终导致p继承了body的颜色。

假设我们给p额外定义一个不同的颜色,情况就符合预期了:
p{
color: orange;
}
:not(p){
color: #e4393c;
}

所以到这里你会发现,not用于解决同级元素(比如上面的为li过滤部分特殊的li)非常好用,但如果not用于过滤全局的部分元素,那你就得考虑样式继承带来的额外影响,而我在解决项目问题时,肯定不能用全局过滤,可行的肯定是利用同级class来过滤,结果看了一眼并没有什么特化的同级类型,于是我又陷入了沉思….
这个问题的根本原因,就是同事偷懒写了一个不可逆转的样式,那我何不将这个样式作为此组件的一个属性呢?考虑到这个组件在其它地方也有使用,所以修改思路是隐藏滚动条默认开关是true,而需要隐藏的地方在外层传递flase即可,而只有在开关是true时才会在这个组件上添加一个同名class,然后再在样式中结合not让不需要隐藏滚动条的地方不要生效,大概思路是:
// 组件定义处
className={classnames("table-list", { 'show-scroll-bar': this.props.isShowScrollBar })}
// 使用组件的地方
<Component isShowScrollBar>
//样式部分,有show-scroll-bar这个class的table组件不会隐藏滚动条
.table-list:not(.show-scroll-bar) {...}
那么到这里,我们既没有影响现有组件在其它页面的表现,也没有很麻烦的复写滚动条默认样式,同时也为组件增加了一个开启和关闭隐藏滚动条的自选配置,一举多得,此问题就告一段落了。
肆 ❀ 总
其实单站在解决问题的角度,暴力复写其实早就解决了,只是在追求一个更小影响更巨接受度的做法上,对方案进行了多次探索与推翻。所以明明是一个样式问题,但由于我对于滚动条的陌生以及部分基础知识的遗忘,这个问题我足足玩了一天….
但事实上,我们在定义一个基础组件时,确实不应该在组件内部定义一些外部很难复写的样式,或者很难扭转的逻辑,这会很让后续的开发异常头疼,当然我猜测,写这块的作者估计也不会想到滚动条复原会如此麻烦。
PS:封面原图地址
