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演示

时间: 03-27

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

Numeral.js 是一个用于格式化和数字四则运算的js 库

1.Numeral.js 是一个用于格式化和数字四则运算的js 库. 2.支持多种语言,包含中文在内的17种语言. 在浏览器中引用js文件: <script src="numeral.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/numeral.js/1.4.5/numeral.min.js"></script> 在nodejs开发引用开

如何比较好的编写一个包含业务逻辑的方法体

具体做法:先写出方法体的主体流程,细节部分先只抽象出方法暂不实现,等主体框架完成后再实现具体的细节部分. 缘由:之前的做法是建一个方法,然后把一坨业务逻辑的代码塞到方法里.这样做的问题是如果业务很复杂可能导致思路混乱,而且后期代码也不容易让别人理解.

一个简单的加载动画,js实现

简单效果图: html: <div class="box"> <ul> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> </div> css: .box{ width: 200px; height: 80px; margin: 200px auto; bor

为Node.js编写组件的几种方式

本文主要备忘为Node.js编写组件的三种实现:纯js实现.v8 API实现(同步&异步).借助swig框架实现. 关键字:Node.js.C++.v8.swig.异步.回调. 简介 首先介绍使用v8 API跟使用swig框架的不同: (1)v8 API方式为官方提供的原生方法,功能强大而完善,缺点是需要熟悉v8 API,编写起来比较麻烦,是js强相关的,不容易支持其它脚本语言. (2)swig为第三方支持,一个强大的组件开发工具,支持为python.lua.js等多种常见脚本语言生成C++组件

【C++】编写一个简单的类。包含构造函数,成员函数等。

<pre name="code" class="cpp">//编写一个简单的类.包含构造函数,成员函数等. #include <iostream> using namespace std; class Rec { public: Rec(int l,int w); int Area(); void Print(); private: int length,wide; }; Rec::Rec(int l,int w) { length=l; w

编写一个函数char_contains(char str[],char c), 如果字符串str中包含字符c则返回数值1,否则返回数值0

/* 编写一个函数char_contains(char str[],char c), 如果字符串str中包含字符c则返回数值1,否则返回数值0 */ #include <string.h> #include <stdio.h> // 可读性 -> 性能 -> 精简(重构) int char_contains(char str[], char c); int main() { //int result = char_contains("itc8ast"

Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。

#29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类Truck是Car类的子类,其中包含的属性有载重量payload.每个 类都有构造方法和输出相关数据的方法.最后,写一个测试类来测试这些类的功 能. package hanqi; public class Vehicle { private int wheels; private int weight

几种常用的bootstrap功能。

---恢复内容开始--- 我对于bootstrap定义与一种插件,他可以使我们的网页布局更加的炫酷,更加的整洁和合理.他的优点不多说,缺点一个就够我们头疼的,那就是需要记一些长长的英文名. 我为大家说几种常用的bootstrap的常用功能,希望你们用到的时候,可以来看下. <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> <div class="c

PHP V5 的五种常用设计模式

设计模式 一书将设计模式引入软件社区,该书的作者是 Erich Gamma.Richard Helm.Ralph Johnson 和 John Vlissides Design(俗称 "四人帮").所介绍的设计模式背后的核心概念非常简单.经过多年的软件开发实践,Gamma 等人发现了某些具有固定设计的模式,就像建筑师设计房子和建筑物一样,可以为浴室的位置或厨房的构造方式开发模板.使用这些模板或者说设计模式 意味着可以更快地设计更好的建筑物.同样的概念也适用于软件. 设计模式不仅代表着更