如何让requestAnimationFrame动画按照指定时长准确运行
requestAnimationFrame相关问题解答
1. 为什么帧间隔不恒定、前几帧耗时偏高?
requestAnimationFrame(以下简称rAF)的回调执行时机和浏览器的渲染流水线完全绑定,只要浏览器主线程有其他优先级更高的任务在执行(比如同步JS运算、大规模DOM更新、用户交互事件处理),rAF回调就会被延后,自然不会保持固定间隔。
而前几帧耗时偏高是常见现象,动画启动初期浏览器通常需要完成额外的初始化工作,比如样式重新计算、渲染图层分配、缓存预热等,都会占用额外时间。
2. 16.5ms的间隔是否会随设备/屏幕变化?
会,16.5ms只是60Hz刷新率屏幕的理论刷新间隔(1000ms / 60 ≈ 16.67ms),这个值会随屏幕刷新率变化:120Hz屏的刷新间隔约为8.3ms,144Hz屏约为6.9ms,部分设备开启省电模式时还会主动降低刷新率,间隔会进一步变长,完全不能用固定的16.5ms作为计算基准。
3. 如何保证动画刚好在指定时长内完成?
不要用固定步长累加的方式计算位移,正确做法是用时间差计算动画进度,完全和帧间隔解耦,实现逻辑如下:
- 动画启动时记录起始时间戳
- 每帧rAF触发时用传入的timestamp计算已运行时长,再除以总时长得到当前动画进度(最大为1)
- 用进度乘以总位移高度得到当前需要移动的距离,再更新样式即可
修改后的代码示例:
function changeHeight(children, duration, setTop) { const height = children.clientHeight; // 记录动画起始时间 let startTime = null; const slideUp = function (timestamp) { // 第一帧触发时初始化起始时间 if (!startTime) startTime = timestamp; // 计算已运行时长 const elapsed = timestamp - startTime; // 计算当前进度,最大不超过1 const progress = Math.min(elapsed / duration, 1); // 计算当前需要移动的距离 const currentMove = progress * height; children.style.top = `${setTop(currentMove, height)}px`; // 进度没到1就继续下一帧 if (progress < 1) { requestAnimationFrame(slideUp); } }; requestAnimationFrame(slideUp); }
这个实现完全不依赖固定帧间隔,不管是高刷屏还是卡慢设备,都能保证动画刚好在你传入的duration时长内完成。
内容的提问来源于stack exchange,提问作者McDerp
相关产品推荐
相关产品推荐

