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

Lottie Web如何在同一canvas元素内同时并排播放2个动画

问题核心原因

你的代码有3个关键逻辑错误,导致效果不符合预期:

  • 自动清屏冲突:两个动画实例都设置了clearCanvas: true,每个动画渲染前都会清空整个画布,最终只会显示最后一个触发渲染的动画内容,你一开始画的绿色方块被清掉也是这个原因。
  • 变换时机错误:你在enterFrame事件里才执行canvas平移,但这个事件是Lottie完成当前帧所有绘制操作之后才触发的,变换根本不会作用到当前帧的绘制上,等于无效代码。
  • 缺少状态隔离:canvas的坐标系变换是全局累加的,就算你把平移时机改对,不做状态重置的话动画会越跑位置越偏,最终飞出画布。

可直接运行的修复方案

不要让多个Lottie实例各自控制渲染和清屏,自己接管全局的帧调度,逻辑完全可控,也不会出现冲突:

const testjson = {...animation...};
const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");

// 存储所有在当前canvas渲染的动画配置
const animQueue = [];

/**
 * 创建带偏移的共享canvas动画
 * @param {object} animJson Lottie动画数据
 * @param {number} offsetX X轴偏移量
 * @param {number} offsetY Y轴偏移量
 */
function createSharedAnim(animJson, offsetX, offsetY) {
  const animInstance = bodymovin.loadAnimation({
    renderer: "canvas",
    loop: true,
    autoplay: false, // 关闭自动渲染,由全局循环统一调度
    rendererSettings: {
      context: ctx,
      clearCanvas: false, // 所有实例都关闭自动清屏
    },
    animationData: animJson,
  });

  animQueue.push({
    instance: animInstance,
    x: offsetX,
    y: offsetY,
    startTs: performance.now() // 记录动画启动时间,用来计算播放进度
  });
}

// 全局渲染循环
function renderFrame(nowTs) {
  // 每帧开头统一清空整个画布
  ctx.clearRect(0, 0, cvs.width, cvs.height);

  // 这里可以加全局静态内容绘制,比如你之前的绿色方块
  // ctx.fillStyle = "green";
  // ctx.fillRect(0, 0, 40, 40);

  // 逐个绘制每个动画
  animQueue.forEach(animItem => {
    // 保存画布原始状态
    ctx.save();
    // 平移坐标系到当前动画的目标位置
    ctx.translate(animItem.x, animItem.y);

    // 计算当前帧位置
    const runTime = nowTs - animItem.startTs;
    const fps = animItem.instance.frameRate;
    const totalFrames = animItem.instance.totalFrames;
    const currentFrame = (runTime / 1000 * fps) % totalFrames;

    // 绘制当前帧
    animItem.instance.goToAndStop(currentFrame, true);

    // 恢复画布原始状态,清除平移变换,避免影响下一个动画
    ctx.restore();
  });

  requestAnimationFrame(renderFrame);
}

// 初始化两个红球动画,分别在x=0和x=100位置
createSharedAnim(testjson, 0, 0);
createSharedAnim(testjson, 100, 0);

// 启动渲染
requestAnimationFrame(renderFrame);

补充说明

  • 这个方案不会出现洋葱皮残影问题,不需要调用setSubframe(false)。
  • 如果需要做动画暂停、倍速、销毁控制,只要在animQueue里加对应状态字段,在计算currentFrame的时候做逻辑判断即可,比多实例各自渲染更易维护。

内容的提问来源于stack exchange,提问作者user1207504

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 22:12:22