requestAnimationFrame比setTimeout更稳,因其严格对齐屏幕刷新节奏(如60Hz下~16.6ms/帧),而setTimeout仅“尽量”每16ms调用,受主线程阻塞和后台持续执行影响,易导致掉帧、突跳与耗电。

仪表盘动画卡顿,八成不是设备性能差,而是动效路径踩进了浏览器渲染的“雷区”——改 left、读 offsetTop、滥用 filter、或用 setTimeout 驱动刷新,都会让帧率掉到 30 以下还抖动。
为什么 requestAnimationFrame 比 setTimeout 更稳
浏览器对 requestAnimationFrame 有原生调度优化,它会严格对齐屏幕刷新节奏(比如 60Hz 屏幕就是 ~16.6ms 一帧),而 setTimeout(fn, 16) 只是“尽量每 16ms 调一次”,实际执行时机不可控:主线程一忙,它就排队;页面切后台,它还在狂发回调,回来直接突跳。
- 必须把所有动画逻辑塞进
requestAnimationFrame回调里,别嵌套setTimeout - 启停要配对:
cancelAnimationFrame(id)清理上一帧 ID,否则内存泄漏+后台持续耗电 - 如果只是“滚动时更新指针角度”,用布尔开关控制是否继续调用
requestAnimationFrame,别退回到节流定时器
哪些 CSS 属性能真正跑满 60fps
只有被浏览器标记为“可合成”(composited)的属性,才能绕过主线程的样式计算、布局、绘制,直通 GPU 合成器。目前稳定支持的只有 transform 和 opacity。
- 把仪表盘指针的旋转从
top/left+width/height改成transform: rotate(45deg)或transform: translateX(100px) rotate(30deg) - 数值渐变动画别用
visibility+opacity组合,直接用opacity+will-change: opacity(动前加,动完移除) - 禁用
filter: blur()、drop-shadow()、box-shadow——它们会让元素退回到 CPU 渲染,DevTools 的 Layers 面板里会突然少一层,但控制台不报错
JS 动画里最容易触发卡顿的三件事
不是代码写得慢,而是某些操作会强制浏览器中断渲染流水线,当场回流(reflow)——哪怕只查一次 getBoundingClientRect(),也可能让那一帧超时 20ms 以上。
- 别在
requestAnimationFrame回调里先读再写:比如先el.offsetTop,再el.style.transform = '...';浏览器必须立刻重排返回准确值,打断后续绘制 - 所有“读取布局”的操作(
offsetTop、scrollHeight、getBoundingClientRect())集中放到一帧开头;所有“写入样式”的操作(style.transform、classList.toggle)集中到帧末尾 - 仪表盘数据更新频繁?缓存 DOM 元素引用,别每帧都
document.querySelector('.gauge-value');数值格式化等纯计算提前做完,回调里只负责设值
硬件加速不是开得越多越好
will-change: transform 或 transform: translateZ(0) 确实能提升合成层优先级,但滥用会导致图层爆炸、内存占用飙升、低端机更卡。
- 只对“即将开始动画且持续时间 > 1s”的元素加
will-change,动完立刻el.style.willChange = 'auto' - 仪表盘里多个环形进度条、多个指针?别给每个都加
translateZ(0),统一用一个transform: scale(1)包裹容器,提升整组 - 用 Chrome DevTools → Layers 面板确认:动起来的元素是否真在独立图层里;如果图层太多、合并频繁,说明提升过度了
真正难调的不是“怎么让数字转起来”,而是“怎么让它转的时候不拖慢整个页面”。合成层、RAF 节奏、读写分离——这些点单看简单,但混在真实仪表盘逻辑里,稍不注意就会互相抵消效果。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











