系列文章目录
Three.js 快速入门教程【一】开启你的 3D Web 开发之旅
Three.js 快速入门教程【二】透视投影相机
Three.js 快速入门教程【三】渲染器
Three.js 快速入门教程【四】三维坐标系
Three.js 快速入门教程【五】动画渲染循环
Three.js 快速入门教程【六】相机控件 OrbitControls
Three.js 快速入门教程【七】常见几何体类型
Three.js 快速入门教程【八】常见材质类型
Three.js 快速入门教程【九】光源类型
Three.js 快速入门教程【十】常见的纹理类型
Three.js 快速入门教程【十一】天空盒的多种实现方式
Three.js 快速入门教程【十二】外部模型加载
Three.js 快速入门教程【十三】外部模型加载后常见的处理操作
Three.js 快速入门教程【十四】使用Stats.js监控渲染帧率和性能优化
Three.js 快速入门教程【十五】交互神器DragControls使用详解,实现对物体或模型拖拽
Three.js 快速入门教程【十六】调试神器 gui.js使用详解,可视化面板控制场景参数提高开发效率
Three.js 快速入门教程【十七】射线拾取模型——射线与射线投射器Raycaster介绍
Three.js 快速入门教程【十八】射线拾取模型——鼠标点击屏幕选中模型或物体
Three.js 快速入门教程【十九】CSS2D渲染器介绍,实现场景中物体或设备标注标签信息
Three.js 快速入门教程【二十】三维模型优化实战:使用gltf-pipeline与Draco对模型进行压缩,提高加载速度和流畅性
Three.js 快速入门教程【二十一】CSS3D渲染器(CSS3DRenderer、CSS3DObject 、CSS3DSprite)介绍,实现场景中物体标注标签信息
Three.js 快速入门教程【二十二】动画神器Tween.js使用指南
文章目录
一、前言
在 Three.js 中实现动画效果时,Tween.js 是一个不可多得的利器。它通过数学插值生成平滑过渡的动画,能显著提升开发效率。本文将结合 Three.js 的场景特性,系统讲解 Tween.js 的核心用法与实战技巧。
二、什么是Tween.js?
Tween.js是一个简单的JavaScript补间动画库,它可以平滑地改变对象的属性值。在Three.js项目中,我们常用它来创建相机移动、物体变换、颜色过渡等动画效果。
三、安装Tween.js
npm安装
npm install @tweenjs/tween.js
import * as TWEEN from '@tweenjs/tween.js';
或者直接CDN引入:
<script src="https://cdn.jsdelivr.net/npm/@tweenjs/tween.js@18.5.0/dist/tween.umd.js"></script>
四、Tween.js基本语法
以一个简单例子来熟悉下Tween.js语法
import * as TWEEN from '@tweenjs/tween.js';
// 创建一个立方体
const cube = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
scene.add(cube);
// 创建动画:在2秒内将立方体从当前位置移动到x=5的位置
const tween = new TWEEN.Tween(cube.position)
.to({ x: 5 }, 2000)
.start();
// 在动画循环中更新TWEEN
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
renderer.render(scene, camera);
}
animate();
上述例子,创建了一个正方体并让其在2秒内从初始位置移动到x=5的位置形成连续动画
4.1、执行动画步骤:
(1)创建动画实例(构造函数)
const tween=new TWEEN.Tween(object, [group])
- object:需要进行动画的对象,通常是包含数值属性的对象(如 Three.js 中的 Vector3、Color 等)
- group(可选):指定动画所属的组,用于批量控制
例如:
const obj = { x: 0, y: 0 };
const tween = new TWEEN.Tween(obj) //
(2)定义目标属性和动画持续时间
tween.to(properties, duration)
- properties:目标属性值对象
- duration:动画持续时间(毫秒)
例如:
tween.to({ x: 100 }, 1000) // 在 1 秒内将 x 从初始值过渡到 100
(3) 开始动画
tween.start([time])
- time(可选):指定开始时间
(4)更新动画
在每一帧调用 TWEEN.update():
function animate() {
requestAnimationFrame(animate);
TWEEN.update(); // 更新所有活动的补间动画
}
animate();
4.2 其他核心 API 方法
4.2.1 动画参数设置
(1) 缓动函数
Tween.js 提供多种缓动效果,控制动画的速度变化:
语法:
tween.easing(easingFunction)
内置常见缓动函数有:
- Linear.None: 线性变化
- Quadratic.In/Out/InOut: 二次方缓动
- Cubic.In/Out/InOut: 三次方缓动
- Sinusoidal.In/Out/InOut:正弦缓动
- Elastic.In/Out/InOut: 弹性效果
- Bounce.In/Out/InOut: 弹跳效果
除了Linear其他每个类别都有 In、Out 和 InOut 三种变体。
示例:
tween.easing(TWEEN.Easing.Quartic.Out); // 减速效果
(2) 延迟与重复
tween.delay(milliseconds) //延迟执行
tween.repeat(times) //重复次数
tween.repeatDelay(milliseconds)//重复间隔时间
tween.yoyo(enabled)//是否启用往复动画
- delay:动画启动前的延迟时间
- repeat:重复次数(设置为 Infinity 表示无限重复)
- repeatDelay:重复间隔时间
- yoyo:是否启用往复动画
4.2.2 动画控制
tween.start(): 开始动画
tween.stop(): 停止动画
tween.pause(): 暂停动画
tween.resume(): 恢复暂停的动画
4.2.3 事件回调
tween.onStart(callback)
tween.onUpdate(callback)
tween.onComplete(callback)
tween.onStop(callback)
- onStart:动画开始时触发
- onUpdate:每一帧更新时触发
- onComplete:动画完成时触发
- onStop:动画停止时触发
其中 onUpdate(callback),中callback回调函数入参obj为对应的是new TWEEN.Tween(pos)的参数对象pos在当前帧所对应的值
示例:
new TWEEN.Tween({x:0})
.to({ x: 5 }, 1000)
.onStart(() => console.log('动画开始'))
.onUpdate((obj) => console.log('动画更新中',obj.x))
.onComplete(() => console.log('动画完成'))
.start();
从打印台可以看出onUpdate打印数据是从0-5变化过程,
4.2.4 链式调用
当有多个动画依次执行可采用链式调用
tween.chain(tweens2)
示例:
const tween1 = new TWEEN.Tween(obj).to({ x: 10 }, 1000);
const tween2 = new TWEEN.Tween(obj).to({ y: 10 }, 1000);
const tween3 = new TWEEN.Tween(obj).to({ z: 10 }, 1000);
// 顺序执行
tween1.chain(tween2)
tween2.chain(tween3)
tween1.start(); //执行第一个
五、示例代码
示例1:
相机绕着物体做圆周运动
import * as THREE from "three";
import * as TWEEN from '@tweenjs/tween.js';
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
// 创建相机
const camera = new THREE.PerspectiveCamera( // 透视相机
75,
window.innerWidth / window.innerHeight,
0.1,
3000
);
camera.position.set(0, 1, 10);
// 创建WebGLRenderer渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染器宽高
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//加入点光源
let pointLight = new THREE.PointLight(0xffffff, 15, 1000, 1);
pointLight.position.set(3, 3, 3);
scene.add(pointLight);
/**
* 创建一个立方体
*/
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshLambertMaterial({ color: 0x1678e2 });
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.set(0,0,0);//设置立方体位置
scene.add(boxMesh);
/**
* 创建一个圆柱体做参照物
*/
const geometry = new THREE.CylinderGeometry(1, 1, 1, 32);
const material = new THREE.MeshLambertMaterial({ color: 0xffff00 });
const cylinderMesh = new THREE.Mesh(geometry, material);
cylinderMesh.position.set(0, 0, 12);//设置圆柱体位置
scene.add(cylinderMesh);
//创建动画
new TWEEN.Tween({angle:0})
.to({angle: Math.PI*2}, 10000)
.onUpdate(function(obj){
camera.position.x = 10 * Math.cos(obj.angle);
camera.position.z = 10 * Math.sin(obj.angle);
camera.lookAt(boxMesh.position);//始终看向立方体
})
.start()
// 在动画循环中更新TWEEN
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
renderer.render(scene, camera);
}
animate();
运行效果:
示例2:
链式动画,使物体进行一系列复杂动画组合
import * as THREE from "three";
import * as TWEEN from '@tweenjs/tween.js';
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
// 创建相机
const camera = new THREE.PerspectiveCamera( // 透视相机
75,
window.innerWidth / window.innerHeight,
0.1,
3000
);
camera.position.set(0, 1, 10);
// 创建WebGLRenderer渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染器宽高
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//加入点光源
let pointLight = new THREE.PointLight(0xffffff, 15, 1000, 1);
pointLight.position.set(3, 3, 3);
scene.add(pointLight);
/**
* 创建一个立方体
*/
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshLambertMaterial({ color: 0x1678e2 });
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.set(0,0,0);//设置立方体位置
scene.add(boxMesh);
// 顺序执行多个动画
const moveRight = new TWEEN.Tween(boxMesh.position)
.to({ x: 5 }, 2000);
const moveUp = new TWEEN.Tween(boxMesh.position)
.to({ y: 5 }, 2000);
const rotate = new TWEEN.Tween(boxMesh.rotation)
.to({ z: Math.PI }, 800);
// 链式调用:右移 → 上移 → 旋转
moveRight.chain(moveUp)
moveUp.chain(rotate)
moveRight.start()
// 在动画循环中更新TWEEN
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
renderer.render(scene, camera);
}
animate();
示例3:
3D大模型(厂区、园区、城市等)分区放大查看功能
<template>
<button class="view-btn" @click="handleViewA">查看A区</button>
<button class="view-btn-2" @click="handleViewB">查看B区</button>
</template>
<script setup>
import * as THREE from "three";
//引入相机控制器
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import * as TWEEN from "@tweenjs/tween.js";
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
3000
);
camera.position.set(0, 10, -5);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 3);
scene.add(ambientLight);
//添加平行光
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(-5, 2, 200);
scene.add(light);
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建加载器实例
const loader = new GLTFLoader();
// 异步加载模型
loader.load(
"factory.gltf",
(gltf) => {
const model = gltf.scene;
//相机看向模型
camera.lookAt(model);
scene.add(model);
},
(xhr) => {
console.log(`已加载 ${(xhr.loaded / xhr.total) * 100}%`);
},
(error) => {
console.error("加载失败:", error);
}
);
// 创建 OrbitControls 控件
const controls = new OrbitControls(camera, renderer.domElement);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
controls.update();
TWEEN.update(); // 更新所有活动的补间动画
renderer.render(scene, camera);
}
animate();
/**
* 创建动画
* @param endPos:动画结束相机位置
* @param endTarget:动画结束相机观察点
* @param duration:动画持续时长
*/
const creaeteTween = (endPos, endTarget, duration = 1500) => {
return new TWEEN.Tween({
// 当前的位置和目标观察点
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
tx: controls.target.x,
ty: controls.target.y,
tz: controls.target.z,
})
.to(
{
// 动画结束相机位置坐标
x: endPos.x,
y: endPos.y,
z: endPos.z,
// 动画结束相机指向的目标观察点
tx: endTarget.x,
ty: endTarget.y,
tz: endTarget.z,
},
duration
)
.onUpdate(function (obj) {
// 动态改变相机位置
camera.position.set(obj.x, obj.y, obj.z);
// 动态改变相机观察点
controls.target.set(obj.tx, obj.ty, obj.tz);
controls.update();
});
};
//查看A区
const handleViewA = () => {
//
let tween = creaeteTween(
{
x: 9.8,
y: 2.9,
z: -2.8,
},
{
x: 9.8,
y: 0,
z: 0,
}
);
tween.start();
};
//查看B区
const handleViewB = () => {
//升高动画
let tween1 = creaeteTween(
{
x: 9.8,
y: camera.position.y + 3,
z: camera.position.z - 3,
},
controls.target
);
tween1
.easing(TWEEN.Easing.Quartic.Out)
//等上升结束才开始移动不能使用链式调用,此时起始坐标才是上升后的坐标
.onComplete(() => {
//移动动画
let tween2 = creaeteTween(
{
x: -9.8,
y: 2.9,
z: -2.8,
},
{
x: -9.8,
y: 0,
z: 0,
}
);
tween2.start();
})
.start();
};
</script>
运行效果:
示例4:
相机漫游(常见于厂区、园区、城市等3D大模型中)
<template>
<button class="view-btn" @click="cameFlying">相机漫游</button>
<button class="view-btn-2" @click="stopFlying">停止漫游</button>
</template>
<script setup>
import * as THREE from "three";
//引入相机控制器
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import * as TWEEN from "@tweenjs/tween.js";
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
3000
);
camera.position.set(7.8, 0.09, 0.44);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 3);
scene.add(ambientLight);
//添加平行光
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(-5, 2, 200);
scene.add(light);
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建加载器实例
const loader = new GLTFLoader();
// 异步加载模型
loader.load(
"factory.gltf",
(gltf) => {
const model = gltf.scene;
//相机看向模型
camera.lookAt(model);
scene.add(model);
},
(xhr) => {
console.log(`已加载 ${(xhr.loaded / xhr.total) * 100}%`);
},
(error) => {
console.error("加载失败:", error);
}
);
// 创建 OrbitControls 控件
const controls = new OrbitControls(camera, renderer.domElement);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
controls.update();
TWEEN.update(); // 更新所有活动的补间动画
renderer.render(scene, camera);
}
animate();
/**
* 创建并执行动画
* @param endPos:动画结束相机位置
* @param endTarget:动画结束相机观察点
* @param duration:动画持续时长
*/
const creaeteTween = (endPos, endTarget, duration = 3000) => {
//返回Promise
return new Promise((resolve) => {
let tween = new TWEEN.Tween({
// 当前的位置和目标观察点
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
tx: controls.target.x,
ty: controls.target.y,
tz: controls.target.z,
})
.to(
{
// 动画结束相机位置坐标
x: endPos.x,
y: endPos.y,
z: endPos.z,
// 动画结束相机指向的目标观察点
tx: endTarget.x,
ty: endTarget.y,
tz: endTarget.z,
},
duration
)
.onUpdate(function (obj) {
// 动态改变相机位置
camera.position.set(obj.x, obj.y, obj.z);
// 动态改变相机观察点
controls.target.set(obj.tx, obj.ty, obj.tz);
controls.update();
})
.onComplete(() => {
//返回tween实例
resolve(tween);
})
.start();
});
};
let isFlying = false; //是否正在漫游
let tween = null; //tween实例
//开始相机漫游
const cameFlying = async () => {
isFlying = true;
while (isFlying) {
let endPos = camera.position.clone().sub(new THREE.Vector3(2, 0, 0)); //相机结束位置在当前位置x坐标-2
let endTarget = controls.target.clone().sub(new THREE.Vector3(2, 0, 0)); //结束目标观察点当前目标点x坐标-2
tween = await creaeteTween(endPos, endTarget); //同步执行
}
};
//停止相机漫游
const stopFlying = () => {
tween && tween.stop(); //停止动画
isFlying = false;
};
</script>
运行效果:
六、总结
通过本文的介绍,相信你已经对在 three.js 中使用 TweenJS 有了一定的了解。TweenJS 凭借其简单易用的 API 和丰富的动画效果,能为你的 three.js 项目增添更多活力。无论是简单的物体移动,还是复杂的序列动画,它都能很好地胜任。你可以根据实际需求,灵活运用 TweenJS 的各种功能,创建出更加精彩的 3D 动画效果。更多用法请查看官方文档(https://github.com/tweenjs/tween.js)。