【CSS】如何复原被隐藏的滚动条?记一个看似简单的样式问题所引发的一系列思考

壹 ❀ 引

故事的起因是这样的,某一个同事在封装了一个TableList组件,用于做表格视图渲染,但出于研发经验考虑上,他可能觉得表格若出滚动条可能会引发某些不可预估的小问题(毕竟一个基础组件会被用于很多地方),省掉滚动条也避免很多宽度不够的适配问题,于是选择了暴力做法,直接在组件样式中隐藏了双向滚动条...

// 滚动条有两个方向,width隐藏纵向,height隐藏横向
.scrollbar::-webkit-scrollbar {
     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 {
     0;
    height: 0;
}
div.scroll {
    margin: 20px auto;
    height: 100px;
     130px;
    overflow: auto;
}

既然滚动条是因为宽高都没了,那我让这个样式不生效不就好了,直接复写样式进行覆盖:

#test::-webkit-scrollbar {
     unset;
    height: unset;
}

刷新页面,然后让我产生了两个疑惑点,第一,滚动条并未出现,第二,样式怎么没有覆盖的痕迹:

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

难道用unset不行?或者权重不够?带着疑惑我将unset替换成具体的数值,升值在后面加上了!important

#test::-webkit-scrollbar {
     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结构,发现确实有滚动条的站位,尝试拖动,果然存在。那么现在我们可以得出两条结论:

  1. 滚动条一旦被隐藏,复原时需要重写它所有的属性样式,不然它会有站位但不可见。
  2. 我们前面的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开始选择,但是不包括classnewli,所以除了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) {...}

那么到这里,我们既没有影响现有组件在其它页面的表现,也没有很麻烦的复写滚动条默认样式,同时也为组件增加了一个开启和关闭隐藏滚动条的自选配置,一举多得,此问题就告一段落了。

肆 ❀ 总

其实单站在解决问题的角度,暴力复写其实早就解决了,只是在追求一个更小影响更巨接受度的做法上,对方案进行了多次探索与推翻。所以明明是一个样式问题,但由于我对于滚动条的陌生以及部分基础知识的遗忘,这个问题我足足玩了一天....

但事实上,我们在定义一个基础组件时,确实不应该在组件内部定义一些外部很难复写的样式,或者很难扭转的逻辑,这会很让后续的开发异常头疼,当然我猜测,写这块的作者估计也不会想到滚动条复原会如此麻烦。

杂记 ❀ 迫于无奈,我吃了别人女友点的外卖

分享一个今天让我啼笑皆非的故事。由于今晚公司有一个大版本需要发布,所有研发都需要待命,所以晚6点的时候我点了一份外卖。因为疫情影响,外卖是没办法送上楼的,大楼物业也是在楼下安置了按楼层划分的桌子用于放外卖。考虑到晚上点外卖的人并不多,所以我也是不慌不忙等了好一会才下去取,当走到桌子前我傻眼了,9-15楼的桌子上一共三份外卖,反反复复看了四遍,确认过眼神,我是外卖被偷的人!!

但我又考虑到可能是外卖小哥放错了,于是立马给小哥打了个电话,当小哥精确说出我所点的外卖店家名称后,我确信他没放错,外卖确实是被人拿走了。小哥说让我等等,等他回来确认下再找大楼调监控,我想着也就20块钱的事,就不用那么麻烦了,大家都是打工人也第都不容易,赔偿就不用了,只是今晚,这个世界多了一个饿肚子的人。

当我走进电梯后来了一个陌生电话,我原以为是外卖小哥打过来告诉我外卖真的放错了,不禁内心狂喜!!!接通后,对方就是那个拿错外卖并已经吃完的人....他说他跟我同姓,然后我和他的公司只相隔一层,所以我俩的外卖在同一个桌子,晚上他也没多想,看到姓氏正确就拿上去吃掉了,吃完了才发现吃错了外卖,跟我道歉的同时表示要赔偿我,并把他的外卖给我吃。我说赔偿就不必了,把你的饭给我吃就行;他让我在15楼等会,出于歉意,他会帮我送上来。

电话挂断后我心里一想,不对啊,自己点的外卖自己难道不清楚吗,怎么吃完才发现,这什么脑回路...但又一想到有饭吃就行了,不管那么多了,等了一会后,电话另一头的人真的送外卖上来了,他说他的外卖是他女朋友帮他点的,所以其实他也不知道具体点的什么,吃完了才发现号码不对。我接过外卖一看,我自己点的拌饭,他女朋友给他点的也是拌饭,只是店家不同,而且我俩一个姓,拿错也确实情有可原了。

迫于无奈,我吃了她女朋友点给他的外卖,但不得不说,45一顿的外卖还真就比20一顿的要香!虽然吃外卖的时候,被迫还吃了一顿狗粮。

PS:封面原图地址

原文地址:https://www.cnblogs.com/echolun/p/15293078.html