player-javascript
动画
自定义动画

自定义动画

您可以直接使用 TWEEN 来创建完全自定义的动画效果。

如何使用

通过 refonReady 获取 player 实例后,使用 getElementsByName 或点击事件获取元素,然后直接对 元素的属性(位置、旋转、缩放、颜色等)应用 TWEEN 动画。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <title>ICraft Player TWEEN Custom Animation Demo</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        font-family: Arial, sans-serif;
      }
      #container {
        width: 100%;
        height: 100vh;
        position: relative;
      }
      #controls {
        position: absolute;
        bottom: 10px;
        right: 10px;
        padding: 10px;
        font-size: 14px;
      }
    </style>
    <script src="https://unpkg.com/@icraft/player@latest/dist/umd/icraft-player.min.js"></script>
    <script src="https://unpkg.com/@tweenjs/tween.js@23.1.3/dist/tween.umd.js"></script>
  </head>
  <body>
    <div id="container"></div>
    <div id="controls">
      <div>
        <button id="pauseBtn">pause</button>
      </div>
    </div>
  </body>
  <script>
    let player = null;
    let isPaused = false;
    let allAnimations = [];
    let currentTween = null;
 
    const playerInstance = new ICraftPlayer({
      src: "/templates/tween.iplayer",
      container: document.getElementById("container"),
      onReady: (playerRef) => {
        player = playerRef;
        const car = player.getElementsByName("car")?.[0];
        if (car) {
          startSquarePathAnimation(car);
        }
      },
    });
 
    function startSquarePathAnimation(car) {
      const startPos = {
        x: car.position.x,
        y: car.position.y,
        z: car.position.z,
      };
      const startRot = car.rotation.y;
 
      const pathSegments = [
        {
          to: { x: startPos.x, y: startPos.y, z: startPos.z + 20 },
          rotation: startRot,
        },
        {
          to: { x: startPos.x + 20, y: startPos.y, z: startPos.z + 20 },
          rotation: startRot - Math.PI / 2,
        },
        {
          to: { x: startPos.x + 20, y: startPos.y, z: startPos.z },
          rotation: startRot - Math.PI,
        },
        {
          to: { x: startPos.x, y: startPos.y, z: startPos.z },
          rotation: startRot - (Math.PI * 3) / 2,
        },
      ];
 
      const animations = [];
 
      pathSegments.forEach((segment, index) => {
        const moveTween = new TWEEN.Tween(car.position)
          .to(segment.to, 2000)
          .easing(TWEEN.Easing.Quadratic.InOut);
 
        const rotateTween = new TWEEN.Tween(car.rotation)
          .to({ y: segment.rotation }, 300)
          .easing(TWEEN.Easing.Quadratic.InOut);
 
        moveTween.onStart(() => {
          rotateTween.start();
        });
 
        animations.push(moveTween);
      });
 
      for (let i = 0; i < animations.length; i++) {
        const nextIndex = (i + 1) % animations.length;
        animations[i].chain(animations[nextIndex]);
      }
 
      allAnimations = animations;
      animations[0].start();
      currentTween = animations[0];
 
      startTweenUpdateLoop();
    }
 
    function startTweenUpdateLoop() {
      function animate() {
        TWEEN.update();
        requestAnimationFrame(animate);
      }
      animate();
    }
 
    function togglePause() {
      const pauseBtn = document.getElementById("pauseBtn");
 
      if (isPaused) {
        allAnimations.forEach((animation) => {
          if (animation.isPaused && animation.isPaused()) {
            animation.resume();
          }
        });
        isPaused = false;
        pauseBtn.textContent = "pause";
        pauseBtn.className = "";
      } else {
        allAnimations.forEach((animation) => {
          if (animation.pause) {
            animation.pause();
          }
        });
        isPaused = true;
        pauseBtn.textContent = "play";
        pauseBtn.className = "pause";
      }
    }
 
    document.getElementById("pauseBtn").addEventListener("click", togglePause);
 
    window.addEventListener("beforeunload", () => {
      if (currentTween) {
        currentTween.stop();
      }
      TWEEN.removeAll();
    });
  </script>
</html>
 

常用动画示例

位置动画

// 移动到目标位置
const moveTween = new TWEEN.Tween(element.position)
  .to({ x: 10, y: 5, z: 0 }, 1000)
  .easing(TWEEN.Easing.Quadratic.InOut)
  .start();

旋转动画

// 绕 Y 轴旋转 360 度
const rotateTween = new TWEEN.Tween(element.rotation)
  .to({ y: element.rotation.y + Math.PI * 2 }, 2000)
  .easing(TWEEN.Easing.Cubic.InOut)
  .start();

缩放动画

// 缩放到 1.5 倍
const scaleTween = new TWEEN.Tween(element.scale)
  .to({ x: 1.5, y: 1.5, z: 1.5 }, 1000)
  .easing(TWEEN.Easing.Back.Out)
  .start();

组合动画

// 同时执行多个动画
const moveTween = new TWEEN.Tween(element.position).to({ x: 5, y: 3, z: 2 }, 2000);
 
const rotateTween = new TWEEN.Tween(element.rotation).to({ y: Math.PI }, 2000);
 
const scaleTween = new TWEEN.Tween(element.scale).to({ x: 1.2, y: 1.2, z: 1.2 }, 2000);
 
// 同时开始
moveTween.start();
rotateTween.start();
scaleTween.start();

链式动画

// 依次执行动画
const firstTween = new TWEEN.Tween(element.position).to({ x: 5 }, 1000);
 
const secondTween = new TWEEN.Tween(element.position).to({ y: 5 }, 1000);
 
const thirdTween = new TWEEN.Tween(element.position).to({ z: 5 }, 1000);
 
// 链接动画
firstTween.chain(secondTween);
secondTween.chain(thirdTween);
firstTween.start();

API

TWEEN

TWEEN 库提供了强大的动画功能:

方法description参数
constructor创建新的补间动画实例(object: Object)
to设置动画目标值(properties: Object, duration: number)
easing设置缓动函数(easing: Function)
start开始动画() => Tween
stop停止动画() => Tween
chain链接下一个动画(...tweens: Tween[])
yoyo启用往返动画(yoyo: boolean)
repeat设置重复次数(times: number)
onUpdate动画更新时回调(callback: Function)
onComplete动画完成时回调(callback: Function)

缓动函数

TWEEN 提供了丰富的缓动函数:

  • TWEEN.Easing.Linear.None - 线性
  • TWEEN.Easing.Quadratic.In/Out/InOut - 二次方
  • TWEEN.Easing.Cubic.In/Out/InOut - 三次方
  • TWEEN.Easing.Quartic.In/Out/InOut - 四次方
  • TWEEN.Easing.Quintic.In/Out/InOut - 五次方
  • TWEEN.Easing.Sinusoidal.In/Out/InOut - 正弦
  • TWEEN.Easing.Exponential.In/Out/InOut - 指数
  • TWEEN.Easing.Circular.In/Out/InOut - 圆形
  • TWEEN.Easing.Elastic.In/Out/InOut - 弹性
  • TWEEN.Easing.Back.In/Out/InOut - 回弹
  • TWEEN.Easing.Bounce.In/Out/InOut - 弹跳

元素属性

可以动画化的常用元素属性:

属性description类型
position位置(x, y, z)Vector3
rotation旋转(x, y, z)Euler
scale缩放(x, y, z)Vector3

最佳实践

  1. 性能考虑:避免同时运行太多动画,合理使用 stop() 方法清理动画
  2. 内存管理:使用 ref 保存动画实例,便于管理和清理
  3. 用户体验:提供停止动画的方式,如点击空白处停止
  4. 动画组合:合理使用链式动画和并行动画创建复杂效果
  5. 缓动函数:选择合适的缓动函数让动画更自然

TWEEN 文档

https://tweenjs.github.io/tween.js (opens in a new tab)

注意

⚠️

javascript 版本需要单独引入 TWEEN 库,才能使用 TWEEN 动画。而且一定要注意开启渲染循环。

function startTweenUpdateLoop() {
  function animate() {
    TWEEN.update();
    requestAnimationFrame(animate);
  }
  animate();
}