Threejs -- TweenJS自定义flyTo函数

发布时间 2023-09-27 18:30:37作者: lisashare

TweenJS

参考文档

笔记末尾附自定义flyTo函数

动画库tweenjs简介和引入项目

TweenJS是一个有javascript语言编写的补间动画库,如果需要tweenjs辅助你生成动画,对于任何前端web项目,你都可以选择tweenjs库。

如果你是用three.js开发web3d项目,使用tween.js辅助three.js生成动画效果也是比较好的选择,比如微信小游戏跳一跳中的模型伸缩、跳动动作。

.html引入

写一个简单小例子或者学习的时候,你可以在.html文件中直接引入tween.js
github下载的文件中tween.umd.js添到.html文件中,TWEEN会作为全局变量存在,你通过TWEEN可以访问tweenjs所有方法。

<script src="./tween.js-master/dist/tween.umd.js"></script>

npm安装

在工程化开发的时候,可以通过npm命令进行安装tween.js模块

npm i @tweenjs/tween.js@^18
const TWEEN = require('@tweenjs/tween.js')
// 或者
import TWEEN from '@tweenjs/tween.js'
a.chain(b)
b.chain(a)
// 这样写可以进行连续循环动画

回调函数

tweenjs库提供了onStartonUpdateonComplete等用于控制动画执行的回调函数。

// x:模型x坐标
var pos = {x: 0}
var tween = new TWEEN.Tween(pos).to({x: 100}, 4000)
// 开始执行:动画片段tween开始执行的时候触发onStart
.onStart(function() {
  mesh.material.color.set(0xff0000)
})
// 正在执行:动画片段tween正在执行的时候触发onUpdate
// tween动画执行期间,.onUpdate()重复执行
.onUpdate(function() {
  // 更新mesh坐标x
  mesh.position.x = pos.x
})
// 执行完成:动画片段tween执行完成的时候触发onComplete
.onComplete(function() {
  mesh.material.color.set(0x00ff00)
})

onStop

手动执行tween.stop()停止动画会触发回调函数onStop执行。

一段tween完成后多个tween同步执行

通过构造函数TWEEN.Tween()可以实例化一个对象tween,该对象tween表示一段动画。

动画片段A执行完成后,动画片段B和C基本同步执行。

a.chain(b) // 动画片段a完成接着执行动画片段b
b.chain(c)

d.chain(e)
e.chain(f)

// 动画片段a执行完触发回调函数a.onComplete执行
a.onComplete(function() {
  d.start() // 动画片段d开始执行
})

或者

// a.chain(b) // 动画片段a完成接着执行动画片段b
b.chain(c)

d.chain(e)
e.chain(f)

// 动画片段a执行完触发回调函数a.onComplete执行
a.onComplete(function() {
  b.start() // 触发动画片段b、d开始执行
  d.start() 
})

批量创建tween动画片段并串连

// 生成mesh沿着系列轨迹坐标pointArr运动的动画
var pointArr = [
  0,0,-150,
  100,0,-150,
  100,0,-50,
  0,0,-50,
  0,0,50,
  100,0,50,
  100,0,150,
  0,0,150
] 
var pos = { x: pointArr[0], y: pointArr[1], z: pointArr[2] } // mesh坐标
var tweenArr = [] // 所有动画片段tween的集合
// 批量创建动画片段tween
for (var i = 3;i < pointArr.length; i+=3) { // 跳过pointArr第一个点坐标
  var tween = new TWEEN.Tween(pos)
  .to({ x: pointArr[i], y: pointArr[i+1], z: pointArr[i+2] }, 1000)
  .onUpdate(function() {
    mesh.position.x = pos.x
    mesh.position.z = pos.z
  })
  tweenArr.push(tween)
}
// 批量连接所有动画片段
for (var i = 0;i < tweenArr.length - 1; i++) {
  tweenArr[i].chain(tweenArr[i + 1])
}
tweenArr[0].start() // 播放一串动画片段

.easing()方法(缓动算法)

动画片段tween通过.easing()方法可以设置缓动算法。在一些动画场景中你设置合理的缓动算法,可以让动画看起来非常自然,比如一辆车从静止进入匀速状态,动画最好有一个加速过程的过渡,对于这个加速的方式就可以通过缓动算法实现。

形象理解,所谓缓动,你可以理解为运动缓缓加速的过程,缓动算法就是运动加速的算法,推广一下,减速理,再推广一下,不一定针对运动,比如颜色渐变也可以类比运动加减速。

.easing()语法格式

// easing函数:缓动算法(运动效果)
// easing类型:定义缓动算法起作用地方
tween.easing(TWEEN.Easing.easing函数.easing类型)

easing类型(定义缓动算法起作用地方)

easing函数和easing类型都有多种方式,可以自由组合使用(Linear除外)。

// 动画开始缓动方式(类比加速启动)
tween.easing(TWEEN.Easing.Sinusoidal.In)
// 动画结束缓动方式(类比减速刹车)
tween.easing(TWEEN.Easing.Sinusoidal.Out)
// 同时设置In和Out
tween.easing(TWEEN.Easing.Sinusoidal.InOut)

easing函数(定义缓动算法)

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:指数衰减的正弦曲线缓动;TWEEN.Easing.Elastic.inout会有弹性衰减往复运动感
Back:超过范围的三次方缓动((s+1)*t^3 - s*t^2)会有弹性衰减往复运动感
Bounce:指数衰减的反弹缓动,会有弹性衰减往复运动感

匀速运动(特殊情况说明)

Linear:默认效果可以不设置,可以理解为没有加速过程直接进入匀速状态,或者说没有减速过程,直接刹车。

注意:匀速设置TWEEN.Easing.Linear.None(默认效果可以不设置)

对于Linear不要设置TWEEN.Easing.Linear.InTWEEN.Easing.Linear.OutTWEEN.Easing.Linear.InOut,会报错

官方案例03_graphs.html

官方案例03_graphs.html,可以查看各种缓动算法的曲线效果图。

模型颜色渐变动画

var obj = {r: 1, g: 0, b: 0}
var tween = new TWEEN.Tween(obj) // 创建一段tween动画
tween.to({r: 0, g: 1, b: 1}, 4000)
tween.onUpdate(function() {
  mesh.material.color.setRGB(obj.r, obj.g, obj.b)
})
tween.start()
function render() {TWEEN.update() ...}

自定义 flyTo 函数

/**
 * 
 * @param {Object} options TWEEN参数
 * @param {Boolean} e 是否清除所有的TWEEN动画
 * @param {Function} callback 回调函数
 */
function flyTo(options, e, callback) {
	const cameraPos = camera.position.clone(); 
	const controlsTar = controls.target.clone();
	options.position = options.position || [cameraPos.x, cameraPos.y, cameraPos.z];
	options.controls = options.controls || [controlsTar.x, controlsTar.y, controlsTar.z];
	options.duration = options.duration || 1000;
	options.easing = options.easing || TWEEN.Easing.Linear.None;
	e !== false && TWEEN.removeAll();
	const r = new TWEEN.Tween({
		number: 0,
		x1: cameraPos.x,
		y1: cameraPos.y,
		z1: cameraPos.z,
		x2: controlsTar.x,
		y2: controlsTar.y,
		z2: controlsTar.z
	}).to({
		number: 1,
		x1: options.position[0],
		y1: options.position[1],
		z1: options.position[2],
		x2: options.controls[0],
		y2: options.controls[1],
		z2: options.controls[2]
	}, options.duration).easing(options.easing)
	.onStart(() => {
		controls.enabled = false
	}).onUpdate(e => {
		controls.enabled = false
		let cpx = (options.position[0] - cameraPos.x) * e.number + cameraPos.x
			, cpy = (options.position[1] - cameraPos.y) * e.number + cameraPos.y
			, cpz = (options.position[2] - cameraPos.z) * e.number + cameraPos.z
			, ctx = (options.controls[0] - controlsTar.x) * e.number + controlsTar.x
			, cty = (options.controls[1] - controlsTar.y) * e.number + controlsTar.y
			, ctz = (options.controls[2] - controlsTar.z) * e.number + controlsTar.z;

		camera.position.set(cpx, cpy, cpz);
		controls.target.set(ctx, cty, ctz);
		controls.update()
	}).onComplete(() => {
		controls.enabled = true
		if (callback && typeof callback === 'function' ) {
			callback()
		}
	}).start()
}