three.js 控制动画进度 进度条拖拽控制

724 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

2022-04-07 最新发现 之前的方案会因为拖拽过快 计算遗漏导致最后几帧的模型一直停留 解决方案也很让我难以接受只需要几行代码即可 😭

const setProgress = (percent: number, clear?: boolean) => {
    const t = ((maxDuration + 1) / 100) * percent;
    for (var i = 0; i < mixer._actions.length; i++) {
      const animate = mixer._actions[i];
      animate.stop();
      animate.play();
    }
    mixer.setTime(t);
  };

前提是动画的循环次数是一次 AnimationClip.setLoop(THREE.LoopOnce)

 gltf.animations.forEach((animate: THREE.AnimationClip) => {
      mixer
        .clipAction(animate)
        .setLoop(THREE.LoopOnce,1)
        .play();
      if (animate.duration > maxDuration) maxDuration = animate.duration;
    });

只需要调用 stop 然后 调用play 如果只调用 play方法 不会有变化 原因可能是播放次数是1导致不会再更新模型 stop会reset动画状态 查看源码 我参考的代码 - 527行左右 部分源码: 在这里插入图片描述


  • 在载入动画时获取最长的动画所需时间
  • 调用 mixer.setTime(t); 方法设置动画进度
  • 动画如果不设置LoopOnce只播放一次 会不断重复播放该模型的动画
  • 可以通过当前设置时间是否比该模型的总播放时长要长,长就调用stop方法否则就调用play方法允许播放

请添加图片描述

逻辑 javascript

/**
 * 将百分比传入 内部转换成具体时间
 * @param {number} percent 进度百分比
 */
const setProgress = (percent) => {
  const t = (maxDuration / 100) * percent;

  for (var i = 0; i < mixer._actions.length; i++) {
    const animate = mixer._actions[i];
    const { duration } = animate.getClip();
    if (duration > t) {
      animate.play();
    } else animate.stop();
  }
  
  mixer.setTime(t);
};

typescript

/**
  * 进度蓝将百分比传入 内部转换成具体时间
  * @param {number} percent 进度百分比
  */
const setProgress = (percent: number) => {
    // if (percent > 100) return console.warn("进度大于100--" + percent);
    const t = (maxDuration / 100) * percent;
    //@ts-ignore
    for (var i = 0; i < mixer._actions.length; i++) {
      //@ts-ignore
      const animate = mixer._actions[i] as THREE.AnimationAction;
      const { duration } = animate.getClip();
      //多次计算存在误差 +0.5是防止最后一帧动画元素 可以触发play方法 弊端就是有的动画时长不足0.5s会‘闪现’ 也可以在maxDuration+1
      if (duration > t + 0.1) {
        animate.play();
      } else animate.stop();
    }
    mixer.setTime(t);
  };
/**
 * 适用于手动设置动画进度后 从当前进度开始播放 只要循环调用 setProgress即可
 */
const play = (callback: (percent: number) => void) => {
  if (!mixer) return;
  stop();
  let sum = mixer.time || 0;
  const clock = new THREE.Clock();
  const animate = () => {
    if (sum > maxDuration) return;
    timer = requestAnimationFrame(animate);
    const t = clock.getDelta();
    sum += t;
    const percent = (sum / maxDuration) * 100;
    setProgress(percent);
    callback(percent);
  };
  animate();
};