likes
comments
collection
share

手撸一个滚动条组件

作者站长头像
站长
· 阅读数 10

滚动条组件的地位比较尴尬,它是属于既熟悉又陌生的范畴。“熟悉”是因为它很常见,常见到跟空气一样,每天都会接触;“陌生”是因为市面上成熟的组件库里似乎都没有“滚动条组件”的身影。

那为什么要撸它呢?肯定是业务需要呗,但是又很纠结,到底是实现一个滚动条,还是直接修改滚动条的默认样式。所以我决定先实现一个,然后再决定要不要用它。

最终我的结论是,对于我们部门的业务以及工作环境来说,还是直接修改滚动条的默认样式吧。

一、实现效果

手撸一个滚动条组件

二、实现的功能

  • 支持垂直方向上的滑动
  • 支持垂直方向上的快速定位

三、预制知识点

  • clientHeight与 offsetHeight 的区别。
  • pageY 与 offsetY的区别。

手撸一个滚动条组件

从上图我们可以看到,clientHeight与offsetHeight的区别仅仅在于是否包含滚动条。

假如此时我点击了div元素,还是以上图为例,那么此时pageY是相对于整个html文档而言的,而offsetY是相对于元素(即div)边框而言的。

四、实现思路讲解

滚动条的本质其实就是缩略图,相当于王者荣耀里的小地图。想要实现这个功能并不难,只要按照相应比例进行缩放就可以了。

手撸一个滚动条组件

通过上图我们发现,子元素的高度超过父元素的高度,导致父元素产生了右侧的滚动条。右侧的滚动条分为2个部分,分别是:

  • 白色的“滚动条轨道”。
  • 绿色的“滚动条滑块”。

长页面向上滑动,绿色滑块向下滑动。长页面向上滚动到底,绿色滑块向下滚动并且同时也到达了白色轨道的底部。

这就意味着,长页面滚动了多少,绿色滑块也需要按照相同比例在轨道里滚动

如何计算这个比例呢?

滚动过的距离 / 长页面高度 === 滑块滚动过的距离 / 滑块轨道高度

根据这个等式,我们发现初始化滑块轨道高度是必要操作的事情

解决了滑块的滚动距离的计算,我们还需要解决滚动条滑块的高度。这个高度也是一样,根据下面的比例来求解:

父元素的高度 / 长页面的高度 === 滑块的高度 / 滑块轨道的高度

因为我们知道 滑块轨道的高度,所以滑块的高度也可以得到答案。

经过上面的分析,我们来总结一下实现垂直方向上的滚动条的步骤:

  • 初始化滚动条轨道的高度。
  • 确定滚动条滑块的高度。
  • 监听父元素滚动事件,根据比例得出滚动条滑块的位置。

五、代码实现

<div class="father-box">
    <div class="child-box">1</div>
    <div class="child-box">2</div>
    <div class="child-box">3</div>
    <div class="child-box">4</div>
    <div class="child-box">5</div>
</div>

样式如下:

.father-box {
    position: relative;
    width: 200px;
    height: 500px;
    box-sizing: border-box;
    overflow-y: auto;
    background: cornflowerblue;
    margin-left: 300px;
    margin-top: 200px;
}
.child-box {
    box-sizing: border-box;
    width: 100%;
    height: 300px;
    border-bottom: 1px solid #ccc;
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: 600;
}

此时如果我们的运行系统如果是windows,那么效果应该是下面这样:

手撸一个滚动条组件

我们看到,windows下的滚动条的默认样式其实也没那么难看,但是有人的地方就有江湖,我们还是要抱着试一试的态度看看自己能不能抹平各种系统在这方面的差异,而且这个实现经历对你也无害。

修改html结果如下:

<div class="father-box" onscroll="scroll1()">
    <!--
        child-box元素不变,新增元素如下:
        
        scrollbar-line:滚动条轨道
        scrollbar-handler:滚动条滑块。
    -->
    <div class="scrollbar-line">
        <div class="scrollbar-handler"></div>
    </div>
</div>

新增样式如下:

.scrollbar-line {
    width: 10px;
    height: 100%;
    position: absolute;
    top: 0px;
    right: 0px;
    background: darkgray;
}
.scrollbar-handler {
    width: 100%;
    background: teal;
    border-radius: 5px;
}

此时我们的样式如下:

手撸一个滚动条组件

接着我们应该进入正题了,用js实现如下功能:

  • 初始化滚动条滑块高度。
  • 监听父元素滚动事件,根据比例得出滚动条滑块的位置。
// 初始化滚动条滑块的高度
function initScrollbar(){
    let fatherHeight = document.querySelector('.father-box').clientHeight;
    // 内容总高度
    let contentHeight = document.querySelector('.father-box').scrollHeight;
    let defaultRatio = fatherHeight / contentHeight;
    document.querySelector('.scrollbar-handler').style.height = (defaultRatio * fatherHeight) + 'px';
}

function scroll1(){
    // 滑块
    let scrolbarHandler = document.querySelector('.scrollbar-handler');
    // 滑槽
    let scrollbarLine = document.querySelector('.scrollbar-line');
    // 滚动过的距离
    let scrolledDistance = document.querySelector('.father-box').scrollTop;
    // 内容总高度
    let contentHeight = document.querySelector('.father-box').scrollHeight;
    // 滑槽总高度
    let scrollbarLineHeight = scrollbarLine.getBoundingClientRect().height;

    let defaultRatio = scrollbarLineHeight / contentHeight;
    
    // 固定住滚动条的轨道
    scrollbarLine.style.transform = 'translateY(' + scrolledDistance  + 'px)';
    // 在固定住轨道的基础上,正确的设置滑块的位置
    scrolbarHandler.style.transform = 'translateY(' + defaultRatio * scrolledDistance + 'px)';
}

window.addEventListener('DOMContentLoaded', function (){
    initScrollbar();
})

紧接着,修改html内容如下:

<div class="father-box" onscroll="scroll1()">
    <div class="child-box">1</div>
    <div class="child-box">2</div>
    <div class="child-box">3</div>
    <div class="child-box">4</div>
    <div class="child-box">5</div>
    <div class="scrollbar-line">
        <div class="scrollbar-handler"></div>
    </div>
</div>

至此,我们完成了自定义滚动条的基础功能,效果如下:

手撸一个滚动条组件

此时我们发现,这个滚动条组件还有2件事情没有干,分别是:

  • 隐藏原生的滚动条
  • 没有实现点击定位的功能

继续修改默认滚动条样式如下:

.father-box::-webkit-scrollbar {
    width: 0px;
}

继续添加点击定位功能如下:

function scrollTo1(event){
    // 滑块
    let scrolbarHandler = document.querySelector('.scrollbar-handler');
    // 滑槽
    let scrollbarLine = document.querySelector('.scrollbar-line');
    // 滚动过的距离
    let scrolledDistance = document.querySelector('.father-box').scrollTop;
    // 内容总高度
    let contentHeight = document.querySelector('.father-box').scrollHeight;
    // 滑槽总高度
    let scrollbarLineHeight = scrollbarLine.getBoundingClientRect().height;

    // 父级元素
    let fatherElement = document.querySelector('.father-box');

    let defaultRatio = scrollbarLineHeight / contentHeight;

    // 反算实际长页面需要滚动多少像素
    fatherElement.scrollTo(0, contentHeight * event.offsetY / scrollbarLineHeight);

}

修改dom如下:

<div class="father-box" onscroll="scroll1()">
    <div class="child-box">1</div>
    <div class="child-box">2</div>
    <div class="child-box">3</div>
    <div class="child-box">4</div>
    <div class="child-box">5</div>
    <div class="scrollbar-line" onclick="scrollTo1(event)">
        <div class="scrollbar-handler"></div>
    </div>
</div>

至此,我们实现了文章开头 滚动条组件的功能了。

六、结尾

本文我们通过缩放比例的方式实现了一个自定义的滚动条,然后通过比例关系来确定滚动条滑块的高度,滑块的滚动位置,以及反写实际元素的滚动距离 来实现了滚动条组件具备的基本功能。

结尾的结尾,又到了该说再见的时候啦,如果我写的内容对你有帮助,还请不要吝啬手里的小赞赞,那么我们下期再见啦~~