Threejs -- TweenJS自定义flyTo函数
TweenJS
笔记末尾附自定义flyTo函数
动画库tweenjs简介和引入项目
TweenJS是一个有javascript语言编写的补间动画库,如果需要tweenjs辅助你生成动画,对于任何前端web项目,你都可以选择tweenjs库。
如果你是用three.js开发web3d项目,使用tween.js辅助three.js生成动画效果也是比较好的选择,比如微信小游戏跳一跳中的模型伸缩、跳动动作。
- github地址:https://github.com/tweenjs/tween.js
- npm地址:https://www.npmjs.com/package/@tweenjs/tween.js
- 官网:http://www.createjs.cc/tweenjs
.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库提供了onStart
、onUpdate
、onComplete
等用于控制动画执行的回调函数。
// 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.In
、TWEEN.Easing.Linear.Out
或TWEEN.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()
}