三种自定义滚动条的方式
把滚动条变成自己想要的样子有三个层级,代价与能力递增:其一,用 CSS 给原生滚动条换皮肤(::-webkit-scrollbar 与标准的 scrollbar-color / scrollbar-width);其二,隐藏原生滚动条,用 DOM 元素自绘 track 与 thumb,把交互逻辑拿回手里;其三,用
Canvas 整条自绘,换取平滑阻尼动画与复杂可视化。三个 demo 滚动的是同一段文本,可直接对比手感。
1 · 其一 · CSS 换皮肤:::-webkit-scrollbar 与标准属性
这是成本最低、改动范围也最小的一档:滚动条仍是浏览器原生的,只是换了外观。WebKit / Blink 提供 ::-webkit-scrollbar 系列伪元素(可分别命中 thumb / track / button / corner),Firefox 与标准走 scrollbar-width +
scrollbar-color。拖动下面的控件即时改样式:
两套属性各有边界。::-webkit-scrollbar 粒度细但非标准,Firefox 完全不认;标准的 scrollbar-width 只接受 auto / thin / none 三档,无法给精确 px,scrollbar-color 也只能定 thumb 与 track 两色。配套的
scrollbar-gutter: stable 可为滚动条预留 gutter,避免内容在滚动条显隐时横向抖动。
WebKit / Blink 一侧能命中的伪元素与可设属性,远不止 thumb 一处:
| 伪元素 | 命中部位 | 常设属性 |
|---|---|---|
::-webkit-scrollbar |
整个滚动条 | width / height |
::-webkit-scrollbar-thumb |
可拖动的滑块 | background / border-radius / border / box-shadow |
::-webkit-scrollbar-track |
滑槽(轨道) | background / box-shadow |
::-webkit-scrollbar-track-piece |
thumb 两侧的轨道段 | 同 track |
::-webkit-scrollbar-button |
两端方向箭头(默认无) | background / 尺寸 |
::-webkit-scrollbar-corner |
横竖滚动条交汇直角 | background |
::-webkit-resizer |
可缩放元素右下把手 | background |
再叠加 :horizontal / :vertical / :hover / :active 等伪类可进一步细分,如 ::-webkit-scrollbar-thumb:hover。
标准侧只有三个属性,但跨浏览器: scrollbar-width: auto | thin | none(只有三档,给不了精确 px)、scrollbar-color: <thumb> <track>(只能定两色)、scrollbar-gutter: auto | stable | stable both-edges(预留 gutter 防抖动)。
2 · 其二 · DOM 自绘:隐藏原生,用元素接管
当需求超出换肤——例如要自定义命中区、显隐时机、拖拽手感——就隐藏原生滚动条(scrollbar-width: none + ::-webkit-scrollbar { display: none }),用两个绝对定位的 div 当 track 与 thumb。监听 scroll 事件按比例算出 thumb 的高度与 transform,再用
pointer 事件实现拖拽与点击跳转。下面这条是 DOM 画的,可拖拽、可点轨道跳转:
**DOM 方案是多数滚动条库 (SimpleBar / OverlayScrollbars) 的选型。**它自带无障碍与命中语义,元素少时由浏览器合成、性能良好。短板在于:视觉一旦复杂(渐变 / 刻度 / 多状态叠加),节点与 CSS 状态会相互掣肘,且 thumb 用 transform 直接跟随,没有阻尼过渡。
3 · 其三 · Canvas 自绘:整条画在画布上
最后一档把滚动条整条画进一块 <canvas>:位置、长度、宽度、透明度全部由每帧的绘制函数决定,状态集中在一个 JS 对象里。配合 LERP 阻尼,scrollTop 变化时 thumb 平滑过渡而非瞬移;用 ResizeObserver 监听容器尺寸即可重算 metrics。下面这条 hover
会变宽、滚动有缓动:
**Canvas 的代价是它是个黑盒。**画布里没有真实 DOM:文字不可选、无法被读屏,无障碍需要用 off-screen DOM 另行补齐;命中检测也得自己用坐标算。换来的是复杂可视化(目录刻度、进度、动画)不再撑爆节点数——这正是原文用 Canvas 重写滚动条的理由:它做的早已不是一根滚动条,而是带导航的进度控件。
4 · 三者怎么选
| 维度 | ::-webkit-scrollbar |
DOM 自绘 | Canvas 自绘 |
|---|---|---|---|
| 改外观 | 仅换肤 | 任意 | 任意 |
| 改交互行为 | 不能 | 能 | 能 |
| 无障碍 / 文本选中 | 原生自带 | DOM 自带 | 需补 |
| 复杂视觉 (刻度/动画) | 几乎不行 | 节点易冲突 | 最顺手 |
| 实现成本 | 几行 CSS | 中 | 最高 |
| 跨浏览器一致 | WebKit/标准两套 | 一致 | 一致 |
越接近原生滚动条,CSS / DOM 越划算;越往「可视化控件」走,Canvas 的集中式重绘越占优。