如何运行的?
new Vue({
el:‘#app-1‘,
data:{
position:{
distance:10,
height:30,
}
},
methods:{
flying:function(){
var targetPos = {distance:300,height:120}
var tween = new TWEEN.Tween(this.position)
function animate(time){
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if(!isFlying) cancelAnimationFrame(id);
}
tween.to(targetPos, 2000)
tween.start()
//内部有个this._startTime,如果传的time-this._startTime大于2000
//那么这个update只会执行一次
// animate(3000)
animate()
},
}
})
#app-1 p{
font-size: 2em;
position: absolute;
color:#fff;
}
<div id="app-1" class="bg-dark" style="width:350px;height:180px;">
<button @click="flying()" class="btn btn-info pl-4 pr-4 pt-1 pb-1 mt-1 ml-1 font-weight-bold">fly</button>
<p v-bind:style="{ left: position.distance + ‘px‘, top: position.height + ‘px‘ }">?</p>
</div>
TWEEN作用就是对一个对象持续的进行线性式的改动,比如对象原始状态为{distance:10,height:30}
,最终状态为{distance:300,height:120}
,我们设置个时间,TWEEN就在该时间内把对象原始的状态渐进的变换成最终状态。
在初始化实例new TWEEN.Tween(original-obj)
时设置原始数据对象,在实例方法to(final-obj,time)
中设置最终数据对象及时间。通过实例方法start()
启动一个TWEEN。然后通过全局方法TWEEN.update(time)
去改动对应时间的原始数据对象,每一刻的时间对应着一份修改的数据,大概像下面这样:
调用update() | 调用后数据的改变 |
---|---|
TWEEN.update(20) |
{distance:15,height:31} |
TWEEN.update(40) |
{distance:25,height:33} |
TWEEN.update(70) |
{distance:45,height:37} |
如修改下代码:
flying:function(){
var targetPos = {distance:300,height:120}
var tween = new TWEEN.Tween(this.position)
// function animate(time){
// var id = requestAnimationFrame(animate);
// var isFlying = TWEEN.update(time);
// if(!isFlying) cancelAnimationFrame(id);
// }
tween.to(targetPos, 2000)
tween.start()
TWEEN.update(1500);
//内部有个this._startTime,如果传的time-this._startTime大于2000
//那么这个update只会执行一次
// animate(3000)
// animate()
},
点起飞,只会转换到1500毫秒时的状态。
结合requestAnimationFrame(fn)
如果我们不传参数,那么它会根据start()
的时间自动计算好当前的时间再修改该时间数据。因为最后数据是要渲染成动画形式,我们不可能一行行去调用update()
。
因此要结合一个神器requestAnimationFrame(fn)
,这个函数就是在浏览器每一帧重绘一次屏幕,非常的稳定。在这里如果是60fps的情况下就是16ms(1000ms/60)调用一次animate()
,然后我们在这个animate()
重绘循环中不停的调用update()
,它就可以线性的修改数据,渲染之后就形成动画。
TWEEN也可以链式调用函数。开始的代码可以修改成:
flying:function(){
var targetPos = {distance:300,height:120}
var tween = new TWEEN.Tween(this.position)
.to(targetPos, 2000)
.start();
function animate(time){
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if(!isFlying) cancelAnimationFrame(id);
}
animate()
},
easing函数
前面默认情况下,数据变换和时间成正比的(Linear.None
),我们可以通过传递参数easing()
改变这种默认效果,内置了许多变换效果。
给TWEEN加上Bounce.Out
效果
var tween = new TWEEN.Tween(this.position)
.to(targetPos, 2000)
.easing(TWEEN.Easing.Bounce.Out)
.start();
自定义easing变换函数
TWEEN里最有特色的功能就是easing()
参数可以是一个自定义的函数,这样可以实现变换的定制。
function customerFn(k){
return fn(k) //用k作基础的自定义运算
}
tween.easing(customerFn);//写好后传给easing()函数就行
这个k
是系统调用的,跟你无关,它是一个在[0,1]范围内不停增长的值,速度与时间成正比,函数返回一个基于k
进行运算后的值。比如简单的像Linear.None
那样的默认变换,就是不运算直接返回k
。
我们也可使用以有的变换做运算,正如下面这样做。
methods: {
flying: function () {
var targetPos = { distance: 300, height: 120 }
/** 绘制变换曲线 */
var target = document.getElementById(‘target‘);
target.appendChild(this.createGraph(‘Noisy Exponential.InOut‘, noisyEasing) );
function noisyEasing(k) {
return 0.3 * Math.random() + 0.7 * TWEEN.Easing.Bounce.Out(k);
}
var tween = new TWEEN.Tween(this.position)
.to(targetPos, 2000)
.easing(noisyEasing)
.start();
function animate(time) {
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if (!isFlying) cancelAnimationFrame(id);
}
animate()
},
createGraph:function( t, f, c ) {
var div = document.createElement( ‘div‘ );
div.style.display = ‘inline-block‘;
div.style.width = ‘200px‘;
div.style.height = ‘120px‘;
var canvas = document.createElement( ‘canvas‘ );
canvas.width = 180;
canvas.height = 100;
var context = canvas.getContext( ‘2d‘ );
context.fillStyle = "rgb(250,250,250)";
context.fillRect( 0, 0, 180, 100 );
context.lineWidth = 0.5;
context.strokeStyle = "rgb(230,230,230)";
context.beginPath();
context.moveTo( 0, 20 );
context.lineTo( 180, 20 );
context.moveTo( 0, 80 );
context.lineTo( 180, 80 );
context.closePath();
context.stroke();
context.lineWidth = 2;
context.strokeStyle = "rgb(255,127,127)";
var position = { x: 5, y: 80 };
var position_old = { x: 5, y: 80 };
new TWEEN.Tween( position ).to( { x: 175 }, 2000 ).easing( TWEEN.Easing.Linear.None ).start();
new TWEEN.Tween( position ).to( { y: 20 }, 2000 ).easing( f ).onUpdate( function () {
context.beginPath();
context.moveTo( position_old.x, position_old.y );
context.lineTo( position.x, position.y );
context.closePath();
context.stroke();
position_old.x = position.x;
position_old.y = position.y;
}).start();
div.appendChild( document.createTextNode( t ) );
div.appendChild( document.createElement( ‘br‘ ) );
div.appendChild( canvas );
return div;
}
}
<body>
<div id="app-1" class="bg-dark" style="width:350px;height:180px;">
<button @click="flying()" class="btn btn-info pl-4 pr-4 pt-1 pb-1 mt-1 ml-1 font-weight-bold">起飞</button>
<p v-bind:style="{ left: position.distance + ‘px‘, top: position.height + ‘px‘ }">?</p>
</div>
<div id="target"></div>
</body>
以上noisyEasing(k)
函数就是我们基于内置的Bounce.Out
实现的自定义变换。createGraph()
函数是官方例子扣下来的,就是在页面绘制下自定义变换的曲线。
这是内置的Bounce.Out
变换,其实就是做了一丝丝抖动效果。
回调方法
TWEEN的四种状态启动、停止、更新和完成,每种下面都可以绑定一个回调函数,onStart(fn)
、onStop(fn)
、onUpdate(fn)
和onComplete(fn)
。例如飞行动画结束后,将飞机复位。
new Vue({
el: ‘#app-2‘,
data: {
position: {
distance: 10,
height: 30,
}
},
methods: {
flying: function () {
var _this = this
var targetPos = { distance: 300, height: 120 }
var tween = new TWEEN.Tween(this.position)
tween.to(targetPos, 5000)
.easing(TWEEN.Easing.Circular.InOut)
.onComplete(function () {
_this.position.distance = 10
_this.position.height = 30
})
function animate(time) {
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if (!isFlying) cancelAnimationFrame(id);
}
tween.start()
animate()
}
}
})
由于闭包的关系,Vue里的this
不能用钩子函数里,因此定义了一个中间变量_this
。
其实update()
是最常用的钩子,一般用来在每次修改后对页面元素做数据绑定,但这里有Vue就不需要它了。
循环执行(repeat)与停止动画(stop)
调用实例方法repeat(frequency)
设置动画循环次数,若参数为Infinity
,则循环无限次。
new Vue({
el: ‘#app-3‘,
data: {
rotation: {
x: 0, y: 0, z: 0
}
},
methods: {
rotate: function () {
var tween_x= new TWEEN.Tween(this.rotation)
.to({ x: 360 })
var tween_y = new TWEEN.Tween(this.rotation)
.to({ y: 360 }).repeat(3)
var tween_z = new TWEEN.Tween(this.rotation)
.to({ z: 360 }).repeat(Infinity)
function animate(time) {
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if (!isFlying) cancelAnimationFrame(id);
}
tween_x.start()
tween_y.start()
tween_z.start()
animate()
}
}
})
<style>
#app-3 i {
opacity: 0.7;
}
</style>
<div id="app-3">
<button @click="rotate()" class="btn btn-info pl-4 pr-4 pt-1 pb-1 mt-1 ml-1 font-weight-bold">旋转</button>
<div class="row ml-5 mb-4 mt-4">
<span class="ml-3 mr-5">转一圈</span>
<span class="ml-1 mr-4">转三圈</span>
<span class="ml-3 mr-4">无限转</span>
</div>
<div class="row ml-5">
<!-- 需要导入font-awesome字库 -->
<i class="fa fa-spinner fa-5x mr-3" :style="{transform:‘rotate(‘ + rotation.x + ‘deg)‘}"></i>
<i class="fa fa-spinner fa-5x mr-3" :style="{transform:‘rotate(‘ + rotation.y + ‘deg)‘}"></i>
<i class="fa fa-spinner fa-5x mr-3" :style="{transform:‘rotate(‘ + rotation.z + ‘deg)‘}"></i>
</div>
</div>
可以使用tween.stop()
停止动画,修改上段JS代码:
methods: {
rotate: function () {
var tween_x= new TWEEN.Tween(this.rotation)
.to({ x: 360 })
var tween_y = new TWEEN.Tween(this.rotation)
.to({ y: 360 }).repeat(3).onComplete(function(){
//当第二个tween动画完成时,停止第三个tween运行
tween_z.stop()
})
var tween_z = new TWEEN.Tween(this.rotation)
.to({ z: 360 }).repeat(Infinity)
function animate(time) {
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if (!isFlying) cancelAnimationFrame(id);
}
tween_x.start()
tween_y.start()
tween_z.start()
animate()
}
}
当第二动画的三圈转完时,停止第三个动画效果。
调用链条与链条循环
使用tween_a.chain(tween_b)
可以按顺序的(先a后b)链式调用多个tween实例,如果接着调用tween_b.chain(tween_a)
调用将进入无限循环中(执行a->b->a->b),如下:
new Vue({
el: ‘#app-4‘,
data: {
position: { x: 20, y: 0 }
},
methods: {
move: function () {
console.log(‘aaa‘);
var tween_a = new TWEEN.Tween(this.position)
.to({ x: 280, y: 0 }, 3000)
var tween_b = new TWEEN.Tween(this.position)
.to({ x: 280, y: 120 }, 3000)
var tween_c = new TWEEN.Tween(this.position)
.to({ x: 20, y: 120 }, 3000)
var tween_d = new TWEEN.Tween(this.position)
.to({ x: 20, y: 0 }, 3000)
function animate(time) {
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if (!isFlying) cancelAnimationFrame(id);
}
tween_a.chain(tween_b)
tween_b.chain(tween_c)
tween_c.chain(tween_d)
tween_d.chain(tween_a)
tween_a.start()
animate()
}
}
})
<style>
#app-4 i {
position: relative;
color: #fff;
}
</style>
<div id="app-4" class="bg-dark pl-2" style="width:340px;height:210px" >
<button @click="move()" class="btn btn-info pl-4 pr-4 pt-1 pb-1 mt-1 ml-1 mb-2 font-weight-bold">启动</button><br>
<i class="fa fa-taxi fa-2x" v-bind:style="{ left: position.x + ‘px‘, top: position.y + ‘px‘ }"></i>
</div>
YOYO
如果TWEEN有循环(调用repeat()
),并调用tween.yoyou(true)
,那么在执行下一次动画之前,动画会弹到起始位置。
el: ‘#app-5‘,
data: {
position: { x: 20 }
},
methods: {
move: function () {
var tween = new TWEEN.Tween(this.position)
.to({ x: 280 }, 1000).repeat(1).yoyo(true)
function animate(time) {
var id = requestAnimationFrame(animate);
var isFlying = TWEEN.update(time);
if (!isFlying) cancelAnimationFrame(id);
}
tween.start()
animate()
}
}
<style>
#app-5 i {
position: relative;
color: #fff;
}
</style>
<div id="app-5" class="bg-dark pl-2" style="width:340px;height:210px">
<button @click="move()" class="btn btn-info pl-4 pr-4 pt-1 pb-1 mt-1 ml-1 mb-2 font-weight-bold">启动</button>
<br>
<i class="fa fa-taxi fa-2x" v-bind:style="{ left: position.x + ‘px‘, top: ‘40px‘ }"></i>
</div>