自定义动画
您可以直接使用 TWEEN 来创建完全自定义的动画效果。
如何使用
通过 ref 或 onReady 获取 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 |
最佳实践
- 性能考虑:避免同时运行太多动画,合理使用
stop()方法清理动画 - 内存管理:使用 ref 保存动画实例,便于管理和清理
- 用户体验:提供停止动画的方式,如点击空白处停止
- 动画组合:合理使用链式动画和并行动画创建复杂效果
- 缓动函数:选择合适的缓动函数让动画更自然
TWEEN 文档
https://tweenjs.github.io/tween.js (opens in a new tab)
注意
⚠️
javascript 版本需要单独引入 TWEEN 库,才能使用 TWEEN 动画。而且一定要注意开启渲染循环。
function startTweenUpdateLoop() {
function animate() {
TWEEN.update();
requestAnimationFrame(animate);
}
animate();
}