You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 14:14:52