JavaScript封装减速运动框架

2019-02-15  本文已影响0人  AriesMiki

假设需要实现一个div 盒子的宽高减速运动变化过渡,首先要思考的问题就是,现在div盒子的宽高是多少?要变化到多少?

首先需要解决的问题,需要获取用户输入对象的当前css属性值,其次JavaScript要实现减速运动,运动变化到多少需要使用定时器来把运动拆分成不同时间段的具体变化,从而实现动画的过渡。下面来详细讲解JavaScript如何实现减速运动。

不同的浏览器获取css属性值的写法不一样,所以要书写判断。IE和Opera浏览器支持的写法是obj.currentStyle[attr],其他w3c支持的浏览器支持的写法是getComputedStyle(obj,null)[attr]。

以下是封装获取div元素的css属性值的代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            
            #box{width: 200px; height: 300px; background:tan; position: absolute; left: 0;}
                        
        </style>
    </head>
    <body>
        
        
        <button id="btn1">按钮1</button>
        <button id="btn2">按钮2</button>
        
        <div id="box"></div>
        
        <script>
            
            var btn1 = document.getElementById("btn1");
            var btn2 = document.getElementById("btn2");
            var box = document.getElementById("box");
            
            //封装获取用户输入对象对应css属性值函数
            function getStyle(obj,attr){
                //如果obj存在currentStyle
                if(obj.currentStyle){
                    //IE Opera浏览器
                    //返回对象属性值
                    return obj.currentStyle[attr];
                }else{
                    //w3c支持的浏览器
                    return getComputedStyle(obj,null)[attr];
                }
            }
            
            //这里输出盒子的高度300px
            //console.log(getStyle(box,"height"));
            
        </script>       
    </body>
</html>

现在已经可以得到用户输入属性对应的css属性值,下面来讲解如何实现动画。

首先需要理解减速运动的公式,减速运动在于每次的运动的步长越来越小,需要在循环定时器内计算每次运动的步长。

步长 = (目标值 - 当前值)/ 步数

在循环定时器内部,当前值是不断获取不断变化的,并且越来越接近目标值,而对象的css属性值也需要不断变化。

对象的css属性值 = 当前值 + 步长

目标值是用户输入的属性最终的值,而用户输入的最终值可能是多个属性,所以最终值应该是一个json数据,多个属性操作则需要对json数据进行遍历。当前值则需要在定时器内不断的获取。

实现代码和详细注释:

<script>
            
            var btn1 = document.getElementById("btn1");
            var btn2 = document.getElementById("btn2");
            var box = document.getElementById("box");
            
            //封装获取用户输入对象对应css属性值函数
            function animate(obj,json){

                setInterval(function(){
                    //遍历用户输入属性json数据
                    for(k in json){
                        //k是用户输入属性
                        //json[k]是对应属性的值
                        //console.log(json[k]);
                        //获取对象当前css属性值,获取的是字符型,需要转化成数字
                        var curStyle = parseInt(getStyle(obj,k));
                        
                        //得到步长
                        //步长 = (最终值 - 当前值)/ 步数;
                        var step = (json[k] - curStyle) / 10;
                        
                        //控制台输出能看到步长不断减小
                        //console.log(step)
                        
                        //对象的css属性值 = 当前值 + 步长;
                        obj.style[k] = curStyle + step + "px";
                        
                    }
                                
                },10)
                
            }
            
            //点击btn1能看到box宽高动画变化
            btn1.onclick = function(){
                animate(box,{width:400,height:600});    
            }

</script>

如下图所示,点击btn1已经可以实现盒子宽高的减速动画变化过渡。

减速动画变化

前面步骤虽然已经可以实现盒子动画减速过渡变化,但是审查元素发现盒子最终宽高并不等于用户输入的数值。如下图所示:

审查元素

原因是因为浏览器最小只能识别1px,但是步长step在除以步数的过程中,会出现除不尽的小数情况。所以在这里步长要作一个取整处理。如果步长是正数则向上取整,否则向下取整。

Math.ceil()向上取整,取比该数值大的整数
Math.floor()向下取整,取比该数值小的整数

步长加上以下判断赋值则可以解决这个问题。

step = step>0?Math.ceil(step):Math.floor(step);

上面已经可以实现盒子宽高减速运动变化,那么什么时候停止定时器呢?

所有用户输入的css属性都完成动画变化过渡则可以停止定时器。

在遍历外声明两个变量来记录属性变化,声明num记录属性总个数,声明key来记录已经完成变化属性的个数。遍历前两个变量的值都先设置为0。

属性总个数num遍历一次增加一个num++,已经完成变化属性个数key,变化完成一个增加一个 key++,怎么判断属性是否变化完成?如果当前值 = 目标值,该属性就变化完成。

下面代码把定时器赋给了对象obj的timer属性,如果需要调整运动速度可以通过修改定时器的间隔时间或者步数。

以下代码包含详细注释:

<script>
            
            var btn1 = document.getElementById("btn1");
            var btn2 = document.getElementById("btn2");
            var box = document.getElementById("box");
            
            //封装获取用户输入对象对应css属性值函数
            function getStyle(obj,attr){
                //如果obj存在currentStyle
                if(obj.currentStyle){
                    //IE Opera浏览器
                    //返回对象属性值
                    return obj.currentStyle[attr];
                }else{
                    //w3c支持的浏览器
                    return getComputedStyle(obj,null)[attr];
                }
            }
            
            //这里输出盒子的高度300px
            //console.log(getStyle(box,"height"));
            
            //封装缓动动画函数
            function animate(obj,json){
                
                //为了优化效率,开启定时器前先清除定时器
                clearInterval(obj.timer);
                
                obj.timer = setInterval(function(){
                    
                    //在遍历外记录属性个数,初始都定义为0
                    var num = 0;    //记录总个数
                    var key = 0;//记录已达到属性的个数
                    
                    //遍历用户输入属性json数据
                    for(k in json){
                        //k是用户输入属性
                        //json[k]是对应属性的值
                        //console.log(json[k]);
                        //获取对象当前css属性值,获取的是字符型,需要转化成数字
                        var curStyle = parseInt(getStyle(obj,k));
                        
                        //得到步长
                        //步长 = (最终值 - 当前值)/ 步数;
                        var step = (json[k] - curStyle) / 10;
                        
                        //控制台能看到步长不断减小
                        //console.log(step)
                        
                        //Math.ceil()向上取整,取比该数值大的整数
                        //Math.floor()向下取整,取比该数值小的整数
                        //步长判断取整后重新赋值
                        step = step>0?Math.ceil(step):Math.floor(step);
                        
                        //对象的css属性值 = 当前值 + 步长;
                        obj.style[k] = curStyle + step + "px";
                        
                        //属性总个数遍历一次增加一个
                        num++;
                        
                        //如果当前值等于用户输入的值,则已经完成一个属性的变化
                        if(curStyle == json[k]){
                            //key记录的是已经完成变化的属性数,到达一个增加一个key++
                            key ++;
                        }
                                        
                        
                    }
                                    
                    //在遍历外面判断到达属性的个数是否等于遍历属性的总个数                
                    if(key == num){
                        //如果两个相等,表示所有属性变化完成,则停止定时器
                        clearInterval(obj.timer)
                    }
                    
                    //console.log(num)
                    //console.log(key)          
                    
                },30)
                
            }
            
            //点击btn1能看到box属性动画变化
            btn1.onclick = function(){
                animate(box,{width:1200,height:400,marginTop:100}); 
            }
            
            //点击btn2能看到box属性动画变化
            btn2.onclick = function(){
                animate(box,{width:200,height:200,marginTop:0});    
            }

                        
        </script>   

点击按钮效果,如下图所示:

点击按钮效果

到这一步,已经完成了减速运动变化框架封装。

最后一步来进行优化,为减速运动框架添加回调函数。

所谓回调函数是指在动画运动完成后执行的函数。需要给animate函数添加一个参数fn接收回调函数,如果用户有传回调函数,运动完成后就执行这个函数,所以是在停止定时器后再进行判断和调用fn。

下面为减速运动框架封装最终代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            
            #box{width: 200px; height: 300px; background:tan; position: absolute; left: 0;}
                        
        </style>
    </head>
    <body>
        
        
        <button id="btn1">按钮1</button>
        <button id="btn2">按钮2</button>
        
        <div id="box"></div>
        
        <script>
            
            var btn1 = document.getElementById("btn1");
            var btn2 = document.getElementById("btn2");
            var box = document.getElementById("box");
            
            //封装获取用户输入对象对应css属性值函数
            function getStyle(obj,attr){
                //如果obj存在currentStyle
                if(obj.currentStyle){
                    //IE Opera浏览器
                    //返回对象属性值
                    return obj.currentStyle[attr];
                }else{
                    //w3c支持的浏览器
                    return getComputedStyle(obj,null)[attr];
                }
            }
            
            //这里输出盒子的高度300px
            //console.log(getStyle(box,"height"));
            
            //封装缓动动画函数
            function animate(obj,json,fn){
                
                //为了优化效率,开启定时器前先清除定时器
                clearInterval(obj.timer);
                
                obj.timer = setInterval(function(){
                    
                    //在遍历外记录属性个数,初始都定义为0
                    var num = 0;    //记录总个数
                    var key = 0;//记录已达到属性的个数
                    
                    //遍历用户输入属性json数据
                    for(k in json){
                        //k是用户输入属性
                        //json[k]是对应属性的值
                        //console.log(json[k]);
                        //获取对象当前css属性值,获取的是字符型,需要转化成数字
                        var curStyle = parseInt(getStyle(obj,k));
                        
                        //得到步长
                        //步长 = (最终值 - 当前值)/ 步数;
                        var step = (json[k] - curStyle) / 10;
                        
                        //控制台能看到步长不断减小
                        //console.log(step)
                        
                        //Math.ceil()向上取整,取比该数值大的整数
                        //Math.floor()向下取整,取比该数值小的整数
                        //步长判断取整后重新赋值
                        step = step>0?Math.ceil(step):Math.floor(step);
                        
                        //对象的css属性值 = 当前值 + 步长;
                        obj.style[k] = curStyle + step + "px";
                        
                        //属性总个数遍历一次增加一个
                        num++;
                        
                        //如果当前值等于用户输入的值,则已经完成一个属性的变化
                        if(curStyle == json[k]){
                            //key记录的是已经完成变化的属性数,到达一个增加一个key++
                            key ++;
                        }                                   
                        
                    }
                                    
                    //在遍历外面判断到达属性的个数是否等于遍历属性的总个数                
                    if(key == num){
                        //如果两个相等,表示所有属性变化完成,则停止定时器
                        clearInterval(obj.timer);
                        //如果存在就调用
                        //if(fn){
                            //fn();
                        //}
                        //化简写法
                        fn&&fn();
                    }
                    
                    //console.log(num)
                    //console.log(key)          
                    
                },20)
                
            }
            
            //点击btn1能看到box属性动画变化
            btn1.onclick = function(){
                animate(box,{width:1200,height:400,marginTop:100},function(){
                    alert("按钮1运动完成!")
                });
            }
            
            //点击btn2能看到box属性动画变化
            btn2.onclick = function(){
                animate(box,{width:200,height:200,marginTop:0});    
            }
                        
        </script>
        
    </body>
</html>

注意这个减速运动框架只适用于属性值为正整数的属性,属性值为小数例如opacity和属性值非数字的属性暂不支持。

上一篇下一篇

猜你喜欢

热点阅读