原生JavaScript面向对象封装动画效果原理详解

一、JavaScript中动画原理

所谓的动画,就是通过一些列的运动形成的动的画面。在网页中,我们可以通过不断的改变元素的css值,来达到动的效果。

JavaScript的动画用的最多的3个api就是setInterval()、setTimeout()和requestAnimationFrame()

据说,普通人眼能看到1/24秒,就是说1秒至少24帧,每次移位间隔需要小于1000/24=41.7毫秒,也就说setInterval要每隔至少40毫秒执行一次,一般地,我们采用10毫秒,当然间隔时间越短,客户端执行计算次数就越多,如果你code计算量大则可以适当调长些。

1.1 setTimeout()和setInterval ()

1.2 requestAnimationFrame(回调函数)

像setTimeout、setInterval一样,requestAnimationFrame是一个全局函数。调用requestAnimationFrame后,它会要求浏览器根据自己的频率进行一次重绘,它接收一个回调函数作为参数,在即将开始的浏览器重绘时,会调用这个函数,并会给这个函数传入调用回调函数时的时间作为参数。由于requestAnimationFrame的功效只是一次性的,所以若想达到动画效果,则必须连续不断的调用requestAnimationFrame,就像我们使用setTimeout来实现动画所做的那样。requestAnimationFrame函数会返回一个资源标识符,可以把它作为参数传入cancelAnimationFrame函数来取消requestAnimationFrame的回调。跟setTimeout的clearTimeout很相似啊。

可以这么说,requestAnimationFrame是setTimeout的性能增强版。

有一点需要注意的是,requestAnimationFrame不能自行指定函数运行频率,而是有浏览器决定刷新频率。所以这个更能达到浏览器所能达到的最佳动画效果了。

这个方法不是所有的浏览器都兼容。

下面就从最简单的版本进行出发:(可以直接看最后一个版本

首先这些版本都有同一个js文件是一样的,那就是Easing文件,这里封装了很多的匀速,匀加速,弹跳等等动画,会返回一个动画算子,所以这个文件是共有的,我先放上来:

Easing.js

var pow = Math.pow,
    BACK_CONST = 1.70158;
// t指的的是动画进度(百分比) 归一化的时间 前面的p
Easing = {
// 匀速运动
    linear: function (t){
        return t;
    },
// 匀加速运动
    easeIn: function (t){
        return t * t;
    },
// 减速运动
    easeOut: function (t){
        return (2 - t) * t;
    },
//先加速后减速
    easeBoth: function (t){
        return (t *= 2) < 1 ? .5 * t * t : .5 * (1 - (--t) * (t - 2));
    },
// 4次方加速
    easeInStrong: function (t){
        return t * t * t * t;
    },
// 4次方法的减速
    easeOutStrong: function (t){
        return 1 - (--t) * t * t * t;
    },
// 先加速后减速,加速和减速的都比较剧烈
    easeBothStrong: function (t){
        return (t *= 2) < 1 ? .5 * t * t * t * t : .5 * (2 - (t -= 2) * t * t * t);
    },
//
    easeOutQuart: function (t){
        return -(Math.pow((t - 1), 4) - 1)
    },
// 指数变化 加减速
    easeInOutExpo: function (t){
        if (t === 0) return 0;
        if (t === 1) return 1;
        if ((t /= 0.5) < 1) return 0.5 * Math.pow(2, 10 * (t - 1));
        return 0.5 * (-Math.pow(2, -10 * --t) + 2);
    },
//指数式减速
    easeOutExpo: function (t){
        return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
    },
// 先回弹,再加速
    swingFrom: function (t){
        return t * t * ((BACK_CONST + 1) * t - BACK_CONST);
    },

// 多走一段,再慢慢的回弹
    swingTo: function (t){
        return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1;
    },

//弹跳
    bounce: function (t){
        var s = 7.5625,
            r;

        if (t < (1 / 2.75)){
            r = s * t * t;
        }else if (t < (2 / 2.75)){
            r = s * (t -= (1.5 / 2.75)) * t + .75;
        }else if (t < (2.5 / 2.75)){
            r = s * (t -= (2.25 / 2.75)) * t + .9375;
        }else{
            r = s * (t -= (2.625 / 2.75)) * t + .984375;
        }

        return r;
    }
};

版本1:

最基本的,没有用动画Easing.js文件:




    
    Title
    



版本2:

需要明白这一个个版本是相互递进的,每个新的版本实现的功能一步一步深入。

①用了Easing.js文件。

②HTML文件




    
    Title
    
    
    



版本3:

开始封装框架。

①用了Easing.js文件。

②HTML文件




    
    Title
    
    
    
    
    



③框架js文件:

/*
        为了实现更加复杂的动画,我们可以将动画进行 简易 的封装,要进行封装,我们先要抽象出动画相关的要素:
        动画时长:T = duration
        动画进程:p = t/T  进度(百分比)
        easing: e = f(p) (动画算子:p的函数 )
        动画方程: x = g(e) y = g(e) (动画的位移相对于动画算子的方程)
        动画生命周期:开始、进程中、结束
*/
/*
      参数1:动画的执行时间(周期duration)
      参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
      参数3:动画执行的时候的回调函数(动画执行的要干的事情)

*/

function MyAnimator(duration,easing,callBack) {
    this.duration=duration;
    this.easing=easing || function (p) {
        return p;
    };
    this.callBack=callBack;
}
MyAnimator.prototype={
    start:function () {
        var startTime=new Date();
        var that= this;  //回调函数this丢失
        requestAnimationFrame(function step() {
            var p = Math.min(new Date()-startTime)/that.duration;  //p是动画算子
            var e = that.easing(p);  //得到p的具体值,然后去调用动画算子函数,返回一个值
            that.callBack(e);  //把需要的E给调用函数(在HTML中script中用户会自己去写这个回调函数)
            if(p<1){
                requestAnimationFrame(step);
            }
        });
    },
}

版本4:

①用了Easing.js文件。

②HTML文件




    
    Title
    
    
    
    
    



③框架js文件:

/*
        为了实现更加复杂的动画,我们可以将动画进行 简易 的封装,要进行封装,我们先要抽象出动画相关的要素:
        动画时长:T = duration
        动画进程:p = t/T  进度(百分比)
        easing: e = f(p) (动画算子:p的函数 )
        动画方程: x = g(e) y = g(e) (动画的位移相对于动画算子的方程)
        动画生命周期:开始、进程中、结束
*/
/*
      参数1:动画的执行时间(周期duration)
      参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
      参数3:动画执行的时候的回调函数(动画执行的要干的事情)

*/

function MyAnimator(duration,easing,callBack) {
    this.duration=duration;
    this.easing=easing || function (p) {
        return p;
    };
    this.callBack=callBack;
}
MyAnimator.prototype={
    start:function () {
        var startTime=new Date();
        var that= this;  //回调函数this丢失
        requestAnimationFrame(function step() {
            var p = Math.min(new Date()-startTime)/that.duration;  //p是动画算子
            var e = that.easing(p);  //得到p的具体值,然后去调用动画算子函数,返回一个值
            that.callBack(e);  //把需要的E给调用函数(在HTML中script中用户会自己去写这个回调函数)
            if(p<1){
                requestAnimationFrame(step);
            }
        });
    },
}

版本5:

①用了Easing.js文件。

②HTML文件




    
    Title
    
    
    
    
    



③框架js文件:

/*
     参数1:动画的执行时间
     参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
     参数3:动画执行的时候的回调函数(动画执行的要干的事情)

    */
function MyAnimatorPlus(duration,Easing,callBack) {
    //默认参数,如果用户没有输入周期,动画运算函数,回调函数,我们就可以进行默认处理
    this.duration = duration || 1000;
    this.Easing = Easing || Easing.linear;
    this.callBack = callBack || function () {};
}
MyAnimatorPlus.prototype = {
    /*开始动画的方法,
     参数:一个数字,执行的次数
    */
    start: function(count) {
        if(count==undefined || count ==null ||count <=0) count=1;
        /*动画开始时间*/
        var startTime = new Date();
            /*回调函数的this失效*/
            self = this;
        /*定义动画执行函数*/
        requestAnimationFrame(function step() {
            /*得到动画执行进度*/
            var p = Math.min(1,(new Date()-startTime)/self.duration);
            self.callBack(self.Easing(p));

            //判断动画进度是否完成,count进行判断,以实现循环执行
            //当p==1时,那就是说明一次动画执行完成,判断count值
            if(p==1){
                count--;  //一次写完就要要减去count值,再去判断是否小于0
                if(count > 0){    //如果大于0 则说明还要在执行,那么这个时候就要有新的时间
                    startTime = new Date();
                    requestAnimationFrame(step);
                }
            }else{    //else的部分就是P<1说明每次的一次动画还未结束,所以要继续运动
                requestAnimationFrame(step);
            }


            /*
            //另外的写法,简便一些
            //如果p等于1 并且 操作count-- ,如现在count的值是1,那么--就是0了(前面已经执行了一遍,初始化),便不在执行
            if(p==1 && --count){
                startTime = new Date();
                requestAnimationFrame(step);
            }else if(p<1){
                requestAnimationFrame(step);
            }*/

        });
    },
};

版本6:

直接可以用这个,主要用到了:

①用了Easing.js文件。(最前面一个)我的命名是Easing.js

②HTML文件.(我的命名是lastVersion.html)




    
    Title
    
    
    
    
    




③框架js文件

(我的命名是:MyAnimatorPlusPlus.js)
这个框架是直接把传入的实参直接用对象包括起来,多个不同的动画效果是用数组进行存贮。

这个框架可以实现的功能有:

可以反转:是为normal 这个正常运动,另外一个是reserve是一次正常一次反转。两个值可选。

周期:传入每一个动画的周期

动画算子函数:传入

回调函数:传入用户想要的效果(其中距离要自己写)

具体效果如下:

原生JavaScript面向对象封装动画效果原理详解_第1张图片


/*执行动画序列
    封装一个对象
     duration:动画的执行时间
     eases:动画算子. 如果没有传入动画算子,则默认使用匀速算子
     callBack:动画执行的时候的回调函数(动画执行的要干的事情)

    */

function MyAnimatorPlusPlus(opts){
    this.opts = opts;
}
/*几个参数作用:
*count:是执行几回,用户传来3,就要循环执行三次。执行一次减掉一次。
*index:就是durations 、callBacks 、Easings三个数组的下标,一一对应,下标不要越界,一越界就变成0
* i:初始化为0,看他执行了几个count,如果是奇数,就是反转,反转就是要逆转数组。为反转做准备的变量。
*
* */
MyAnimatorPlusPlus.prototype = {
    start: function (count){
        //当在start()中存在不传,负值,字符串都可以进行操作
        if (count == undefined || count == null || count <= 0) count = 1;

        var durations = this.opts.durations,
            Easings = this.opts.Easings,
            callBacks = this.opts.callBacks,
            selectDirection = this.opts.selectDirection;

        var startTime = new Date(),
            self = this,
            index = 0;  // 表示正在执行的动画的索引,默认是用数组0的开始执行
        var i = 0;    //默认0,为改变方向做准备的变量,当他为奇数(也就是执行第二次,那就改变方向咯)
        requestAnimationFrame(function step(){
            var p = Math.min(1, (new Date() - startTime) / durations[index]);  //归一化时间,注意写法
            /*self.callBacks[index](self.eases[index](p));*/

            var e = Easings[index](p);      //得到动画算子
            /*判断如果是reverse,并且执行动画已经第二次了,那么就改变方向 i如果是奇数,那么就是第二次执行了,就改变方向*/
            if (i % 2 == 1 && selectDirection == "reverse"){
                e = 1 - e;
            }
            callBacks[index](e);
            /*核心代码,深度理解:
            * p==1表示已经执行了一回
            * 那现在是反方向执行还是继续下一个回调函数的执行呢?
            * 我们当然设计成下一个回调函数执行咯,所以,先对index加加,然后进行执行下一个回调函数
            * 当一轮执行完之后,也就是index到达了回调函数的个数时,我们就要去对count执行次数减掉1
            * 然后对已经执行的次数加1,用来判断是否要反方向
            * 并且重现new date 进行一次新的变换
            * */
            if (p == 1){  // fun fun fun三个回调函数依次执行,p==1表示已经执行了一回
                index++;
                if (index < callBacks.length){
                    startTime = new Date();
                    requestAnimationFrame(step);
                }else{
                    count--;
                    i++;
                    if (count > 0){
                        //走到这里就表示往右往下往左往下这一个轮回走完了,已经执行好了一回
                        index = 0;  // 如果完成了一次, 下一次继续从回调函数序列中第 0 个继续执行
                        startTime = new Date();
                        //执行完一轮,下一轮就要反转数组,让从最后一个回调函数开始,也就是当前的位置
                        if (selectDirection == "reverse"){
                            durations.reverse();
                            Easings.reverse();
                            callBacks.reverse();
                        }
                        requestAnimationFrame(step)
                    }
                }
            }else{
                requestAnimationFrame(step);
            }
        })
    }
}



你可能感兴趣的:(前端从入门到放弃,封装函数动画,运算子函数,动画效果,时间归一化)