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

如何暂停TWEEN动画且恢复时不跳转至最终位置?解决排序可视化问题

问题解决思路与代码示例

1. 修复TWEEN动画暂停跳转问题

TWEEN.js没有原生暂停API,但可以手动记录动画进度实现无跳转的暂停/继续:

  • 给每个Tween实例扩展pause()和resume()方法,记录暂停时的已流逝时间
  • 暂停时从全局更新队列移除实例,恢复时重新计算起始时间并加回队列

代码示例:

// 给Tween原型扩展暂停/继续方法
TWEEN.Tween.prototype.pause = function() {
  if (!this._isPaused) {
    this._elapsedTime = Date.now() - this._startTime;
    this._isPaused = true;
    // 从全局队列移除当前动画
    const index = TWEEN._tweens.indexOf(this);
    if (index !== -1) TWEEN._tweens.splice(index, 1);
  }
  return this;
};

TWEEN.Tween.prototype.resume = function() {
  if (this._isPaused) {
    this._isPaused = false;
    // 重新计算起始时间,保证动画从暂停点继续
    this._startTime = Date.now() - this._elapsedTime;
    TWEEN.add(this);
  }
  return this;
};

// 批量暂停所有动画
function pauseAllTweens() {
  TWEEN._tweens.forEach(tween => tween.pause());
}

// 批量恢复所有动画
function resumeAllTweens() {
  TWEEN._tweens.forEach(tween => tween.resume());
}

2. 控制排序算法与动画同步

排序逻辑不能同步硬跑,必须改成异步分步执行,让每一步都等待动画完成/暂停状态解除后再继续:

  • 用全局开关isPaused控制流程,暂停时进入等待状态
  • 把排序拆成单个步骤,用Promise包裹动画操作,确保动画完成后再执行下一步

代码示例:

let isPaused = false;
let resumeResolve;

// 暂停时等待恢复信号的函数
function waitForResume() {
  return new Promise(resolve => {
    resumeResolve = resolve;
  });
}

// 恢复排序流程
function resumeSort() {
  isPaused = false;
  resumeResolve?.();
}

// 异步冒泡排序示例
async function bubbleSort(elements) {
  const len = elements.length;
  for (let i = 0; i < len; i++) {
    for (let j = 0; j < len - i - 1; j++) {
      // 检查是否暂停,暂停则等待
      while (isPaused) {
        await waitForResume();
      }

      // 触发对比可视化:改变元素颜色
      elements[j].material.color.set(0xff0000);
      elements[j+1].material.color.set(0xff0000);
      // 等待对比可视化完成
      await new Promise(resolve => setTimeout(resolve, 1000));

      if (elements[j].value > elements[j+1].value) {
        // 执行交换动画,用Promise包裹确保动画完成后再继续
        await swapElements(elements[j], elements[j+1]);
      }

      // 恢复元素默认颜色
      elements[j].material.color.set(0xffffff);
      elements[j+1].material.color.set(0xffffff);
    }
  }
}

// 交换动画的Promise封装
function swapElements(el1, el2) {
  return new Promise(resolve => {
    const pos1 = el1.position.x;
    const pos2 = el2.position.x;

    new TWEEN.Tween(el1.position)
      .to({x: pos2}, 500)
      .onComplete(() => resolve())
      .start();

    new TWEEN.Tween(el2.position)
      .to({x: pos1}, 500)
      .start();
  });
}

// 播放/暂停按钮绑定逻辑
document.getElementById('pauseBtn').addEventListener('click', () => {
  isPaused = !isPaused;
  if (isPaused) {
    pauseAllTweens();
  } else {
    resumeAllTweens();
    resumeSort();
  }
});

3. 添加同步旁白语音

用浏览器原生Web Speech API实现,和排序步骤绑定触发:

const synth = window.speechSynthesis;

// 播放旁白的工具函数
function speak(text) {
  if (synth.speaking) {
    synth.cancel();
  }
  const utterance = new SpeechSynthesisUtterance(text);
  synth.speak(utterance);
}

// 在排序步骤中插入旁白调用
async function bubbleSort(elements) {
  // ...
  while (isPaused) {
    await waitForResume();
  }

  speak(`正在对比第${j+1}个和第${j+2}个元素`);
  elements[j].material.color.set(0xff0000);
  elements[j+1].material.color.set(0xff0000);
  await new Promise(resolve => setTimeout(resolve, 1000));

  if (elements[j].value > elements[j+1].value) {
    speak(`交换第${j+1}个和第${j+2}个元素`);
    await swapElements(elements[j], elements[j+1]);
  }
  // ...
}

要不要更换动画库?

如果觉得手动扩展TWEEN.js太繁琐,可以换用GSAP:它原生支持暂停/继续、时间线管理,能更轻松地控制复杂动画序列,还能和异步逻辑更好地结合。但如果上面的方法能解决你的问题,继续用TWEEN.js也完全没问题——它轻量且足够覆盖当前需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 19:23:10