前端经常遇到各种浏览器的兼容问题,无论是浏览器api层面的还是样式层面的,都非常让人头痛。尤其是当产品提出“为什么滚动条在各个浏览器上面的样式不一样,能不能统一下”时,我彻底悟了,与其无休止的调各个浏览器滚动条的样式问题,为什么我不自己写一个滚动条呢,于是这篇文章就出来了。

手写滚动条样式

滚动条样式其实很简单,不过在写滚动条样式的时候我们需要将浏览器自带的滚动条隐藏掉

./style.css
1
2
3
4
5
6
7
8
9
10
11
*{
/* 隐藏滚动条 */
<!-- 火狐 -->
scrollbar-width: none;
<!-- E 与微软浏览器 -->
-ms-overflow-style: none;
}
<!-- chrome -->
*::-webkit-scrollbar {
display: none;
}

滚动条样式(这里我是用div来模拟滚动条的)

./xxx.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<!-- 滚动容器 -->
<div class="scroll_container" @scroll="onScroll" :style="{
transform: `translate(0px, ${scrollBarTop}px)`
}">
<!-- 滚动容器内容区域 -->
<div class="scroll_view">
// 滚动区域的内容
</div>
<!-- 滚动条 -->
<div class="scroll_bar"></div>
</div>
</template>
./xxx.css
1
2
3
4
5
6
7
8
9
10
11
12
.scroll_bar {
display: block;
position: absolute;
top: 0;
z-index: 999;
width: 6px;
right: 0;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.3);
transition: opacity 0.1s ease-in-out;
hieght: 120px;
}

这样一个滚动条的样式就出现在我们屏幕的右边了

实现滚动条滚动

因为我用的是vue3,所以这部分都是按照vue3的方式来写。

实现思路

怎么让屏幕滑动到最低端时滚动条也一起滑动到最低端,这里我说一下大概的思路。
我这里是用的是 css 中的 transform 来实现的。
首先我们得先知道滚动容器可以滚动多远(滚动最大距离),这点我们可以通过滚动容器内容区域的高度减去滚动容器本身的高度,这里需要注意的是当滚动容器中的内容不可滚动时的情况(滚动容器内容区域的高度小于滚动容器本身的高度)
得到了滚动最大距离之后就可以计算滚动条应该位移的距离了,我们通过监听scroll事件得到当前滚动的距离,用当前滚动距离/最大滚动距离可以得到当前滚动距离的百分比,然后用这个百分比*(滚动容器内容区域的高度 - 滚动条本身的高度)就得到了滚动条应该滚动的距离了。
大致思路就是这样的,听起来很啰嗦,直接看代码吧!

./xxx.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// js部分
// 滚动容器
const scrollREF = ref<HTMLDivElement>();
// 滚动容器内容区域
const scrollContentREF = ref<HTMLDivElement>();
// 滚动条应该滚动的距离
const scrollBarTop = ref(0);

const onScroll = () => {
if (scrollREF.value && scrollContentREF.value) {
// 先计算可以滑动多远
const scrollRange =
scrollContentREF.value.clientHeight > scrollREF.value.clientHeight ?
scrollContentREF.value.clientHeight - scrollREF.value.clientHeight :
0;
scrollRange > 0 &&
(scrollBarTop.value = (scrollREF.value.scrollTop / scrollRange) * (scrollContentREF.value.clientHeight - scrollHeight.value));
}
};

其实到这里滚动条基本功能就已经实现了,还有细节可以优化

细节优化

滚动条高度问题
存在的问题

其实上面已经属于一个勉强能用的状态,但是有一个很致命的问题–滚动条的高度是固定的。浏览器自带的滚动条会根据滚动内容区域的高度进行变化(内容多时: 滚动条变矮, 内容少时,滚动条变高),那样更符合人类的直觉。滚动条固定还有一个破坏体验感的就是–当内容比较少时滚动条又太矮了,滚动条的滚动速度就会太快;当内容比较多时滚动条又比较高时,很察觉滚动条的滚动。

实现思路

其实有很多解决方法,只要是大概符合反比例函数的模型的其实都是可以的(当滑动区域越小时,滚动条高度越高;当滑动区域越大时,滚动条高度越低)。
我的解决方法是滚动容器高度/滚动内容区域高度*滚动容器高度

./xxx.vue
1
2
3
4
5
6
7
8
9
10
11
// 滚动条应该滚动的距离
const scrollHeight = ref(0);
if (scrollContentREF.value && scrollREF.value) {
// 我这里进行了取整,因为后面需要减去滚动条的高度,尽量避免高度出现小数
// 关于这里需不需要处理 (滚动容器高度/滚动内容区域高度 ) > 1 的情况,
// 我认为不需要,因为大于1时就证明滚动容器内容区域无法滚动,无法滚动自然就无法触发scroll回调
scrollHeight.value = Math.trunc(
(scrollREF.value.clientHeight / scrollContentREF.value.clientHeight) *
scrollREF.value.clientHeight
);
}

模板代码中需要将scrollHeight加进去

  • 如果你的滚动区域是静态数据(页面加载成功滚动区域的数据就不会变化)
    可以直接将面的那段代码写入生命周期中(onMounted)。
  • 如果你滚动区域是动态变化的数据(可以滚动新增内容的那种)
    你可以将上面的代码封装成一个函数,在每次新数据加载好了之后调用一次。
其他可以优化的地方
  1. 滚动条在非滚动时不可见
./xxx.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 我这里是通过透明度实现
const scrollOpacity = ref(0);
let scrollDisplayTimer: Timer = null;

// js部分
// 滚动容器
const scrollREF = ref<HTMLDivElement>();
// 滚动容器内容区域
const scrollContentREF = ref<HTMLDivElement>();
// 滚动条应该滚动的距离
const scrollBarTop = ref(0);
const scrollOpacity = ref(0);

const onScroll = () => {
if (scrollREF.value && scrollContentREF.value) {
// 先计算可以滑动多远
const scrollRange =
scrollContentREF.value.clientHeight > scrollREF.value.clientHeight ?
scrollContentREF.value.clientHeight - scrollREF.value.clientHeight :
0;
scrollRange > 0 &&
(scrollBarTop.value = (scrollREF.value.scrollTop / scrollRange) * (scrollContentREF.value.clientHeight - scrollHeight.value));
// <!-- 控制滚动条是否显示 start -->
scrollOpacity.value = 1;
if (scrollDisplayTimer) {
clearTimeout(scrollDisplayTimer);
}
scrollDisplayTimer = setTimeout(() => {
scrollOpacity.value = 0;
}, 1500);
// <!-- 控制滚动条是否显示 end -->
}
};

模板代码中需要将scrollOpacity加进去

最终代码

  • 模板部分
./xxx.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<!-- 滚动容器 -->
<div class="scroll_container"
@scroll="onScroll"
:style="{
transform: `translate(0px, ${scrollBarTop}px)`,
height: `${scrollHeight}px`,
opacity: scrollOpacity,
}">
<!-- 滚动容器内容区域 -->
<div class="scroll_view">
// 滚动区域的内容
</div>
<!-- 滚动条 -->
<div class="scroll_bar"></div>
</div>
</template>
  • js部分
./xxx.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 滚动容器
const scrollREF = ref<HTMLDivElement>();
// 滚动容器内容区域
const scrollContentREF = ref<HTMLDivElement>();
// 滚动条应该滚动的距离
const scrollBarTop = ref(0);
// 滚动条透明度
const scrollOpacity = ref(0);
// 滚动条高度
const scrollHeight = ref(0);

// 计算滚动条高度
const countscrollHeight = () => {
if (scrollContentREF.value && scrollREF.value) {
// 我这里进行了取整,因为后面需要减去滚动条的高度,尽量避免高度出现小数
scrollHeight.value = Math.trunc(
(scrollREF.value.clientHeight / scrollContentREF.value.clientHeight) *
scrollREF.value.clientHeight
);
}
}
//滑动监听事件
const onScroll = () => {
if (scrollREF.value && scrollContentREF.value) {
// 先计算可以滑动多远
const scrollRange =
scrollContentREF.value.clientHeight > scrollREF.value.clientHeight ?
scrollContentREF.value.clientHeight - scrollREF.value.clientHeight :
0;
scrollRange > 0 &&
(scrollBarTop.value = (scrollREF.value.scrollTop / scrollRange) * (scrollContentREF.value.clientHeight - scrollHeight.value));
// <!-- 控制滚动条是否显示 start -->
scrollOpacity.value = 1;
if (scrollDisplayTimer) {
clearTimeout(scrollDisplayTimer);
}
scrollDisplayTimer = setTimeout(() => {
scrollOpacity.value = 0;
}, 1500);
// <!-- 控制滚动条是否显示 end -->
}
};

onMounted(() => {
countscrollHeight();
});
  • css部分
./xxx.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
*{
/* 隐藏滚动条 */
<!-- 火狐 -->
scrollbar-width: none;
<!-- E 与微软浏览器 -->
-ms-overflow-style: none;
}
<!-- chrome -->
*::-webkit-scrollbar {
display: none;
}
.scroll_bar {
display: block;
position: absolute;
top: 0;
z-index: 999;
width: 6px;
right: 0;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.3);
transition: opacity 0.1s ease-in-out;
}

如果对你有帮助可以关注本站哦!!!