move ---- 编写一个包含几种常用动画的js库

     在操作dom元素的时候为了让网站显得更有活力或者某些想让人注意到, 经常需要用到一些小动画, 但常用的 jquery 库只有一种ease(先加速后减速的动画)运动方式的动画, 虽然这是很常用的动画, 但有时也会用到其他的, 最近写了一个集成几种常用动画的库, move.js , 如果不是走动画队列的话, 通常的动画库在进行一个动画的时候, 在对元素施加另一个动画就会马上停止当前动画, 马上执行新添加的动画, move动画库稍微修改了一下, 在新动画添加之后, 老动画还会继续跑, 两个动画会进行叠加. 你可以在demo演示中疯狂的点击start, demo演示

四种常见动画:

  • ease       ---- 先加速后减速动画, 初速度较小开始加速, 过了中点就减速, 这也是jquery默认动画
  • easeOut  ---- 初速度较大, 一直做减速运动
  • collision  ---- 碰撞动画, 初速度较小或为0, 一直加速, 碰撞终点的时候反弹, 就像篮球落地
  • elastic    ---- 弹性动画, 初速度较大, 一直做加速度减小的加速运动, 到达终点位置加速度为0(因为设置了阻力,所以加速度为0的地方在终点之前), 来回摆动

 

动画曲线:

技术分享

  有了动画曲线, 这几种动画其实只是一道简单的高一物理题, 只需要特别注意一下终点位置像素偏差如何处理.

终点位置处理

  因为使用的定时器做动画, 时间间隔是13ms, 因为jquery也是13ms, 我测试了13ms也可以得到比较平滑的效果, 但因为浏览器刷新时间间隔是16.7ms, 我的理解是需要3ms多进行dom渲染, 这点不太确定, 忘知道的朋友指点.    因为每隔13ms里目标值接近几个像素, 那么如果在接近终点的时候, 如果定时器上一次设置得值离目标1px, 那么下一次可能跳过目标, 因此会做位置判断, 如果速度>0, 并且当前位置已经>=目标值, 或者速度<0,当前位置<=目标值, 就让当前位置在目标位置.

 

代码如下:

var move = {
    css: function(obj, attr){
        if( typeof attr === "string" ){
            return obj.currentStyle ?obj.currentStyle[attr] : window.getComputedStyle(obj, false)[attr];
        }
        else if( typeof attr === "object" ){
            var a;
            for(a in attr){
                switch(a){
                    case "width":
                    case "height":
                    case "left":
                    case "top":
                    case "right":
                    case "padding":
                    case "paddingLeft":
                    case "paddingRight":
                    case "paddingTop":
                    case "paddingBottom":
                    case "margin":
                    case "marginLeft":
                    case "marginRight":
                    case "marginTop":
                    case "marginBottom":
                    case "borderRadius":
                    case "borderWidth":
                        if( typeof attr[a] === "number" )    obj.style[a] = attr[a] + ‘px‘;
                        else    obj.style[a] = attr[a];
                        break;
                    case "opacity":
                        if( +attr[a] < 0 ) attr[a] = 0;
                        obj.style.filter = "alpha(opacity="+ attr[a]*100 +")";
                        obj.style.opacity = attr[a];
                        break;
                    default:
                        obj.style[a] = attr[a];
                }
            }
        }
    },
    init: function(obj, json, time){
        if( !obj.ani ){
            obj.ani = {};                //动画对象
            obj.ani.s0 = {},             //当前值
            obj.ani.st = {},             //目标值
            obj.ani.dis = {},            //目标值和起始值距离            
            obj.ani.va = {},             //平均速度
            obj.ani.v = {},              //初始速度,当前速度
            obj.ani.a = {},              //加速度
            obj.ani.d = {},              //t时间段内的位移
            obj.ani.res = {};            //此刻的结果
        }

        obj.aniOver = false;
        obj.ani.time = time || 500;
        obj.ani.interval = 13;
        obj.ani.total = Math.ceil( obj.ani.time/obj.ani.interval );        //定时器总次数
        obj.ani.t = 0;                    //当前次数


        //如果第一次动画还没结束第二次就开始了, 就将第二次的json属性传入obj.ani.st(第一次的还在)
        //并且上一次动画的目标值不受影响
        var attr;
        for( attr in json) obj.ani.st[attr] = parseFloat(json[attr], 10);
        for( attr in obj.ani.st ){
            obj.ani.s0[attr] = parseFloat(move.css(obj, attr), 10);
        //    obj.ani.st[attr] = obj.ani.st[attr];
            obj.ani.dis[attr] = obj.ani.st[attr] - obj.ani.s0[attr];
            obj.ani.va[attr] = obj.ani.dis[attr]/obj.ani.total;
            obj.ani.d[attr] = 0;
        }
    },

    ease: function(obj, json, time, fn){
        if( obj.aniOver === false ) clearInterval(obj.ani.timer);
        this.init(obj, json, time);

        var attr, This = this;

        //因为每一种动画的初始速度, 最大速度, 加速度不同, 所以这三个单独设置
        for( attr in obj.ani.st ){
            obj.ani.v[attr] = 0.5*obj.ani.va[attr];
            //假设最大速度是3倍平均速度,初速度是0.5倍, 因此是3-0.5
            obj.ani.a[attr] = (3-0.5)*obj.ani.va[attr]/(0.5*obj.ani.total);
        }
        obj.ani.timer = setInterval(function(){
            obj.ani.t++;
            for( attr in obj.ani.st ){
                if( Math.abs(obj.ani.d[attr]) < Math.abs(obj.ani.dis[attr]/2) ){
                    obj.ani.v[attr] += obj.ani.a[attr];
                    obj.ani.d[attr] += obj.ani.v[attr];
                }
                else if( Math.abs(obj.ani.d[attr])>=Math.abs(obj.ani.dis[attr]/2) && Math.abs(obj.ani.d[attr])<=Math.abs(obj.ani.dis[attr]) ){
                    obj.ani.v[attr] -= obj.ani.a[attr];
                    obj.ani.d[attr] += obj.ani.v[attr];
                }
                obj.ani.res[attr] = obj.ani.s0[attr] + obj.ani.d[attr];
                if( (obj.ani.v[attr] > 0 && obj.ani.res[attr] > obj.ani.st[attr]) || (obj.ani.v[attr] < 0 && obj.ani.res[attr] < obj.ani.st[attr]) ) obj.ani.res[attr] = obj.ani.st[attr];
                if( obj.ani.t > obj.ani.total ){
                    clearInterval(obj.ani.timer);
                    obj.aniOver = true;
                    break;
                }
            }
            move.css(obj, obj.ani.res);
            if( obj.aniOver ){
                obj.ani.st = {};
                obj.ani.res = {};
                if( typeof fn === "function" ) fn.call(obj);
            }
        }, obj.ani.interval);
    },

    easeOut: function(obj, json, time, fn){
        if( obj.aniOver === false ) clearInterval(obj.ani.timer);
        this.init(obj, json, time);

        var attr, This = this;
        //因为每一种动画的初始速度, 最大速度, 加速度不同, 所以这三个单独设置
        for( attr in obj.ani.st ){
            obj.ani.v[attr] = 5*obj.ani.va[attr];
            obj.ani.a[attr] = -6*obj.ani.va[attr]/(0.5*obj.ani.total);
        }
        obj.ani.timer = setInterval(function(){
            obj.ani.t++;
            for( attr in obj.ani.st ){
                obj.ani.v[attr] += obj.ani.a[attr];
                obj.ani.d[attr] += obj.ani.v[attr];
                obj.ani.res[attr] = obj.ani.s0[attr] + obj.ani.d[attr];
                if( (obj.ani.v[attr] > 0 && obj.ani.res[attr] > obj.ani.st[attr]) || (obj.ani.v[attr] < 0 && obj.ani.res[attr] < obj.ani.st[attr]) ){
                    for( attr in obj.ani.res )    obj.ani.res[attr] = obj.ani.st[attr];
                    clearInterval(obj.ani.timer);
                    obj.aniOver = true;
                    break;
                }
            }
            move.css(obj, obj.ani.res);
            if( obj.aniOver && typeof fn === "function" ) fn.call(obj);
        }, obj.ani.interval);
    },

    collision: function(obj, json, time, fn){
        if( obj.aniOver === false ) clearInterval(obj.ani.timer);
        this.init(obj, json, time);
        var attr, This = this, temp;
        //因为每一种动画的初始速度, 最大速度, 加速度不同, 所以这三个单独设置
        for( attr in obj.ani.st ){
            obj.ani.v[attr] = 2*obj.ani.va[attr];
            obj.ani.a[attr] = 6*obj.ani.va[attr]/(0.5*obj.ani.total);
        }

        obj.ani.timer = setInterval(function(){
            obj.ani.t++;

            for( attr in obj.ani.st ){
                console.log(obj.ani.st)
                if( obj.ani.d[attr] === obj.ani.dis[attr] ) obj.ani.v[attr]*=-0.5;
                obj.ani.v[attr] += obj.ani.a[attr];
                obj.ani.v[attr] *= 0.999;
                temp = obj.ani.dis[attr] - obj.ani.d[attr];
                if( temp*obj.ani.v[attr] > 0 && Math.abs(temp) < Math.abs(obj.ani.v[attr]) ){
                    obj.ani.d[attr] += temp;
                }
                else{
                    obj.ani.d[attr] += obj.ani.v[attr];
                }
                obj.ani.res[attr] = obj.ani.s0[attr] + obj.ani.d[attr];

                if( obj.ani.t > obj.ani.total ){
                    for( attr in obj.ani.res )    obj.ani.res[attr] = obj.ani.st[attr];
                    clearInterval(obj.ani.timer);
                    obj.aniOver = true;
                    break;
                }
            }
            move.css(obj, obj.ani.res);
            if( obj.aniOver ){
                obj.ani.st = {};
                obj.ani.res = {};
                if( typeof fn === "function" ) fn.call(obj);
            }
        }, obj.ani.interval);
    },

    elastic: function(obj, json, fn){
        if( obj.aniOver === false ) clearInterval(obj.ani.timer);
        this.init(obj, json);

        var attr, This = this, factor={};
        //因为每一种动画的初始速度, 最大速度, 加速度不同, 所以这三个单独设置
        for( attr in obj.ani.st ){
            obj.ani.v[attr] = 0*obj.ani.va[attr];
            factor[attr] = 0.06;
        }
        obj.ani.timer = setInterval(function(){
            obj.ani.t++;
            for( attr in obj.ani.st ){
                obj.ani.a[attr] = (obj.ani.dis[attr] - obj.ani.d[attr])*factor[attr];
                obj.ani.v[attr] += obj.ani.a[attr];
                obj.ani.v[attr] *= 0.8;
            //    obj.ani.v[attr] = obj.ani.v[attr] > 0 ? Math.ceil(obj.ani.v[attr]) : Math.floor(obj.ani.v[attr]);
                obj.ani.d[attr] += obj.ani.v[attr];
                obj.ani.res[attr] = obj.ani.s0[attr] + obj.ani.d[attr];

                if( Math.abs(obj.ani.v[attr]) <= 2 && Math.abs(obj.ani.dis[attr] - obj.ani.d[attr]) <= 2  ){
                    factor[attr] = 0;
                    obj.ani.v[attr]=0;
                    obj.ani.res[attr] = obj.ani.st[attr];
                }
                if( obj.ani.t > obj.ani.total ){
                    for( attr in obj.ani.res )    obj.ani.res[attr] = obj.ani.st[attr];
                    clearInterval(obj.ani.timer);
                    obj.aniOver = true;
                    break;
                }
            }
            move.css(obj, obj.ani.res);
            if( obj.aniOver ){
                obj.ani.st = {};
                obj.ani.res = {};
                if( typeof fn === "function" ) fn.call(obj);
            }
        }, obj.ani.interval);
    }
}

      

  为了少传参数, 所以我用了命名空间来管理运动库, 如果想调用碰撞动画, 只需这样实现:

move.collision(obj, {left:500, top:300}, 1000, function(){
	alert("动画完成");
});

  其他几种动画调用方法同理, 但注意elastic不需要传入时间

为什么要在obj下声明一个ani对象?  

  由于不是走的动画队列, 所以动画正在进行时如果施加新动画, 之前动画就会停止, 比如设置left从100px跑到600px, 在右移动的过程中如果马上施加一个top从当前值跑到500px的动画, 如果所有参数(初速度, 目标值, 时间等)放到函数中作为局部变量, 每次都会设置新的, 之前的目标值则就消失了,  因此将这些属性放到dom节点对象下面的ani对象中, 方便管理. 

  这个动画库原理还是非常简单的, 有问题或不足的地方欢迎指正, 源码下载: move.js,   demo演示

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。