player-react
动画
自定义动画

自定义动画

iCraft Player React 内置 TWEEN 库,您可以直接使用 TWEEN 来创建完全自定义的动画效果。

如何使用

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

"use client";
 
import { ICraftPlayer, ICraftPlayerInstance, TWEEN } from "@icraft/player-react";
import { useRef, useState } from "react";
 
export default () => {
  const [isPaused, setIsPaused] = useState<boolean>(false);
  const playerRef = useRef<ICraftPlayerInstance>();
  const tweenRef = useRef<TWEEN.Tween<any> | null>(null);
  const allAnimationsRef = useRef<TWEEN.Tween<any>[]>([]);
 
  const onReady = (player: ICraftPlayerInstance) => {
    playerRef.current = player;
    const car = player.getElementsByName("car")?.[0];
    if (car) {
      startSquarePathAnimation(car);
    } else {
    }
  };
 
  const startSquarePathAnimation = (car: any) => {
    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: TWEEN.Tween<any>[] = [];
 
    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]);
    }
 
    allAnimationsRef.current = animations;
 
    animations[0].start();
    tweenRef.current = animations[0];
  };
 
  const togglePause = () => {
    if (isPaused) {
      allAnimationsRef.current.forEach((animation) => {
        if (animation.isPaused && animation.isPaused()) {
          animation.resume();
        }
      });
      setIsPaused(false);
    } else {
      allAnimationsRef.current.forEach((animation) => {
        if (animation.pause) {
          animation.pause();
        }
      });
      setIsPaused(true);
    }
  };
 
  return (
    <div style={style}>
      <ICraftPlayer
        src='/templates/tween.iplayer'
        onReady={onReady}
        ref={playerRef}
      />
      <div style={infoStyle}>
        <div style={{ display: "flex", gap: "10px", marginTop: "10px" }}>
          <button onClick={togglePause}>{isPaused ? "play" : "pause"}</button>
        </div>
      </div>
    </div>
  );
};
 
const style = {
  width: "100%",
  height: "100%",
  position: "relative" as const,
  overflow: "hidden" as const,
};
 
const infoStyle = {
  position: "absolute" as const,
  bottom: 10,
  right: 10,
  padding: "10px",
};
 

常用动画示例

位置动画

// 移动到目标位置
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)