因为最近在写项目的UI库,遇到自定义滚动条这一个槛还是卡了我挺久的,主要卡在了如何自动监听内容变化并更新滚动条高度。市面上基本所有的滚动条插件都没有实现这一点,最后面扒了element的源码才最终解决。本文主要讲的也是这个。
首先,我们先把需要实现的功能先确定下来。
- 鼠标左键点击可以拖动
- 鼠标滑轮滚动
- 内容发生变化,自动更新滚动条长度
- 提供开发者一个滚动回调的接口
前面两点依靠原生滚动条其实比较简单,但是在第三点上实在是卡了我好久,想了好久都没有想出来。最后还是看了element源码才实现成功。
接下去我会以垂直滚动条为例(水平滚动条基本同理),实现一个自定义的滚动条出来。我争取把其中原理细节讲清楚。
1、搭建好基本的样式框架
开始我们先把HTML和样式写好
<div class="scrollbar">
<div class="scrollbar-content">
<ul class="box">
<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
</ul>
</div>
<div class='scrollbar-bar'>
<div ref="thumb" class="scrollbar-thumb"></div>
</div>
</div>
滚动条的框架如上面所示,接下午我会以简称wrap
,bar
,thumb
进行简称
wrap
:内容区域包裹框bar
: 包裹区域中自定义滚动条的滚动框thumb
:自定义滚动条
开始之前要大家可以先记住一点,我们并不是不用原生滚动条,实际上我们所有的操作都需要依靠原生滚动条才能实现。只不过它隐藏在了暗处,而让UI更好看的自定义滚动条出现在明处。
1.1计算出滚动条的宽度。
第一步我们先将原生的滚动条隐藏掉。但是这里涉及到第一个问题,那就是不同浏览器的下滚动条宽度是不一样的。我们需要准确的知道,如果wrap
产生了滚动条,那它的宽度是多少。
先写一个获取到区域内滚动条的宽度(scrollWidth)的回调函数getScrollWidth
,获取到滚动条高度之后,
function getScrollWidth(){
const outer = document.createElement("div");
outer.className = "el-scrollbar__wrap";
outer.style.width = '100px';
outer.style.visibility = "hidden";
outer.style.position = "absolute";
outer.style.top = "-9999px";
document.body.appendChild(outer);
const widthNoScroll = outer.offsetWidth;
outer.style.overflow = "scroll";
const inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
const widthWithScroll = inner.offsetWidth;
outer.parentNode.removeChild(outer);
scrollBarWidth = widthNoScroll - widthWithScroll;
return scrollBarWidth;
}
获取到滚动条的宽度scrollBarWidth
之后,通过再来设置wrap
的css样式,通过marginRight
将滚动条移动到视线之外
wrap.style.overflow = scroll;
wrap.style.marginRight = -scrollWidth + "px";
1.2计算出滚动条的高度。
第二步我们需要计算出滚动条的高度。计算方法也很简单,元素高度scrollHieght
/内容高度clientHeight
,得出来的就是滚动条所占的百分比。
因为内容高度经常变更,我们可以写一个更新滚动条高度的回调函数updateThumb
,方便后期s随时调用。
function updateThumb(){
let heightPercentage = (wrap.clientHeight * 100 / wrap.scrollHeight);
thumb.style.height = heightPercentage + "%";
}
到了这一步,基本上一个滚动条的基本样式已经出来了。接下去我们要实现它的使用功能。
2、添加滚动条滑动功能
到这里我们已经可以看到成型的滚动条的UI界面了,但是仍然缺少滚动和拖动的功能。关键点是在于如何去监听滚动条的变化。
2.1滚轮滑动
还记得文章开头说过,我们所有功能的实现都依赖隐藏起来的原生滚动条。如果大家理解了我上面说的话,那么问题就简单了。当我们开始滑动滚轮的时候,隐藏在暗处的原生滚动条也会同时滚动,此时便会触发原生滚动条的scroll事件。
这里可以再详细说明下。只要元素的scrollTop发生变化,就必然会触发scroll事件。所以我们操作滚轮,其实本质上是改变元素的scrollTop。
所以我们只需要写一个相应的回调函数handleScroll
,在每次触发回调的时候,实时修改我们自定义滚动条的样式就行了。
function handleScroll(){
this.moveY = (wrap.scrollTop *100 / wrap.clientHeight);
//通过计算出来的百分比,然后对滚动条执行translate移动
thumb.style.transform = "translateY" + moveY;
},
wrap.addEventListener('scroll',handleScroll);
2.2点击滚动框,滚动条及内容移动到相应位置
接下去我们实现第二个功能。当我们点击滚动框的一个位置时,滚动条也会跳到这个位置,同时内容位置也会发生改变。
第一步先获得点击的y坐标,然后计算出和滚动框bar
顶部的距离,再算出占滚动框的百分比,这个百分比就是滚动条的高度
function clickTrackHandle(e){
//获得点击位置与滚动框顶部之间的距离
const offset = Math.abs(e.target.getBoundingClientRect().top - e.clientY)
//让点击位置处于滚动条的中间
const thumbHalf = thumb.offsetHeight / 2;
//计算出滚动条在滚动框的百分比位置
const thumbPositionPercentage = (offset - thumbHalf) * 100 / wrap.offsetHeight;
//通过改变scrollTop来操作。所有操作滚动条的最后一步都是通过handleScroll来实现
wrap.scrollTop = (thumbPositionPercentage * wrap.scrollHeight / 100);
}
bar.addEventListener("click",clickTrackHandle);
只要scrollTop值发生变化就会触发我们上一步写的回调。
2.3拖动滚动条,移动内容
接下来我们再去实现手动拖拽滚动条去实现移动内容,这个知识点就是拖拽的知识点,不过在看源码的时候发现element
的习惯很好,他是在当你点击滚动条的时候绑定拖拽,然后松开的时候取消绑定。
function mouseMoveDocumentHandler(){}; //实时记录滚动条位置的拖拽函数
//当点击滚动条时
document.addEventListener("mousedown",mouseMoveDocumentHandler);
document.onselectstart = false; //同时阻止选中
//当松开滚动条时
document.removeEventListener("mousedown",mouseMoveDocumentHandler);
document.onselectstart = null; //同时阻止选中
因为这一块代码比较多,就不贴文章里,大家可以直接链接里看就是了。
查看拖动滚动条的效果
3、实现滚动条随内容实时更新
第二章讲的主要都是实现滚动条功能,这一章讲的是纠结 😖我很久的功能。
因为滚动条的高度并不是我们一开始能够确定的,它需要在dom内容渲染出来之后才能确定。而且有时候随着内容的变化,还需要实时改变滚动条的高度。再看了市面上的滚动条之后,发现基本都没有满足这一功能。
事实上缺少了这一点,使用起来是缺少视觉交互的。举个例子,加入一个原来有滚动条的元素因为内容减少导致了滚动条小时,但是自定义滚动条因为没有检测到变化仍然存在,那就会给用户造成困扰。
我不希望每次更新内容都要通过加一步回调函数来更新一下滚动条,而是希望它自己实时更新。在网上没有找到答案之后,最终去翻了element源码,研究了好久,总算找到了想要的答案。
关键点就在于我能前面之前说的那一句话——如果我们改变元素的scrollTop,是会触发scroll事件。
那么只要我内容发生了一点变化,滚动条必然会变长或者变短。那么在滚动条长度变化时,scrollTop自然发生了改变(滚动条消失则scrollTop变为0),那么就会触发scroll的回调函数,那么我们就自动监测到了啊 😊。
在明白了这一点后,却又冒出来一个问题。正常情况下,滚动条不可能出现在最底部啊,那怎么办呢?
element
选择了自己造一个置于底部的滚动条来满足自己需求。
<script>
const ul = document.getElementById("ul");
const resizeTrigger = document.createElement("div");
resizeTrigger.className = "resize-triggers";
resizeTrigger.innerHTML = '<div class="expand-trigger"><div><div></div></div></div>';
ul.appendChild(resizeTrigger);
const resetTrigger = function (element) {
const trigger = element.__resizeTrigger__;
const expand = trigger.firstElementChild;
const expandChild = expand.firstElementChild;
expandChild.style.height = expand.offsetHeight + 1 + 'px';
expand.scrollTop = expand.scrollHeight;
};
ul.addEventListener("scroll",function(){
resetTrigger(this);
},true)
</script>
ul
是我们包裹内容的DOM元素。
配合着css来看,第一段JS我们创建出了resizeTrigger
这个div,并且我们将他的height:100%
。这样子如果内容发生变化,resizeTrigger
永远和父元素ul
同时改变高度。这里设置成高度100%非常重要,这样子才能主动同步到内容的变化。
注意到resizeTrigger
里面还有有一个父子元素expand
和expandChild
。在第二段JS的resetTrigger
函数中。然后设置expandChild
的高度超过父元素expand
的高度,促使expand
产生滚动条。然后我们再将滚动条的scrollTop
设置为最大,这样子滚动条就会出现在滚动区域resizeTrigger
的最底部了。
现在我们做到了将滚动条设置在了最底部,所以只要内容发生了变化,那么滚动条的scrollTop必然也会发生变化。
最后一段代码就是scroll的监听。当监听到scrollTop
值发生变化时,触发相应的回调函数。
所以这块代码最后的逻辑其实是这样的。内容改变 –> ul
高度改变–> resizeTrigger
高度改变 –> expand
滚动条的scrollTop
发生变化 –> 触发scroll的回调函数,在函数里面调整再次调整滚动条的高度,保证滚动条高度正确。
通过这三段代码,我们也基本实现了自动监听内容变化来更新滚动条。
通过两个小蓝框产生的滚动条来帮助监听内容变化
4、实现组件化,方便开发者使用
经过以上3大步基本上是可以实现一个自定义的滚动条的。上面的代码是面向原生js的。在我们的项目里面,实现第4点是通过封装成一个scrollbar的的组件,在项目里面进行使用。
这一条要求因为不同框架展现方式都不一样,所以就不详细贴代码了因为自己项目用的是一个Vue框架,所以是个Vue组件,有需求可以自己去看。
好了文章就到此结束了,在看人家源码的过程中也学到了许多。比如使用JSX来编写组件;scroll监听其实就是判断scrollTop;比如通过自己造滚动条的方法监听scrollTop来实现自动更新。最后通过写文章,对一些新的知识点理解还是加深了许多。
今天的文章实现一个自定义滚动条分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14633.html