PocketLibs(1)—— 動畫 tween.js

如何運行的?

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>

clipboard.png

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()
},

clipboard.png

點起飛,只會轉換到1500毫秒時的狀態。java

結合requestAnimationFrame(fn)

若是咱們不傳參數,那麼它會根據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()
},

easing函數

前面默認狀況下,數據變換和時間成正比的(Linear.None),咱們能夠經過傳遞參數easing()改變這種默認效果,內置了許多變換效果。app

clipboard.png

給TWEEN加上Bounce.Out效果dom

var tween = new TWEEN.Tween(this.position)
            .to(targetPos, 2000)
            .easing(TWEEN.Easing.Bounce.Out)
            .start();

clipboard.png

自定義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>

clipboard.png

以上noisyEasing(k)函數就是咱們基於內置的Bounce.Out實現的自定義變換。createGraph()函數是官方例子扣下來的,就是在頁面繪製下自定義變換的曲線。

clipboard.png

這是內置的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()
        }
    }
})

clipboard.png

因爲閉包的關係,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>

clipboard.png

可使用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()
    }
}

clipboard.png

當第二動畫的三圈轉完時,中止第三個動畫效果。

調用鏈條與鏈條循環

使用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>

clipboard.png

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>

clipboard.png

相關文章
相關標籤/搜索