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>