前言
在Web
开发领域,Three.js
已经成为构建精彩3D
内容的首选库之一。它让开发者能够轻松地在浏览器中创建和展示复杂的3D
场景。然而,要让这些场景栩栩如生,平滑的动画效果是必不可少的。这就引入了Tween.js
——一个轻量级但功能强大的JavaScript
库,专门用于在Web
应用中创建平滑的补间动画。本文将探讨如何在Three.js
项目中集成并利用Tween.js
来增强3D
对象的动画表现。
简介
Tween.js
是一个简单易用的库,专注于数值的平滑插值(interpolation)
,非常适合于实现动画效果。无论是简单的颜色渐变、对象位置移动,还是复杂的序列动画,Tween.js
都能轻松应对。在与Three.js
结合时,它能显著提升3D
场景的互动性和视觉吸引力。
安装Tween.js
官方地址为:https://github.com/tweenjs/tween.js
npm install tween.js
当然three.js
包中自带的包含tween.js
,其中three.js
得tween地址为:node_moduls>three>examples>jsm>libs>tween.module.js
导入补间动画
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'
tween.js
的核心方法
.to()
方法
控制补间的运动形式及方向.to()
, 当tween
启动时,Tween.js
将读取当前属性值并 应用相对值来找出新的最终值。
.start(time)
方法
补间动画启动的方法,.start
方法接受一个参数 time
, 如果加入这个参数,那么补间不会立即开始直到特定时刻才会开始
.stop()
方法
关闭补间动画 .stop()
, 关闭这个正在执行的补间动画。
.repeat()
方法
使用该方法可以使动画重复执行,它接受一个参数 , 描述需要重复多少次。
.delay()
方法
延迟执行动画的方法.delay()
, 接受一个参数用于控制延迟的具体时间,表示延迟多少时间后才开始执行动画。
.pause()
方法
暂停动画.pause()
, 暂停当前补间运动,与resume
方法配合使用。
.resume()
方法
恢复动画 .resume()
, 恢复这个已经被暂停的补间运动。
.yoyo()
方法
控制补间重复的模式 .yoyo()
, 这个功能只有在使用repeat
时才有效果 ,该动画像悠悠球一样来回运动 , 而不是重新开始。
.update()
方法
更新补间动画 TWEEN.update()
, 动态更新补间运动一般配合 window.requestAnimationFrame
使用
.chain()
方法
链式补间动画,当我们顺序排列不同的补间动画时,比如我们在上一个补间结束的时候立即启动另外一个补间动画,使用 .chain()
方法来做。
//tweenB动画在tweenA动画完成后执行
tweenA.chain(tweenB);
在一些情况下,可能需要将多个补间链接到另一个补间,以使它们(链接的补间)同时开始动画:
tweenA.chain(tweenB,tweenC);
注意:
调用tweenA.chain(tweenB)
实际上修改了tweenA
,所以tweenA
总是在tweenA
完成时启动。 chain的返回值只是tweenA
,不是一个新的tween
。
.getAll()
方法
获取所有的补间组 TWEEN.getAll()
。
.removeAll()
方法
删除所有的补间组 TWEEN.removeAll()
。
.add()
方法
新增补间 TWEEN.add(tween)
,添加一个特定的补间 var tween=new TWEEN.Tween()
。
.remove()
方法
删除补间 TWEEN.remove(tween)
,删除一个特定的补间var tween=new TWEEN.Tween()
。
.Group()
方法
新增一个补间组,var Group=TWEEN.Group() , new TWEEN.Tween({ x: 1 }, Group)
,将已经配置好的补间动画进行分组 , TWEEN.update()
和TWEEN.removeAll()
, 不会影响到已经分好组的补间动画。
tween.js
回调函数
.onStart()
补间动画开始时执行
补间动画开始时执行,只执行一次,new TWEEN.Tween().onStart((obj)=>{})
, 补间开始时执行,只执行一次, 当使用repeat()
重复补间时,不会重复运行 onStart((obj)=>{})
obj补间对象作为第一个参数传入。
.onStop()
停止补间动画时执行
new TWEEN.Tween().onStop((obj)=>{})
, 当通过 onStop()
显式停止补间时执行,但在正常完成时并且在停止任何可能的链补间之前执行补间,onStop((obj)=>{})
obj补间对象作为第一个参数传入。
.onUpdate()
每次更新时执行
new TWEEN.Tween().onUpdate((obj)=>{})
, 每次补间更新时执行,返回实际更新后的值, onUpdate((obj)=>{})
obj补间对象作为第一个参数传入。
.onComplete()
补间动画完成时执行
new TWEEN.Tween().onComplete((obj)=>{})
, 当补间正常完成(即不停止)时执行 , onComplete((obj)=>{})
obj 补间对象作为第一个参数传入。
.onRepeat()
重复补间动画时执行
new TWEEN.Tween().onRepeat((obj)=>{})
, 当补间动画完成,即将进行重复动画的时候执行 ,onComplete((obj)=>{})
obj 补间对象作为第一个参数传入。
TWEEN.Easing 缓动函数
tween.js为我们封装好了常用的缓动动画,如线性,二次,三次,四次,五次,正弦,指数,圆形,弹性,下落和弹跳等缓动函数, 以及对应的缓动类型:In (先慢后快)
;Out (先快后慢)
和 InOut (前半段加速,后半段减速)
常见缓动动画
Linear
:线性匀速运动效果;Quadratic
:二次方的缓动(t^2)
;Cubic
:三次方的缓动(t^3)
;Quartic
:四次方的缓动(t^4)
;Quintic
:五次方的缓动(t^5)
;Sinusoidal
:正弦曲线的缓动(sin(t))
;Exponential
:指数曲线的缓动(2^t)
;Circular
:圆形曲线的缓动(sqrt(1-t^2))
;Elastic
:指数衰减的正弦曲线缓动;Back
:超过范围的三次方缓动((s+1)t^3 – st^2)
;Bounce
:指数衰减的反弹缓动。
以上每个效果都分三个缓动类型,分别是:
easeIn
:从0
开始加速的缓动,也就是先慢后快;
easeOut
:减速到0
的缓动,也就是先快后慢;
easeInOut
:前半段从0
开始加速,后半段减速到0
的缓动。
Tween.JS和Three.js示例
- 导入动画组件库
import * as TWEEN from "three/examples/jsm/libs/tween.module.js"
- 创建一个圆柱几何
const sphere1 = new THREE.Mesh(
new THREE.CylinderGeometry(1, 1, 1, 64),new THREE.MeshBasicMaterial( {color: 0xffff00} )
)
sphere1.position.set(0, 10, 0)
sphere1.position.x = 0
sphere1.position.y = 0
sphere1.position.z = 0
sphere1.scale.set(-1, -1, 1)
sphere1.rotation.z = -Math.PI/2
this.scene.add(sphere1)
- 创建tween实例和tween动画
const tween = new TWEEN.Tween(sphere1.position)
const tweenXZ = new TWEEN.Tween(sphere1.rotation)
const tween2 = new TWEEN.Tween(sphere1.position)
const tween3 = new TWEEN.Tween(sphere1.position)
const tween4 = new TWEEN.Tween(sphere1.position)
//移动
tween.to({x:4},300).onUpdate(()=>{})
//旋转
tweenXZ.to({y:-Math.PI/2},150).onUpdate(()=>{})
//移动
tween2.to({y:-4},300).onUpdate(()=>{})
//移动
tween3.to({x:0},300).onUpdate(()=>{})
//移动
tween4.to({y:0},300).onUpdate(()=>{})
// 一边移动一边旋转,动画在tween动画完成后执行tween2,并带有旋转效果
tween.chain(tween2,tweenXZ)
tween2.chain(tween3,tweenXZ)
tween3.chain(tween4,tweenXZ)
tween4.chain(tween,tweenXZ)
- 设置动画速度运行曲线
tween.easing(TWEEN.Easing.Quadratic.Inout)
- 开启动画
tween.start()
- 开启动画后还不能完全动起来,还需要逐帧更新动画
animate() {this.controls.update()TWEEN.update()requestAnimationFrame( this.animate );this.renderer.render( this.scene, this.camera );}
完整代码:
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/libs/tween.module.js"
export default {name: 'Three9',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{//随机生成十六进制颜色color16(){//十六进制颜色随机var r = Math.floor(Math.random()*256);var g = Math.floor(Math.random()*256);var b = Math.floor(Math.random()*256);var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);return color;},init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建渲染器this.renderer = new THREE.WebGLRenderer();//渲染器尺寸this.renderer.setSize( window.innerWidth, window.innerHeight ); // 创建三个球const sphere1 = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 1, 64),new THREE.MeshBasicMaterial( {color: 0xffff00} ))sphere1.position.set(0, 10, 0)sphere1.position.x = 0 sphere1.position.y = 0 sphere1.position.z = 0sphere1.scale.set(-1, -1, 1) sphere1.rotation.z = -Math.PI/2 this.scene.add(sphere1)const tween = new TWEEN.Tween(sphere1.position) const tweenXZ = new TWEEN.Tween(sphere1.rotation)const tween2 = new TWEEN.Tween(sphere1.position)const tween3 = new TWEEN.Tween(sphere1.position)const tween4 = new TWEEN.Tween(sphere1.position)//移动tween.to({x:4},300).onUpdate(()=>{})//旋转tweenXZ.to({y:-Math.PI/2},150).onUpdate(()=>{})//移动tween2.to({y:-4},300).onUpdate(()=>{})//移动tween3.to({x:0},300).onUpdate(()=>{})//移动tween4.to({y:0},300).onUpdate(()=>{})// 一边移动一边旋转,动画在tween动画完成后执行tween2,并带有旋转效果tween.chain(tween2,tweenXZ)tween2.chain(tween3,tweenXZ)tween3.chain(tween4,tweenXZ) tween4.chain(tween,tweenXZ)//动画运行速度曲线tween.easing(TWEEN.Easing.Quadratic.Inout) tween.start()this.scene.background=new THREE.Color(0x999999)// 设置相机位置this.camera.position.z = 15; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器this.controls = new OrbitControls(this.camera,this.renderer.domElement)//添加阻尼带有惯性this.controls.enableDamping = true//设置阻尼系数this.controls.dampingFactor = 0.05//元素中插入canvas对象container.appendChild(this.renderer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGL.getWebGLErrorMessage();document.getElementById( document.body ).appendChild( warning );}},//旋转起来animate() {this.controls.update()TWEEN.update()requestAnimationFrame( this.animate );this.renderer.render( this.scene, this.camera );}}
}
</script>