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)
去改动对应时间的原始数据对象,每一刻的时间对应着一份修改的数据,大概像下面这样:javascript
调用update() | 调用后数据的改变 |
---|---|
TWEEN.update(20) |
{distance:15,height:31} |
TWEEN.update(40) |
{distance:25,height:33} |
TWEEN.update(70) |
{distance:45,height:37} |
如修改下代码:css
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毫秒时的状态。java
若是咱们不传参数,那么它会根据start()
的时间自动计算好当前的时间再修改该时间数据。由于最后数据是要渲染成动画形式,咱们不可能一行行去调用update()
。canvas
所以要结合一个神器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() },
前面默认状况下,数据变换和时间成正比的(Linear.None
),咱们能够经过传递参数easing()
改变这种默认效果,内置了许多变换效果。app
给TWEEN加上Bounce.Out
效果dom
var tween = new TWEEN.Tween(this.position) .to(targetPos, 2000) .easing(TWEEN.Easing.Bounce.Out) .start();
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(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>
若是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>