zoukankan      html  css  js  c++  java
  • javascript动画浅析。

    最近一直在弄手机端的游戏,接触到各种动画。加之对之前的自己那个动画类不满意,就有心想写个新的。

    然后翻看各种博客,查资料。也学到一些新的东西。
    动画原理

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

    用到的公式

    总距离S = 总时间T * 速度V 即: V = S/T

    当前距离s = S/T * 已耗时t 即: s = S * (t/T)

    即:当前距离 = 总距离 * (已耗时/总时间)

    即:动画元素开始值 + (动画元素结束值 - 动画元素开始值) * (当前时间-开始时间) / (动画需要时间) + 值的格式

    有了上面这些公式,我们就能利用javascript的setInterval或者setTimeout来做一个简单的动画了。

    然而想要做一个动画库,就不得不考虑另外一些因素了。 比如同一个元素的动画,必须要有顺序的执行。不同元素的动画可以同步运行。

    如此一来,就必须得用另外一个对象来管理这些动画了。我开始的想法是讲每个元素都放在一个数组里,用几个setInterval来循环取出数组中的动画函数依次执行。

    animate1 = [{elem,fn},{elem,fn}];

    animate2 = [{elem,fn},{elem,fn}];

    这样就能达到,相同的元素动画,是有顺序的执行,而不同的则可以同时运行了。然后这样却存在一个问题,那就是如果超过10个元素的动画。程序就要开十个setInterval。

    为了避免这样的情况发生,就在上面的基础上做了一些改进。使得,不论多少个动画。都使用一个setInterval来完成。修改后结构如下。

    [
    [elem,[fn,fn,fn,fn]],
    [elem,[fn,fn,fn,fn]],
    [elem,[fn,fn,fn,fn]]
    ]

    这样一来,就可以用一个setInterval来完成所有动画了。 所需要做就是,循环取出elem,并执行elem后面一个元素的头一个fn,fn执行完毕后删除fn。调用下一个fn,如果fn全部为空则从大的数组中删除elem,如果elem为空时,则清楚setInterval。这样一来,逻辑上便可以走得通了。

    然而动画最关键的因素还有一个,那就是缓动。 如果没有缓动,那么动画效果看起来就非常的死板。千篇一律。目前做js动画用到的缓动算法是很多的,大致分为两类。

    一种是flash类,一种是prototype类。

    flash的需要四个参数。分别是,

    1.时间初始话的时间t

    2.动画的初始值b

    3.动画的结束值c

    4.动画持续的时间d

    下面是一个flash 类的匀速运动算法

     Linear: function(t,b,c,d){ return c*t/d + b; } 

    另一种则是prototype,这一类的参数只需要一个,那就是当前时间t与持续时间d的比值 (t/d)

    我采用了第二种,因为它的参数方便。也更加适合上面的动画公式,下面是一个prototype类的匀速运动算法

    linear : function(t){ return t;}.

    加入缓动后上面的公式变为

    动画元素开始值 + (动画元素结束值 - 动画元素开始值) * 缓动函数((当前时间-开始时间) / (动画需要时间)) + 值的格式。

    至此便是整个动画类设计便结束了。其中参考了一些其它人的博客,在此表示感谢!

    最后,还是贴一下详细代码吧。

      1 /**
      2  * create time 2012/08/29
      3  * @author lynx cat.
      4  * @version 0.77beta.
      5  */
      6 
      7 
      8 (function(win,doc){
      9     var win = win || window;
     10     var doc = doc || win.document,
     11     pow = Math.pow,
     12     sin = Math.sin,
     13     PI = Math.PI,
     14     BACK_CONST = 1.70158;
     15 
     16     var Easing = {
     17         // 匀速运动
     18         linear : function(t){
     19             return t;
     20         },
     21         easeIn : function (t) {
     22             return t * t;
     23         },
     24         easeOut : function (t) {
     25             return ( 2 - t) * t;
     26         },
     27         easeBoth : function (t) {
     28             return (t *= 2) < 1 ?
     29                 .5 * t * t :
     30                 .5 * (1 - (--t) * (t - 2));
     31         },
     32         easeInStrong : function (t) {
     33             return t * t * t * t;
     34         },
     35         easeOutStrong : function (t) {
     36             return 1 - (--t) * t * t * t;
     37         },
     38         easeBothStrong: function (t) {
     39             return (t *= 2) < 1 ?
     40                 .5 * t * t * t * t :
     41                 .5 * (2 - (t -= 2) * t * t * t);
     42         },
     43         easeOutQuart : function(t){
     44           return -(pow((t-1), 4) -1)
     45         },
     46         easeInOutExpo : function(t){
     47           if(t===0) return 0;
     48           if(t===1) return 1;
     49           if((t/=0.5) < 1) return 0.5 * pow(2,10 * (t-1));
     50           return 0.5 * (-pow(2, -10 * --t) + 2);
     51         },
     52         easeOutExpo : function(t){
     53           return (t===1) ? 1 : -pow(2, -10 * t) + 1;
     54         },
     55         swingFrom : function(t) {
     56           return t*t*((BACK_CONST+1)*t - BACK_CONST);
     57         },
     58         swingTo: function(t) {
     59           return (t-=1)*t*((BACK_CONST+1)*t + BACK_CONST) + 1;
     60         },
     61         sinusoidal : function(t) {
     62             return (-Math.cos(t*PI)/2) + 0.5;
     63         },
     64         flicker : function(t) {
     65           var t = t + (Math.random()-0.5)/5;
     66           return this.sinusoidal(t < 0 ? 0 : t > 1 ? 1 : t);
     67         },
     68         backIn : function (t) {
     69             if (t === 1) t -= .001;
     70             return t * t * ((BACK_CONST + 1) * t - BACK_CONST);
     71         },
     72         backOut : function (t) {
     73             return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1;
     74         },
     75         bounce : function (t) {
     76             var s = 7.5625, r;
     77 
     78             if (t < (1 / 2.75)) {
     79                 r = s * t * t;
     80             }
     81             else if (t < (2 / 2.75)) {
     82                 r = s * (t -= (1.5 / 2.75)) * t + .75;
     83             }
     84             else if (t < (2.5 / 2.75)) {
     85                 r = s * (t -= (2.25 / 2.75)) * t + .9375;
     86             }
     87             else {
     88                 r = s * (t -= (2.625 / 2.75)) * t + .984375;
     89             }
     90 
     91             return r;
     92         }    
     93     };
     94 
     95     /**
     96      * 基石 用于返回一个包含对话方法的对象
     97      * @param elem
     98      * @return {Object}
     99      */
    100 
    101     function catfx(elem){
    102         elem = typeof elem === 'string' ? doc.getElementById(elem) : elem;
    103         return new fx(elem);
    104     }
    105 
    106     /**
    107      * 内部基石 用于返回一个包含对话方法的对象
    108      * @param elem
    109      * @return {Object}
    110      */
    111     function fx(elem){
    112         this.elem = elem;
    113         return this;
    114     }
    115 
    116     /**
    117      * 基础类 包含一些基础方法,和不变量
    118      */
    119     var fxBase = {
    120         speed : {
    121             slow : 600,
    122             fast : 200,
    123             defaults : 400
    124         },
    125         fxAttrs : [],
    126         fxMap:[],
    127 
    128         /**
    129          * 返回对象元素的css值
    130          * @param elem
    131          * @param p
    132          * @return css value
    133          */
    134         getStyle : function(){
    135             var fn = function (){};
    136             if('getComputedStyle' in win){
    137                 fn = function(elem, p){
    138                     var p = p.replace(/\-(\w)/g,function(i,str){
    139                         return str.toUpperCase();
    140                     });
    141                     var val = getComputedStyle(elem, null)[p];
    142                     if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){
    143                         val = '0px';
    144                     }
    145                     return val;
    146                 }
    147             }else {
    148                 fn = function(elem, p){
    149                     var p = p.replace(/\-(\w)/g,function(i,str){
    150                         return str.toUpperCase();
    151                     });
    152                     var val = elem.currentStyle[p];
    153                     
    154                     if(~(' '+p+' ').indexOf(' width height') && val === 'auto'){
    155                         var rect =  elem.getBoundingClientRect();                
    156                         val = ( p === 'width' ? rect.right - rect.left : rect.bottom - rect.top ) + 'px';
    157                     }
    158                     
    159                     if(p === 'opacity'){
    160                         var filter = elem.currentStyle.filter;
    161                         if( /opacity/.test(filter) ){
    162                             val = filter.match( /\d+/ )[0] / 100;
    163                             val = (val === 1 || val === 0) ? val.toFixed(0) : val.toFixed(1);
    164                         }else if( val === undefined ){
    165                             val = 1;
    166                         }
    167                     }
    168                     
    169                     if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){
    170                         val = '0px';
    171                     }
    172                     
    173                     return val;
    174                 }
    175             }
    176             return fn;
    177         }(),
    178 
    179         /**
    180          * 返回对象元素的css值
    181          * @param 颜色值(暂不支持red,pink,blue等英文)
    182          * @return rgb(x,x,x)
    183          */
    184         getColor : function(val){
    185             var r, g, b;
    186             if(/rgb/.test(val)){
    187                 var arr = val.match(/\d+/g);
    188                 r = arr[0];
    189                 g = arr[1];
    190                 b = arr[2];
    191             }else if(/#/.test(val)){
    192                 var len = val.length;
    193                 if( len === 7 ){
    194                     r = parseInt( val.slice(1, 3), 16);
    195                     g = parseInt( val.slice(3, 5), 16);
    196                     b = parseInt( val.slice(5), 16);
    197                 }
    198                 else if( len === 4 ){
    199                     r = parseInt(val.charAt(1) + val.charAt(1), 16);
    200                     g = parseInt(val.charAt(2) + val.charAt(2), 16);
    201                     b = parseInt(val.charAt(3) + val.charAt(3), 16);
    202                 }
    203             }else{
    204                 return val;
    205             }
    206             return {
    207                 r : parseFloat(r),
    208                 g : parseFloat(g),
    209                 b : parseFloat(b)
    210             }
    211         },
    212         /**
    213          * 返回解析后的css
    214          * @param prop
    215          * @return {val:val,unit:unit}
    216          */
    217         parseStyle : function(prop){
    218             var    val = parseFloat(prop),
    219                 unit = prop.replace(/^[\-\d\.]+/, '');
    220             if(isNaN(val)){
    221                 val = this.getColor(unit);
    222                 unit = '';
    223             }
    224             return {val : val, unit : unit};
    225         },
    226         /**
    227          * 设置元素的透明度
    228          * @param elem
    229          * @param val
    230          */
    231         setOpacity : function(elem, val){
    232             if( 'getComputedStyle' in win ){
    233                 elem.style.opacity = val === 1 ? '' : val;
    234             }else{
    235                 elem.style.zoom = 1;
    236                 elem.style.filter = val === 1 ? '' : 'alpha(opacity=' + val * 100 + ')';
    237             }
    238         },
    239         /**
    240          * 设置元素的css值
    241          * @param elem
    242          * @param prop
    243          * @param val
    244          */
    245         setStyle : function(elem, prop, val){
    246             if(prop != 'opacity'){
    247                 prop = prop.replace(/\-(\w)/g,function(i,p){
    248                     return p.toUpperCase();
    249                 });
    250                 elem.style[prop] = val;
    251             }else{
    252                 this.setOpacity(elem, val);
    253             }
    254         },
    255         /**
    256          * 返回解析后的prop
    257          * @param prop
    258          * @return {prop}
    259          */
    260         parseProp : function(prop){
    261             var props = {};
    262             for(var i in prop){
    263                 props[i] = this.parseStyle(prop[i].toString());
    264             }
    265             return props;
    266         },
    267         /**
    268          * 修正用户的参数
    269          * @param elem
    270          * @param duration
    271          * @param easing
    272          * @param callback
    273          * @return {options}
    274          */
    275         setOption : function(elem,duration, easing, callback){
    276             var options = {};
    277             var _this = this;
    278             options.duration = function(duration){
    279                 if(typeof duration == 'number'){
    280                     return duration;
    281                 }else if(typeof duration == 'string' && _this.speed[duration]){
    282                     return _this.speed[duration];
    283                 }else{
    284                     return _this.speed.defaults;
    285                 }
    286             }(duration);
    287 
    288             options.easing = function(easing){
    289                 if(typeof easing == 'function'){
    290                     return easing;
    291                 }else if(typeof easing == 'string' && Easing[easing]){
    292                     return Easing[easing];
    293                 }else{
    294                     return Easing.linear;
    295                 }
    296             }(easing);
    297 
    298             options.callback = function(callback){
    299                 var _this = this;
    300                 return function (){
    301                     if(typeof callback == 'function'){
    302                         callback.call(elem);
    303                     }
    304                 }
    305             }(callback)
    306 
    307             return options;
    308         },
    309         /**
    310          * 维护setInterval的函数,动画的启动
    311          */
    312         tick : function(){
    313             var _this = this;
    314             if(!_this.timer){
    315                 _this.timer = setInterval(function(){
    316                     for(var i = 0, len = _this.fxMap.length; i < len; i++){
    317                         var elem = _this.fxMap[i][0];
    318                         var core = _this.data(elem)[0];
    319                         core(elem);
    320                     }
    321                 },16);
    322             }
    323         },
    324         /**
    325          * 停止所有动画
    326          */
    327         stop : function(){
    328             if(this.timer){
    329                 clearInterval(this.timer);
    330                 this.timer = undefined;
    331             }
    332         },
    333         /**
    334          * 存储或者拿出队列
    335          * @param elem
    336          */
    337         data : function(elem){
    338             for(var i = 0, len = this.fxMap.length; i < len; i++){
    339                 var data = this.fxMap[i];
    340                 if(elem === data[0]){
    341                     return data[1];
    342                 }
    343             }
    344             this.fxMap.push([elem,[]]);
    345             return this.fxMap[this.fxMap.length - 1][1];
    346             
    347         },
    348         /**
    349          * 删除队列
    350          * @param elem
    351          */
    352         removeData : function(elem){
    353             for(var i = 0, len = this.fxMap.length; i < len; i++){
    354                 var data = this.fxMap[i];
    355                 if(elem === data[0]){
    356                     this.fxMap.splice(i, 1);
    357                     if(this.isDataEmpty()){
    358                         this.stop();
    359                     }
    360                 }
    361             }
    362         },
    363         isDataEmpty : function(){
    364             return this.fxMap.length == 0;
    365         }
    366     }, $ = fxBase;
    367 
    368     /**
    369      * 核心对象,用于生成动画对象。
    370      * @param elem
    371      * @param props
    372      * @param options
    373      * @return {Object}
    374      */
    375     function fxCore(elem, props, options){
    376         this.elem = elem;
    377         this.props = props;
    378         this.options = options;
    379         this.start();
    380     }
    381 
    382     fxCore.prototype = {
    383         constructor : fxCore,
    384         /**
    385          * 将动画函数加入到队列中,并启动动画。
    386          */
    387         start : function(){
    388             var cores = $.data(this.elem);
    389             cores.push(this.step());
    390             $.tick();
    391         },
    392         /**
    393          * 核心方法,控制每一帧元素的状态。
    394          * @return function
    395          */
    396         step : function(){
    397             var _this = this;
    398             var fn = function(elem){
    399                 var t = Date.now() - this.startTime;
    400                 if(Date.now() < this.startTime + this.options.duration){
    401                     if(t <= 1){ t = 1;}
    402                     for(var i in this.target){
    403                         if(typeof this.source[i]['val'] === 'number'){
    404                             var val = parseFloat((this.source[i]['val'] + (this.target[i]['val'] - this.source[i]['val']) * this.options.easing(t / this.options.duration)).toFixed(7));
    405                         }else{
    406                             var r = parseInt(this.source[i]['val']['r'] + (this.target[i]['val']['r'] - this.source[i]['val']['r']) * this.options.easing(t / this.options.duration));
    407                             var g = parseInt(this.source[i]['val']['g'] + (this.target[i]['val']['g'] - this.source[i]['val']['g']) * this.options.easing(t / this.options.duration));
    408                             var b = parseInt(this.source[i]['val']['b'] + (this.target[i]['val']['b'] - this.source[i]['val']['b']) * this.options.easing(t / this.options.duration));
    409                             var val = 'rgb(' + r + ',' + g + ',' + b + ')';
    410                         }
    411                         $.setStyle(this.elem,i,val + this.source[i]['unit']);
    412                     }
    413                 }else{
    414                     for(var i in this.target){
    415                         if(typeof this.target[i]['val'] === 'number'){
    416                             var val = this.target[i]['val'];
    417                         }else{
    418                             var val = 'rgb(' + this.target[i]['val']['r'] + ',' + this.target[i]['val']['g'] + ',' + this.target[i]['val']['b'] + ')';
    419                         }
    420                         $.setStyle(elem,i,val + this.source[i]['unit']);
    421                     }
    422                     var cores = $.data(elem);
    423                     cores.shift();
    424                     this.options.callback();
    425                     if(cores.length == 0){
    426                         $.setStyle(elem,'overflow',this.overflow);
    427                         $.removeData(elem);
    428                     }
    429                 }
    430             }
    431             return function(elem){
    432                 if(!_this.startTime){
    433                     var source = {};
    434                     _this.target = _this.props;
    435                     for(var i in _this.props){
    436                         var val = $.getStyle(_this.elem, i);
    437                         source[i] = $.parseStyle(val);
    438                     }
    439                     _this.source = source;
    440                     _this.startTime = Date.now();
    441                     _this.overflow = $.getStyle(elem,'overflow');
    442                     $.setStyle(elem,'overflow','hidden');
    443                 }
    444                 fn.call(_this,elem);
    445             }
    446         }
    447     }
    448     
    449     /**
    450      * 外部接口类。
    451      */
    452     fx.prototype = {
    453         constructor : fx,
    454         /**
    455          * 动画方法
    456          * @param prop
    457          * @param duration
    458          * @param easing
    459          * @param callback
    460          * @return {Object}
    461          */
    462         animate : function(prop, duration, easing, callback){
    463             if(arguments.length == 3 && typeof easing === 'function'){    //多数时候用户第三个参数是回调
    464                 callback = easing;
    465                 easing = undefined;
    466             }
    467             var props = $.parseProp(prop);
    468             var options = $.setOption(this.elem,duration,easing,callback);
    469             var core = new fxCore(this.elem,props,options);
    470             return this;
    471         },
    472         /**
    473          * 停止动画方法
    474          * 使用方法  catjs('your element id').stop();
    475          */
    476         stop : function(){
    477             $.removeData(this.elem);
    478         }
    479     }
    480 
    481     win.catfx = catfx;
    482 })(this,document);

    使用起来也比较简单.直接catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,'easeOut',function(){});

    跟jquery的使用方法差不多,如果不传第二个参数,则默认为400毫秒。不传第三个参数则默认匀速。第三个参数为函数,并且总共只有三个参数时。第三个参数为回调。

    例:catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,function(){alert('洒家是回调函数~')});

  • 相关阅读:
    信息安全系统设计基础第一次实验报告
    学号20145220《信息安全系统设计基础》第8周学习总结
    学号20145220《信息安全系统设计基础》第8周学习总结
    学号20145220《信息安全系统设计基础》第7周学习总结
    学号20145220《信息安全系统设计基础》第7周学习总结
    学号20145220 《信息安全系统设计基础》第6周学习总结
    # 学号20145220 《信息安全系统设计基础》第6周学习总结
    # 学号 20145220《信息安全系统设计基础》第5周学习总结
    java读取文件中每一行字符串的出现次数
    【转载】Idea常见问题整理
  • 原文地址:https://www.cnblogs.com/lynxcat/p/2663809.html
Copyright © 2011-2022 走看看