rAF如何实现等价CSS动画alternate方向与无限迭代效果
实现方案
你现在的代码缺三个核心逻辑,补上之后就能和CSS动画效果完全一致:
- 无限循环逻辑:不要在进度到1的时候终止rAF,用时间取模计算当前所属的动画周期
- 交替方向判断:区分正向(路径起点到终点)、反向(路径终点回起点)两个阶段的进度
ease-in-out缓动匹配:当前代码的进度是线性增长的,和CSS动画的缓动节奏不匹配,需要给进度套对应缓动函数
具体实现步骤
- 首先定义时间参数:单趟动画时长和CSS保持一致为3000ms,一个完整往返周期就是6000ms
- 每帧执行时,先计算从动画启动到当前的总耗时,对周期时长取模,拿到当前周期内的已走时间,实现无限循环效果
- 判断当前是正向还是反向阶段:
- 周期内时间小于3000ms时为正向,线性进度 = 周期内时间 / 单趟时长
- 周期内时间大于等于3000ms时为反向,线性进度 = 1 - (周期内时间 - 单趟时长)/单趟时长
- 把线性进度传入
ease-in-out缓动函数,得到最终的路径进度,再计算对应坐标更新圆点位置即可
CSS的ease-in-out对应三次贝塞尔曲线cubic-bezier(0.42, 0, 0.58, 1),可以直接用JS实现对应的计算函数,视觉效果和原生CSS完全一致。
完整可运行代码
const singleDuration = 3000; // 单趟动画时长,和CSS配置对齐 const cycleDuration = singleDuration * 2; // 往返一次的完整周期时长 const curve = document.querySelector('#curve'); const totalPathLength = curve.getTotalLength(); const dot = document.querySelector('#dot'); const animationStart = performance.now(); // 实现CSS ease-in-out 缓动 function easeInOut(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } function animate() { const totalElapsed = performance.now() - animationStart; // 取模实现无限循环,拿到当前周期内的耗时 const currentCycleTime = totalElapsed % cycleDuration; let linearProgress; if (currentCycleTime < singleDuration) { // 正向阶段:从起点移动到终点 linearProgress = currentCycleTime / singleDuration; } else { // 反向阶段:从终点移动回起点 linearProgress = 1 - (currentCycleTime - singleDuration) / singleDuration; } // 应用缓动得到最终路径进度 const pathProgress = easeInOut(linearProgress); // 计算当前点坐标更新位置 const point = curve.getPointAtLength(pathProgress * totalPathLength); dot.setAttribute("transform", `translate(${point.x}, ${point.y})`); requestAnimationFrame(animate); } animate();
如果需要和浏览器原生CSS动画的缓动做到像素级完全一致,也可以借助Web Animations API直接读取动画进度,不需要手写缓动逻辑,日常场景下上面的实现已经可以做到视觉无差异。
内容的提问来源于stack exchange,提问作者smpa01
相关产品推荐
相关产品推荐

