嵌套requestAnimationFrame执行顺序异常问题排查
嵌套requestAnimationFrame执行顺序异常排查
需求说明
期望实现两个循环动画:
anim1循环执行 4次(等于a1Max)anim1结束后切换到anim2,anim2循环执行 5次(等于a2Max)- 每次
anim2执行时,需要连续输出两次internalAnim2的内容,即每次anim2对应以下3行输出:anim2 anim2 - internal anim anim2 - internal anim
现有代码
const a1Max = 4; var anim1counter = a1Max; const a2Max = 5; var anim2counter = a2Max; const ia2Max = 2; var internalAnim2counter = ia2Max; var animationFrameID = null; var internalAnimFrameID = null; function anim1() { console.log("anim1"); if (anim1counter > 0) { animationFrameID = requestAnimationFrame(() => { anim1(); }); anim1counter--; } else { cancelAnimationFrame(animationFrameID); anim1counter = a1Max; anim2(); } } function anim2() { console.log("anim2"); internalAnim2(); if (anim2counter > 0) { animationFrameID = requestAnimationFrame(() => { anim2(); }); anim2counter--; } else { anim2counter = a2Max; cancelAnimationFrame(animationFrameID); anim1(); } } function internalAnim2() { console.log("anim2 - internal anim"); if (internalAnim2counter > 0) { internalAnimFrameID = requestAnimationFrame(() => { internalAnim2(); }); internalAnim2counter--; } else { internalAnim2counter = ia2Max; cancelAnimationFrame(internalAnimFrameID); } } anim1();
实际异常输出
anim1 anim2 anim2 - internal anim anim1 anim2 - internal anim
问题分析
1. 计数器逻辑错误
现有代码中,计数器递减操作在requestAnimationFrame请求之后,导致实际执行次数比预期多1次,且提前触发了动画切换逻辑:
- 以
anim1为例,初始anim1counter=4,第一次执行后请求下一帧再减到3,会连续执行5次才进入else分支切换到anim2,不符合“4次”的预期。 - 同理
anim2会执行6次才切换回anim1,计数器提前归0导致动画切换时机完全混乱。
2. 异步动画帧的执行顺序冲突
requestAnimationFrame的回调会被加入浏览器的动画帧队列异步执行,现有代码的嵌套逻辑导致:
anim2调用internalAnim2后立即请求自身的下一帧,使得anim2的下一次执行和internalAnim2的第二次执行被加入同一队列,两者的执行顺序无法保证关联。- 当
internalAnim2的回调还在队列中时,anim1可能已经被触发,最终导致输出交错混乱。
修复方案
方案1:同步执行internalAnim2(符合连续输出需求)
调整计数器逻辑(先递减再判断),同时将internalAnim2改为同步循环,确保每次anim2执行时连续输出两次内部动画内容:
const a1Max = 4; var anim1counter = a1Max; const a2Max = 5; var anim2counter = a2Max; const ia2Max = 2; var animationFrameID = null; function anim1() { console.log("anim1"); anim1counter--; if (anim1counter > 0) { animationFrameID = requestAnimationFrame(anim1); } else { cancelAnimationFrame(animationFrameID); anim1counter = a1Max; anim2(); } } function anim2() { console.log("anim2"); // 同步执行两次internalAnim2 for (let i = 0; i < ia2Max; i++) { console.log("anim2 - internal anim"); } anim2counter--; if (anim2counter > 0) { animationFrameID = requestAnimationFrame(anim2); } else { cancelAnimationFrame(animationFrameID); anim2counter = a2Max; anim1(); } } anim1();
方案2:异步等待internalAnim2完成(分帧执行内部动画)
如果需要internalAnim2的两次执行分属不同动画帧,但确保每次anim2完成内部动画后再进入下一次循环,可以用回调方式等待内部动画结束:
const a1Max = 4; var anim1counter = a1Max; const a2Max = 5; var anim2counter = a2Max; const ia2Max = 2; var animationFrameID = null; function anim1() { console.log("anim1"); anim1counter--; if (anim1counter > 0) { animationFrameID = requestAnimationFrame(anim1); } else { cancelAnimationFrame(animationFrameID); anim1counter = a1Max; anim2(); } } function anim2() { console.log("anim2"); let internalCount = 0; function runInternal() { console.log("anim2 - internal anim"); internalCount++; if (internalCount < ia2Max) { requestAnimationFrame(runInternal); } else { // 内部动画完成后,继续anim2循环 anim2counter--; if (anim2counter > 0) { animationFrameID = requestAnimationFrame(anim2); } else { cancelAnimationFrame(animationFrameID); anim2counter = a2Max; anim1(); } } } runInternal(); } anim1();
修复后验证
方案1的输出完全符合预期:
anim1 anim1 anim1 anim1 anim2 anim2 - internal anim anim2 - internal anim anim2 anim2 - internal anim anim2 - internal anim anim2 anim2 - internal anim anim2 - internal anim anim2 anim2 - internal anim anim2 - internal anim anim2 anim2 - internal anim anim2 - internal anim // 之后回到anim1循环
内容的提问来源于stack exchange,提问作者nie podam
相关产品推荐
相关产品推荐

