在web开发中,自定义滚动条是个常见的需求,虽然浏览器原生的滚动条很强大并且在大多数场景下表现的很好,但某些时候我们仍然希望修改他的样式,比如变细一点,或者去掉圆角和轨道,又或者隐藏他们。这些都属于自定义行为,本篇文章将介绍自定义滚动条的几种实现思路,并着重讲解最流行的js方案。
上图是自定义的效果(视频在转换时降速了,其实非常快)
在正文开始前,我们先统一滚动条各个部分的名称。
一、实现思路
实现自定义滚动条的方式不止一种,这里列出三种方式。
1、css修改。
这是最简单的方式,你可以通过::-webkit-scrollbar这个css伪类选择器去修改滚动条样式,包括滚动条轨道、滑块以及上下箭头等,但它只支持webkit内核的浏览器,并且它不是css标准的一部分,这意味着除了浏览器兼容性问题外,将来还可能被浏览器厂商删掉并转而采用新标准。
2、自行实现滚动条部分,但scroll行为交给浏览器原生实现。
这种思路的关键是不能将容器的overflow设为hidden,这样虽然隐藏了滚动条,但也禁止了滚动行为。所以开发者尝试将滚动条遮盖起来,一般通过多个div的嵌套和偏移(偏移量恰好是滚动条的宽度)来实现。遮盖后再将模拟的滚动条固定在容器右侧和底部。之后的关键点就是计算模拟滚动条的宽高与位置,并且监听容器的scroll事件,及时更新滚动条的状态,如果用户拖动滚动条,则此时不能依靠原生滚动行为,需要自己计算实际滚动距离去更新容器的scrollLeft及scrollTop。参考simplebar和react-custom-scrollbars。
该方案有很多优点,首先你可以完全自定义滚动条的样式而不用考虑兼容性问题,其次它的性价比非常高,绝大多数时间,你使用的是浏览器默认的行为(他们性能优秀而且覆盖了边际情况),只有在用户拖动滚动条时,才需要手动计算并更新容器的滚动距离。不过该方案也并非完美无缺,最大的问题是你需要添加多层div才能覆遮盖住原生滚动条,这在一定程度上破坏了开发者预先设想的文档结构。
3、自行实现滚动行为与滚动条样式。
该方案比较复杂,因为滚动行为通常由三个条件触发,分别是鼠标滚轮(或触控板)滑动、键盘导航、鼠标拖动(选择文字时),你得同时监听这三种事件,同时要考虑兼容问题,因为这三种事件在各个浏览器不统一。滚动条部分与方案2相同,这里不再赘述。 虽然这个方案不好搞,但正因为完全自定义,你得以写出更丰富的滚动逻辑,比如整屏滚动或者增加颜色特效。该方案在社区最为流行。
二、js实现思路(pc端)
这里会详细阐述方案3的实现思路。让我们从零开始,现在有一个容器,他的子元素高度超过了容器的高度,需要给他添加一个纵向的滚动条,从交互角度出发,可以分解成以下步骤。
1、监听容器的mousewheel事件。
通过鼠标滚轮或者触控板的滑动,浏览器会生成mousewheel事件,事件中带有滚动偏移量,我们要利用该数值来修改容器的scrollTop以达到滚动效果。这里的问题是mousewheel不是一个标准事件,各个浏览器携带不一样的事件信息,滚动偏移量也不同,所以我们需要抹平他们的差异。一个好的办法是将滚动偏移量统一设为1。
const userAgent = window.navigator.userAgent;
let isSafari = (userAgent.indexOf('Chrome') === -1) && (userAgent.indexOf('Safari') >= 0);
function standardizedWheel(e) {
let wheelEvent = Object.assign({}, e);
// vertical
if (typeof e.wheelDeltaY !== 'undefined') {
// webkit
wheelEvent.deltaY = e.whee