PocketLibs—— 动画 tween.js

Posted jlfw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PocketLibs—— 动画 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>

技术图片

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>

技术图片

以上是关于PocketLibs—— 动画 tween.js的主要内容,如果未能解决你的问题,请参考以下文章

tween.js的动画效果

tween.js的动画效果

vue three.js 结合tween.js 实现动画过渡

Tween.js 动画效果

tween 缓动动画

window.requestAnimationFrame与Tween.js配合使用实现动画缓动效果