我一直觉得滚动条这个东西实在太容易被忽视了。它就待在页面的边缘,不声不响地陪我们翻页、滑动,看起来像个工具,却几乎从未被认真打磨过。大多数时候,我们花了很多精力去优化页面动画、微交互,甚至为每个按钮设计 hover 效果,唯独滚动条,几乎都用浏览器默认的样式。
有一次我在看一个音乐可视化网站,页面背景的动效特别炫,粒子飞舞、节奏同步,视觉冲击力特别强。那一刻我突然冒出了个想法:如果滚动条也能动起来,能呼吸、能流动,甚至像液体一样具有生命感,那是不是会更酷一点?
这个想法就这么诞生了。
一开始我只是想着能不能让滚动条别那么无趣。比如在用户滚动的时候,滑块后面留下一点拖尾?或者发点光?再夸张点,像《星际穿越》里的虫洞拉伸那样来一发“超时空轨迹”?这个想法说大不大,说小也不算小,我把它记在了笔记里,慢慢琢磨。
最开始我用的是 ::-webkit-scrollbar
去改滚动条的样式。这个方案简单,很多教程里都有。但试了一圈发现兼容性太烂了。Firefox 完全不认,Safari 和 Edge 各有各的奇葩行为。你在 Chrome 上调得再漂亮,换个浏览器就直接打回原形。
所以我干脆一咬牙:那就自己画一个滚动条吧,用 canvas 来搞。
这个项目我起了个名字,叫 FluidScroll。意思很直接:流动的滚动条。
我并不是单纯想做个样式好看的滚动条,我想让它“动”起来,能根据滚动速度做出反应,滑得快的时候拖尾长一点、闪一点颜色,慢的时候就温柔点,像水一样。我的目标是让它感觉像一个有温度的 UI,而不只是一个滑块。
技术栈方面,其实蛮简单:
canvas
自己画滑块和拖尾;requestAnimationFrame
做高帧率动画;scrollTop
变化计算滚动速度;HTML 结构非常清爽:
<div id="scroll-wrapper">
<canvas id="scrollbar"></canvas>
<div id="content">
<h1>🚀 滚动体验</h1>
<p>拖动页面,体验流体滚动条。</p>
<div class="filler"></div>
</div>
</div>
滚动的区域是 #scroll-wrapper
,canvas 是独立画布,固定在页面右边,宽度窄一点,高度占满全屏。真正的浏览器滚动条被我隐藏了,视觉上看到的都是 canvas 渲染出来的。
而且我特地加了 pointer-events: none
,保证 canvas 不拦截任何点击或拖动事件。这个细节很重要,否则你点页面右边会发现啥都点不了。
核心思路其实不复杂:监听滚动事件,每次滚动时,根据滚动速度生成一堆粒子,然后画在 canvas 上。这些粒子会自己扩散、变淡、最后消失,就像一个拖尾。
每个粒子有以下几个属性:
{
x, y, // 位置
dx, dy, // 移动方向
alpha, // 透明度
radius, // 大小
color // hsl颜色
}
动画主循环里,每一帧都更新这些粒子的位置和状态,再重新绘制整张 canvas:
function updateParticles() {
particles.forEach(p => {
p.x += p.dx;
p.y += p.dy;
p.alpha -= 0.02;
});
particles = particles.filter(p => p.alpha > 0);
}
只保留 alpha > 0 的粒子,这样它们会“自然死亡”,不会一直占内存。
这个逻辑就更简单了,纯粹靠 scrollTop
的差值:
let velocity = scrollTop - lastScrollTop;
lastScrollTop = scrollTop;
速度越大,说明滚动越快,我就多生成一些粒子;滚得慢,就少生成点:
for (let i = 0; i < Math.abs(velocity) * 0.5; i++) {
particles.push(createParticle(yPos));
}
这个 yPos
是当前滚动条位置对应的 y 坐标。通过比例映射到 canvas 的高度即可。
颜色我用了黑色底,粒子是 hsl
生成的彩色系,既丰富又不会太杂。因为色相是用随机的 Math.random() * 360
来搞的,所以每次滚动都会有微妙差异,视觉上很灵动。
整个 canvas 不加边框、不加阴影,只保留最核心的光点轨迹。这种视觉风格其实是参考了早期一些音频可视化工具,也有点像某些科幻 HUD 的感觉。
我一开始跑起来,发现滚动快的时候粒子太多了,画布卡得一塌糊涂。于是加了几重优化策略:
requestAnimationFrame
;代码示例:
if (particles.length === 0 && Math.abs(velocity) < 0.01) {
return; // 不滚了,省资源
}
requestAnimationFrame(animate);
调这个东西其实也遇到不少问题:
ctx.scale()
解决;后来我把整个逻辑封装成了一个类,可以直接挂载:
const scrollFX = new FluidScrollbar('#scroll-wrapper')
scrollFX.init()
甚至还留了配置项:
new FluidScrollbar('#scroll-wrapper', {
colorMode: 'hsl',
maxParticles: 40,
blurEffect: true
});
模块化之后,这东西就能在别的项目里复用了,不用每次都复制一堆代码。
说实话,做好之后我自己挺满意的。虽然只是一个滚动条,但它的细节、动效、跟手性都做到了我想要的程度。平常滚页面没啥感觉,现在看到那一串彩色粒子随着滚动拖出来,就像是在点亮页面一样。
它不会干扰用户操作,也不会喧宾夺主,却在每次滚动时,悄悄回应一下你的动作。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。