JavaScript封装完美运动框架的过程

  |   0 评论   |   143 浏览

前言:
当页面上一个元素需要改变它的样式(width,height,left,top,opacity等等),我们可以直接设置元素.style.样式。
如果需要让它的样式从开始值到改变值有一个动态的过程(或者叫动画),那么,就需要给它写一个定时器。
让定时器每隔一段时间执行一段代码,让元素的样式慢慢改变。这样就可以看到动态的过程了。这个就是运动。
但是,如果有多个元素都要有运动效果,给每一个元素都写一个定时器?显然不太现实,代码就明显冗余了。这个时候就需要封装一个运动框架了,将这种一样的运动过程给封装起来,要用的时候只需要写元素名和需要运动的属性就好了。比如这样:animate(div,'width',300);只要事先把animate函数方法封装起来,以后要用的时候,写这么简单的一行代码就可以实现要运动的效果了。
在网上看了一些博客,再加上自己的一些理解,然后稍稍改动了一下其中的一些博主没注意的地方(或者叫小bug),然后就有了自己的封装的animate方法。当然,运动有匀速加速减速,这里只是封装了减速缓冲运动。

正题开始:

首先,我们需要先封装一个css方法,用以获取元素的样式。

//elem    元素    attr    元素的属性名   
function css(elem,attr){   
 return elem.currentStyle ? elem.currentStyle[attr]  :   window.getComputedStyle(elem)[attr];   
}

然后,我们想要一个元素的width动起来,怎么做?给它一个定时器,每隔一段时间改变它的width值,到了想要的值时就清除定时器。如果还想改变别的样式值呢?那么就给这个定时器封装成函数方法,把里面要改变的变成变量,需要用的时候,调用它,传入想改变的样式名就行了。

此时就有了这样的方法:

//elem 元素  attr  属性名  value 属性的改变值  
function animate(elem,attr,value){  
      var timer = setInterval(function(){  
              //设置变量start存储元素要改变的样式的初值  
              var start = parseInt( css(elem,attr) ),  
                       
              //设置变量speed样式改变的速度  
              var speed = (value-start)/10;  
              //当speed负值或者正值时需要给它取整,以免出现start永远不能等于value的情况  
              speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);  
              start += speed;  
              if(start == value){  
                      clearInterval(timer);  
              }else{  
                       elem.style[attr] = start + 'px';  
                 }  
          },13)  
}  

但是,这样的方法不能实现opacity的改变
那么就有了这样的方法:

function animate(elem,attr,value){  
        var timer = setInterval(function(){  
                //设置变量end存储元素要改变的样式的最终值  
                var start = 0, end = 0;  
                if(attr ==  'opacity'){  
                     //Math.round防止无限小数兼容ie  
                    start = Math.round( parseFloat( css(elem,attr) )*100 );  
                    end = value*100;   
                }else{  
                        start = parseInt( css(elem,attr) );  
                        end = value;   
                    }  
                var speed = (end-start)/10;  
                speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);  
                start += speed;  
                if(start == end){  
                        clearInterval(timer);  
                }else{  
                        if(attr == 'opacity'){  
                                elem.style.opacity = start/100;//兼容IE elem.style.filter='alpha(opacity='+start+')';                          

                         }else{  
                                elem.style[attr] = start + 'px';  
                             }  
                 }  
            },13)  
}  

这样的方法,只是在元素本身被设置过属性情况下,那如果元素没有设置这个你想要改变的属性呢?
我们先来看下,当元素没有设置属性值的时候,CSS方法获取的是什么。
比如,获取一个left,css(div,'left'),有的浏览器会返回0px,但是有的浏览器会返回一个undefined或者auto或者其他他非数字的值;同样,关于opacity这个属性,其他浏览器会返回1,但是IE浏览器会返回一个undefined。

一般HTML+CSS布局的时候,大家通常会这样写透明度:filter:alpha(opacity=100);opacity:1;
因为IE浏览器的透明度是filter:alpha(opacity=100);这样的写法就是为了兼容IE的。

知道了原理,那么,这个animate就可以这样写了:

function animate(elem,attr,value){  
        var timer = setInterval(function(){  
               var start = 0, end = 0;  
                if(attr ==  'opacity'){  
                    //兼容IE当没有设置fliter时,默认100;  
                    if(!css(elem,attr)){  
                           start = 100;  
                      }else{  
                              start = Math.round( parseFloat( css(elem,attr) )*100 );  
                        }

                   end = value*100;   
                }else{  
                        //兼容ie 没有设置attr时,默认0(不加parseInt,IE会报错)  
                        if(!parseInt(css(elem,attr))){  
                            start = 0; 

                        }else{  
                               start = parseInt( css(elem,attr) );  
                           }  
                        end = value;   
                    }  
                var speed = (end-start)/10;  
                speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);  
                start += speed;  
                if(start == end){  
                        clearInterval(timer);  
                }else{  
                        if(attr == 'opacity'){  
                                elem.style.opacity = start/100;  
                                 //兼容IE   
                                elem.style.filter='alpha(opacity='+start+')';                          

                         }else{  
                                elem.style[attr] = start + 'px';  
                             }  
                 }  
            },13)  
}

好了,元素单个的属性运动方法已经完毕。
那想要多个属性同时运动呢?
我们可以把attr和对应的value用对象存起来,比如这样 {width:100,height:100,opacity:1}。然后遍历这个对象,依次改变对象里的属性对应的值,也就是改变了元素的属性值。对象遍历我们用for in 方法。
比如,

        var json = {width:100,height:100,opacity:1};  
           for(var i in json){  
                console.log(i)  
            }   

你会发现得到的是width,height,opacity这些字符串。那么json[i]就代表了json对象里的属性值。
还有,前面的方法,在实际运用中会出现BUG。因为可能会出现,上一次运动还没完,用户又开始运动,即开了多个定时器的情况。这样就会出现问题。
通常做法是在开始运动前就清除定时器,这样就不会出现问题。
我们的前面的方法里直接在定时器前面写clearInterval(timer)是不行的,会出错 timer is not defined 。
那么我们可以这样,把元素看成一个对象,把timer设置成元素的一个属性 ,即elem.timer。
所以,

function animate(elem,json){  
        clearInterval(elem.timer);  
        elem.timer = setInterval(function(){  
                //遍历json,依次改变每个属性的属性值  
               for(var attr in json){  
                       var start = 0, end = 0;

                        if(attr ==  'opacity'){  
                          //兼容IE 当没有设置fliter时,默认100;

                            if( !css(elem,attr) ){  
                                   start = 100;  
                              }else{  
                                   //Math.round防止出现无限小数  
                                  start = Math.round( parseFloat( css(elem,attr) )*100 );  
                              }

                              end = parseFloat(json[attr])*100; 

                        }else{  
                                //兼容ie 没有设置attr时,默认0(不加parseInt,IE会报错)  
                              if(!parseInt(css(elem,attr))){  
                                start = 0; 

                            }else{  
                                   start = parseInt( css(elem,attr) );}  end =  parseInt(json[attr]);   
                        }  
                    var speed = (end-start)/10;  
                    speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);  
                    start += speed;  
                    if(start == end){  
                            clearInterval(elem.timer);  
                     }else{  
                                if(attr == 'opacity'){  
                                elem.style.opacity = start/100;  
                                 //兼容IE   
                                elem.style.filter='alpha(opacity='+start+')';                          

                        }else{  
                                elem.style[attr] = start + 'px';  
                             }           

                }   

        },13)  

}     

这样貌似写完了,其实还没完,这样写会有BUG,会出现只实现了第一个属性的运动,而其他属性的运动没有到达你想要的最终值。因为方法里是这样判定的,当start == end的时候,判定运动终止,但是,现在不止有一个start和end,第一个属性的start==end完成了,但是后面的属性的start==end并没有完成。
所以,我们可以用布尔值来做判定条件。

 function animate(elem,json){

        clearInterval(elem.timer);  
        elem.timer = setInterval(function(){  
                //运动终止条件  
                var flag = true;  
                //遍历json,依次改变每个属性的属性值  
               for(var attr in json){  
                       var start = 0, end = 0;

                        if(attr ==  'opacity'){  
                        //兼容IE 当没有设置fliter时,默认100;

                            if( !css(elem,attr) ){  
                                   start = 100;  
                              }else{  
                                   //Math.round防止出现无限小数  
                                  start = Math.round( parseFloat( css(elem,attr) )*100 );  
                              }

                              end = parseFloat(json[attr])*100; 

                        }else{  
                                //兼容ie 没有设置attr时,默认0(不加parseInt,IE会报错)  
                              if(!parseInt(css(elem,attr))){  
                                start = 0; 

                            }else{  
                                   start = parseInt( css(elem,attr) );  
                              }  
                               end =  parseInt(json[attr]);   
                        }  
                    var speed = (end-start)/10;  
                    speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);  
                    start += speed;  
                    //当start != end时,flag = false;,运动继续

                    if(start != end){  
                            flag = false;  
                     }  
                      if(attr == 'opacity'){  
                           elem.style.opacity = start/100;  
                           //兼容IE   
                            elem.style.filter='alpha(opacity='+start+')';                          

                        }else{  
                                elem.style[attr] = start + 'px';  
                             }           

                }   
                 //当所有的start == end时,flag = true;,运动停止  
                if(flag){  
                         clearInterval(elem.timer);  
                 }  
                   

        },13)  

}      

如果想把animate做成链式运动方法,很简单,只要多加个参数fn,在最后判定运动完成时回调函数就行。改变运动的快慢也很简单,只要再加个参数time

所以最终版:

function animate(elem,json,time,fn){

        clearInterval(elem.timer);  
        elem.timer = setInterval(function(){  
                //运动终止条件  
                var flag = true;  
                //遍历json,依次改变每个属性的属性值  
               for(var attr in json){  
                       var start = 0, end = 0;

                        if(attr ==  'opacity'){  
                        //兼容IE 当没有设置fliter时,默认100;  
                            if( !css(elem,attr) ){  
                                   start = 100;  
                              }else{  
                                   //Math.round防止出现无限小数  
                                  start = Math.round( parseFloat( css(elem,attr) )*100 );  
                              }

                              end = parseFloat(json[attr])*100; 

                        }else{  
                           //兼容ie 没有设置attr时,默认0(不加parseInt,IE会报错)  
                              if( !parseInt(css(elem,attr)) ){  
                                start = 0; 

                            }else{  
                                   //parseInt防止json对象里属性值带px  
                                   start = parseInt( css(elem,attr) );  
                              }  
                               end =  parseInt(json[attr]);   
                        }  
                        //如果有time实参传入,就执行speed = (end-start)/ time ,否则speed = (end-start)/10;  
                        var speed = (end-start)/( time || 10 );  
                        speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);  
                        start += speed;  
                        //当start != end时,flag = false;,运动继续

                        if(start != end){  
                            flag = false;  
                         }  
                          if(attr == 'opacity'){  
                           elem.style.opacity = start/100;  
                           //兼容IE   
                            elem.style.filter='alpha(opacity='+start+')';                          

                          }else{  
                                elem.style[attr] = start + 'px';  
                            }           

                }   
                 //当所有的start == end时,flag = true;,运动停止  
                if(flag){  
                         clearInterval(elem.timer);  
                      //运动终止时,如果有函数实参传入就执行回调函数  
                        fn & fn();   
                 }  
                   

        },13)  

}
--------------------------------
云深不知归处
先沉稳而后爱人

评论

发表评论