滚动优化核心是只读轻量属性、只写transform位移:scroll回调仅存scrolltop,raf中用预计算整数值执行translatey,配合图层提升与物理惯性模型。

scroll 事件里只存 scrollTop,别碰 DOM
编辑器滚动卡顿的第一刀,往往砍在 scroll 回调里直接读写 DOM。比如每次滚动都调用 getBoundingClientRect() 或设置 el.style.top,这会触发强制重排(forced layout),浏览器必须立刻计算整个布局树——长文档下代价极高。
正确做法是把 scroll 回调压到最轻:只更新一个缓存变量,不查、不改、不渲染。
- 监听时加
{ passive: true },防止被 JS 阻塞滚动线程 - 只做一件事:
latestScrollY = container.scrollTop - 用布尔标记防重复注册:
if (!isPending) { isPending = true; requestAnimationFrame(updateView); }
在 rAF 回调里只做纯位移 + 提前算好值
rAF 不是“让动画变快”的魔法开关,它只是给你一帧的执行窗口。窗口里干错事,照样撕裂卡顿。关键约束就两条:读轻量、写纯位移。
- 读取仅限:
container.scrollTop、container.clientHeight、el.offsetTop—— 这些不触发重排 - 绝对避开:
offsetHeight、getComputedStyle(el).lineHeight、el.getBoundingClientRect() - 写入只用 transform:
el.style.transform = `translateY(${y}px)`,而非top或margin-top - 所有 y 值在 rAF 外提前算好(比如虚拟滚动的可视行偏移),回调里只赋值
惯性滚动必须自己建物理模型,rAF 只负责调度
光靠 rAF 调用不会产生惯性。你得维护 position、velocity、lastTime 三个状态,并基于真实时间差做衰减。
- 别用固定步长(如每帧 +2px),必须用
deltaTime = timestamp - lastTime插值 - 阻尼推荐指数衰减:
velocity *= Math.pow(0.95, deltaTime / 16),与帧率无关 - 边界处理要闭环:到顶/到底时设
velocity = -0.3微反弹,避免粘滞停顿 - 滚动停止条件看速度残值:
Math.abs(velocity) > 0.05才继续递归调用requestAnimationFrame
图层提升和像素对齐是最后一道防线
即使逻辑全对,没提合成层照样撕裂。GPU 渲染文本和滚动位移若混在同一图层,CPU 和 GPU 会抢资源打架。
- 给滚动容器加
will-change: transform或transform: translateZ(0)强制升层 - 所有滚动相关元素(光标、行高亮、浮动工具栏)统一用
transform: translateY()定位 - 确保位移值是整数:
Math.round(y),避免 sub-pixel 渲染模糊或抖动 - 禁用
position: sticky做粘性标题——它不走合成层,改用 transform + rAF 动态算偏移
真正难的不是写对 rAF,而是守住“只读轻量、只写位移”这条线。一旦在回调里多查一个 offsetHeight,或者漏掉 Math.round(),丝滑感就断在那一帧。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











