JavaScript完美运动框架进阶之旅

作者: 逍遥至尊灬寳 | 来源:发表于2016-05-31 22:02 被阅读1385次

    导语:

    运动框架的实现思路运动,其实就是在一段时间内改变left、right、width、height、opacity的值,到达目的地之后停止。现在按照以下步骤来进行我们的运动框架的封装:匀速运动、缓冲运动、多物体运动、任意值变化。

    运动框架的实现思路

    </br>
    运动,其实就是在一段时间内改变left、right、width、height、opacity的值,到达目的地之后停止。
    

    现在按照以下步骤来进行我们的运动框架的封装:匀速运动、缓冲运动、多物体运动、任意值变化。

    (一) 匀速运动
    速度动画
    运动基础

    思考:如何让 div 动起来?


    如下:
    设置元素绝对定位,只有绝对定位后,left,top等值才会生效。
    定时器的使用(动态改变值),这里使用setInterval()每隔指定时间执行代码。
    计时器setInterval(函数,交互时间(毫秒)):在执行时,从载入页面后每隔指定的时间执行代码。
    取消计时器clearInterval(函数)方法可取消由setInterval()设置的交互时间。
    获取当前的位置,大小等等。offsetLeft(当前元素相对父元素的位置)。
    速度--物体运动的快慢
    定时器间隔时间
    改变值的大小
    。。。
    根据上面的信息我们就可以开始封装运动框架创建一个变化的div了。

    1.  /** 
    2.  * 运动框架-1-动起来
    3. *@param {HTMLElement} element 进行运动的节点
    4.  * /
    5.  var timer = null;
    6.  function startMove(element){
    7.     timer = setInterval( function () { // 定时器      
    8.            element.style.left = element.offsetLeft + 5 + "px;
    9.      },30);
    10. }
    

    你没看错,就是那么简单。但是等等,what ? 怎么不会停?! WTF !
    那是因为我们没有运动终止条件。好在还是比较简单,之间在定时器内部,判断到达目标值,清除定时器就行啦!

    1.  /** 
    2.  * 运动框架-2-运动终止
    3.  *@param {HTMLElement} element 进行运动的节点
    4.  *@param {number}    iTarget 运动终止条件
    5.  */
    6.  var timer = null;
    7. function startMove( element, iTarget) {
    8.      timer = setInterval ( function ( ) {
    9.          element.style.left = element.offsetLeft + 5 + "px";
    10.        if (element.offsetLeft ===iTarget) { // 停止条件
    11.              clearInterval ( timer );
    12.         }; 
    13.     }, 30);
    14.  }
    

    就这样是不是就完成了呢? no ,还有一些bug需处理。

    运动中的bug


    速度取到某些值会无法停止
    到达位置之后再点击还会动
    重复点击速度加快
    速度无法更改

    解决bug


    速度取到某些值会无法停止(这个Bug稍后解决,在进化过程中自然解决)
    把运动和停止隔开(if/else)
    在开始运动时,关闭已有定时器
    把速度用变量保存

    1.  /**
    2.  * 运动框架‐3‐解决Bug
    3.  */
    4. vartimer=null;
    5.  function startMove ( element , iTarget ) {
    6.      clearInterval ( timer ) ; // 开始运动前关闭已有定时器
    7.      timer = setInterval ( function ( ) {
    8.            var iSpeed = 5 ;//把速度用变量保存
    9.            // 把运动和停止隔开( if / else )
    10.          if (element.offsetLeft == iTarget ) { // 结束运动
    11.                clearInterval ( timer );
    12.           } else {
    13.                element.style.left = element.offsetLeft + iSpeed + "px";
    14.            };
    15.     } , 30);
    16. }
    

    这样一个简单的运动框架就完成了。但是,再等等。只能向右走?别急,我们不是定义了把速度变成为了变量吗?只需要对它进行一些处理就行啦!
    var iSpeed = 5;

    1.  // 判断距离目标位置,达到自动变化速度正负
    2.  var iSpeed = 0 ;
    3.  if (element.offsetLeft < iTarget) {
    4.      iSpeed = 5 ;
    5.  } else {
    6.      iSpeed = -5;
    7.  };
    

    透明度动画


    用变量alpha储存当前透明度。
    把上面的element.offsetLeft改成变量alpha。
    运动和停止条件部分进行更改。如下:

    1.  // 透明度浏览器兼容实现
    2.  if ( alpha === iTarget ) {
    3.       clearInterval ( timer ) ;
    4.  } else {
    5.        alpha += speed;
    6.        element.style.filter = 'alpha(opacity:'  + alpha + ')' ; // 兼容IE
    7.        element.style.opacity = alpha / 100; // 标准
    8.   }
    

    (二)缓冲动画


    思考:怎么样才是缓冲动画?
    应该有以下几点:
    逐渐变慢,最后停止
    距离越远速度越大
    速度由距离决定
    速度=(目标值-当前值)/缩放系数
    Bug :速度取整(使用Math方法),不然会闪
    向上取整。Math.ceil(iSpeed)
    向下取整。Math.floor(iSpeed)
    还是对速度作文章:

    1.   /**
    2.   * 运动框架-4-缓冲动画
    3.   */
    4.   functionstartMove ( element,iTarget ) {
    5.        clearInterval ( timer );
    6.        timer = setInterval ( function ( ) {
    7.        // 因为速度要动态改变,所以必须放在定时器中
    8.               var iSpeed = ( iTarget - element.offsetLeft ) / 10; // (目标值 - 当前值) / 缩放系数 = 速度
    9.               ISpeed = ISpeed  > 0 ? Math.ceil ( iSpeed ) : Math.floor ( iSpeed ) ; // 速度取整
    10.             if (element.offsetLeft === iTarget ) // 结束运动
    11.                    clearInterval ( timer ) ;
    12.              } else {
    13.                      element.style.left = element.offsetLeft + iSpeed + "px";
    14.              }
    15.        }, 30);
    16.  }
    

    做到这里,(速度取到某些值会无法停止)这个Bug就自动解决啦!
    例子:缓冲菜单
    跟随页面滚动的缓冲侧边栏
    潜在问题目标值不是整数时

    (三)多物体运动


    思考:如何实现多物体运动?
    单定时器,存在问题。每个div一个定时器
    定时器作为对象的属性
    直接使用element.timer把定时器变成对象上的一个属性。
    参数的传递:物体/目标值
    比较简单把上面框架的进行如下更改:timer-->element.timer
    就这样就行啦!

    (四)任意值变化


    咳咳。我们来给div加个1px的边框。boder :1px solid #000
    然后来试试下面的代码

    1.  setInterval ( function() {
    2.      oDiv.style.width=oDiv.offsetWidth‐1+"px";
    3.  },30)
    

    嗯,神奇的事情发生了!what?我设置的不是宽度在减吗?怎么尼玛增加了! 不对啊,大兄弟。
    究竟哪里出了问题呢?
    一起找找资料,看看文档,原来offset这一系列的属性都会存在,被其他属性干扰的问题。
    好吧,既然不能用,那么我们就顺便把任意值变化给做了吧。

    第一步:获取实际样式

    使用offsetLeft..等获取样式时, 若设置了边框, padding, 等可以改变元素宽度高度的属性时会出现BUG..
    通过查找发现element.currentStyle(attr)可以获取计算过之后的属性。
    但是因为兼容性的问题,需封装getStyle函数。(万恶的IE)
    当然配合CSS的box-sizing属性设为border-box可以达到一样的效果 ? (自认为,未验证)。

    1.   /**
    2.    * 获取实际样式函数
    3.    * @param {HTMLElement} element 需要寻找的样式的html节点
    4.    * @param {String]}attr在对象中寻找的样式属性
    5.    * @returns {String} 获 取 到 的 属 性
    6.    */
    7.    functiongetStyle(element,attr){
    8.        //IE写法
    9.        if ( element.currentStyle ) { 
    10.            return element.currentStyle[attr];
    11.      // 标准
    12.      } else {
    13.            return getComputedStyle ( element , false ) [attr]; 
    14.       }
    15.   }
    
    第二步:改造原函数

    添加参数,attr表示需要改变的属性值
    更改element.offsetLeft为getStyle(element, attr)。
    需要注意的是:getStyle(element, attr)不能直接使用,因为它获取到的字符串,例:10px。
    变量iCurrent使用parseInt(),将样式转成数字。
    element.style.left为element.style[attr]。

    1.   /**
    2.    * 运动框架‐4‐任意值变化
    3.    * @param {HTMLElement} element 运 动 对 象
    4.    *@param{string} attr 需要改变的属性。
    5.    * @param {number} iTarget 目 标 值
    6.    */
    7.    functionstartMove ( element , attr , iTarget ) { 
    8.         clearInterval(element.timer);
    9.         element.timer = setInterval ( function ( ) {
    10.              //因为速度要动态改变,所以必须放在定时器中
    11.       variCurrent=0;
    12.       iCurrent=parseInt(getStyle(element,attr));//实际样式大小
    13.             variSpeed=(iTarget‐iCurrent)/10;//(目标值‐当前值)/缩放系数=速度
    14.             iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);//速度取整
    15.             if(iCurrent===iTarget){//结束运动
    16.                    clearInterval(element.timer);
    17.              } else {
    18.                      element.style[attr]=iCurrent+iSpeed+"px";
    19.              };
    20.         },30);
    21.    };
    

    试一试,这样是不是就可以了呢?
    还记得上面我们写的透明度变化吗? 再试试
    果然还是不行, (废话,你见过透明度有"px"单位的么? - -白眼 )

    第三步:透明度兼容处理

    思考:需要对那些属性进行修改?
    判断attr是不是透明度属性opacity 。
    对于速度进行处理。
    为透明度时,由于获取到的透明度会是小数,所以需要 * 100
    并且由于计算机储存浮点数的问题,还需要将小数,进行四舍五入为整数。使用:
    Math.round(parseFloat(getStyle(element, attr)) * 100)。
    否则,继续使用默认的速度。
    对结果输出部分进行更改。
    判断是透明度属性,使用透明度方法
    否则,使用使用默认的输出格式。

    1.   /**
    2.    * 运动框架‐5‐兼容透明度
    3.    * @param {HTMLElement} element 运 动 对 象
    4.    *@param{string} attr 需要改变的属性。
    5.    * @param {number} iTarget 目 标 值
    6.    */
    7.    function  startMove ( element , attr , iTarget ) {
    8.          clearInterval ( element.timer );
    9.          element.timer = setInterval ( function ( ) {
    10.              //因为速度要动态改变,所以必须放在定时器中
    11.              var iCurrent=0;
    12.              if ( attr === "opacity" ){//为透明度时执行。
    13.                    iCurrent = Math.round ( parseFloat ( getStyle ( element , attr ) ) * 100 );
    14.              } else { //默认情况
    15.                    iCurrent=parseInt(getStyle(element,attr));//实际样式大小 }
    16.              variSpeed=(iTarget‐iCurrent)/10;//(目标值‐当前值)/缩放系数=速度
    17.              iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);//速度取整
    18.              if(iCurrent===iTarget){//结束运动
    19.                    clearInterval(element.timer);
    20.               } else {
    21.                      if ( attr==="opacity"){//为透明度时,执行
    22.                          element.style.filter="alpha(opacity:"+(iCurrent+iSpeed)+")";//IE
    23.                          element.style.opacity=(iCurrent+iSpeed)/100;//标准
    24.                      }else{//默认
    25.                          element.style [ attr ] =iCurrent+iSpeed+"px";
    26.                      }
    27.                  }
    28.              },30);
    29.     }
    

    到这里,这个运动框架就基本上完成了。但是,我们是追求完美的不是吗?
    继续进化!

    (五)链式动画

    链式动画:顾名思义,就是在该次运动停止时,开始下一次运动。
    如何实现呢?
    使用回调函数:运动停止时,执行函数
    添加func形参(回调函数)。
    在当前属性到达目的地时iCurrent === iTarget,判断是否有回调函数存在,有则执行。
    if (iCurrent === iTarget) {//结束运动
    clearInterval(element.timer);
    if (func) {
    func();//回调函数
    }
    }
    good,链式动画完成!距离完美还差一步!

    (六)同时运动

    思考:如何实现同时运动?
    使用JSON传递多个值
    使用for in循环,遍历属性,与值。
    定时器问题!(运动提前停止)
    在循环外设置变量,假设所有的值都到达了目的值为true
    在循环中检测是否到达目标值,若没有值未到则为false
    在循环结束后,检测是否全部达到目标值.是则清除定时器
    实现:
    删除attr与iTarget两个形参,改为json
    在函数开始时,设置一个标记var flag = true; //假设所有运动到达终点.
    在定时器内使用for in,遍历属性与目标,改写原来的attr与iTarget,为json的属性与值
    修改运动终止条件,只有每一项的实际属性值iCurrent,等于目标值json[attr]时,flag才为true。清除定时器,判断是否回调。
    否则,继续执行代码,直到所有属性值等于目标值。
    完美运动框架

    1.  /**
    2.  * 获取实际样式函数
    3.  *@param {HTMLElement} element 需要寻找的样式的html节点
    4.  *@param {String]}attr在对象中寻找的样式属性
    5.  * @returns {String} 获 取 到 的 属 性
    6.  */
    7.  function getStyle(element,attr){
    8.    //IE写法
    9.    if ( element.currentStyle ) {
    10.      return element.currentStyle[attr];
    11.      //标准
    12.      } else {
    13.          return getComputedStyle(element,false)[attr];
    14.      }
    15.  }
    16.  /**
    17.  * 完美运动框架
    18.  *@param{HTMLElement}element 运动对象
    19.  *@param{JSON}  json 属性:目标值
    20.  * @property{String}attr 属性值
    21.  * @config {Number}target 目标值
    22.  *@param{function} func 可选,回调函数,链式动画。
    23.  */
    24.  function startMove ( element , json , func ) {
    25.    var flag=true;//假设所有运动到达终点.
    26.    clearInterval(element.timer);
    27.    element.timer=setInterval(function(){
    28.       for(var attr in json){
    29.       //1.取当前的属性值。
    30.       var iCurrent=0;
    31.       if(attr==="opacity"){//为透明度时执行。
    32.          iCurrent=Math.round(parseFloat(getStyle(element,attr))*100);
    33.        }else{//默认情况
    34.           iCurrent=parseInt(getStyle(element,attr));//实际样式大小
    35.        }
    36.        //2.算运动速度,动画缓冲效果
    37.        var iSpeed=(json[attr]‐iCurrent)/10;//(目标值‐当前值)/缩放系数=速度
    38.        iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);//速度取整
    39.        //3.未到达目标值时,执行代码
    40.        if(iCurrent!=json[attr]){
    41.           flag=false;//终止条件
    42.           if(attr==="opacity"){//为透明度时,执行
    43.              element.style.filter="alpha(opacity:"+(iCurrent+iSpeed)+")";//IE
    44.              element.style.opacity=(iCurrent+iSpeed)/100;//标准
    45.            }else{//默认
    46.                element.style[attr]=iCurrent+iSpeed+"px";
    47.            }
    48.        }else{
    49.            flag=true;
    50.        }
    51.        //4. 运动终止,是否回调
    52.        if(flag){
    53.           clearInterval(element.timer);
    54.           if(func){
    55.               func();
    56.           }
    57.         }
    58.       }
    59.     },30);
    60.   }
    

    运动框架总结



    运动框架演变过程


    框架 变化
    statrMove(element) 运动
    startMove(element,iTarget) 匀速 -- > 缓冲 --> 多物体
    startMove(element,attr,iTargrt) 任意值
    startMove(element,attr,iTargrt,func) 链式运动
    startMove(element,json,func) 多值(同时) --> 完美运动框架


    本文转自-爱思资源网,转载注明出处,谢谢!

    相关文章

      网友评论

        本文标题:JavaScript完美运动框架进阶之旅

        本文链接:https://www.haomeiwen.com/subject/kvjcdttx.html