美文网首页
温故而知新 回顾JavaScript完美运动框架.

温故而知新 回顾JavaScript完美运动框架.

作者: TouchMe丶 | 来源:发表于2018-04-20 09:53 被阅读67次

    这些天用空余时间回顾和整理了自己刚开始从事前端之时,跟着慕课网上Vivian老师封装的运动框架 move.js.
    原视频在 慕课网,下面用markDown写一下具体实现
    代码在我的github中有 ouaisheinie.

    JavaScript 完美运动框架实现(封装成move.js)

    一. 运动框架实现的基本思路和总纲

      1.简单动画:left,right,width,height,opacity等基本的属性值或者透明度的匀速运动。
      2.缓冲运动。
      3.多物体运动。
      4.任意值变化。
      5.链式运动。
      6.同时运动。
    

    二.简单动画

     1.匀速动画:
        匀速动画满足属性最基本的匀速运动
        假设定义一个div1  内部有一个id为share的分享span
    
      <div id="div1">
        <span id="share">分享</span>
      </div>
    

    样式为:

    #div1{
      width:200px;
      height:200px;
      background:red;
      position: relative;
      left:-200px;
      top:0;
    }
    #div1 span{
      width:20px;
      height:50px;
      background:blue;
      position: absolute;
      left: 200px;
      top:75px;
      color:#fff;
    }
    

    我们可以用JS操作其在网站上匀速做运动,代码如下:

    let oDiv = documet.getElementById("div1");
    oDiv.onmouseover = function(){
      startMove(0);
    }
    oDiv.onmouseout = function(){
      startMove(-200);
    }
    let timer  = null;
    function startMove(iTarget){
      clearInterval(timer);//每次执行优先清空定时器
        timer = setInterval(()=>{//定时器定义
          let speed = 0;
          if(oDiv.offsetLeft > iTarget){  //当前left大于目标  就减小
            speed = -10;
          }else{  //反之增加
            speed = 10;
          }
          if (oDiv.offsetLeft == iTarget){  //到达目标  清除定时器timer
            clearInterval(timer);
          }else{
            oDiv.style.left = oDiv.offsetLeft + speed + "px"; //没到达目标继续改变
          }
        },30);
      }
    }
    

    但是仅仅做匀速运动是不够的,接下来我们看看缓冲运动,缓冲运动意思就是在一开始的时候快,但是快要到目标的时候,会慢下来,就像火车到站的时候的速度变化一样。

    三.缓冲运动

    function startMove(iTarget) {
        clearInterval(timer);
        timer = setInterval(() => {
          let speed = (iTarget - oDiv.offsetLeft) / 10;
          //缓冲运动要给速度取整
          speed = speed > 0 ? Math.ceil(speed):Math.floor(speed);
          if (oDiv.offsetLeft == iTarget) {
            clearInterval(timer);
          } else {
            oDiv.style.left = oDiv.offsetLeft + speed + "px";
          }
        }, 30);
      }
    

    这里的speed不再是10或者-10,而是一个动态值。speed等于目标值与当前值的差的十分之一(具体除以多少可以根据需要改变)。
    可以试想一下,最后当目标值与当前值很接近的时候可以无限趋近于0,就会相对静止,运动就结束。但是要注意,这里要给匀速运动判断一下并且取整,speed>0之时用数学方法Math.ceil()向上取整,speed<0之时用Math.floor()向下取整。如果不取整,Javascript会让函数不停执行,分享面板会不停地移动。因为有小数的存在,很难做到oDiv.offsetLeft == iTarget 这个终止条件的达成。

    四.多物体运动

    试想一下如果给几个元素都添加运动函数。这很简单。
    dom结构如下

    <ul class="ManyUl">
        <li class="manyli"></li>
        <li class="manyli"></li>
        <li class="manyli"></li>
      </ul>
    

    外加样式

    .manyli,.ManyUl{
      list-style:none;
    }
    .manyli{
      width:200px;
      height:100px;
      background:purple;
      margin-bottom:20px;
    }
    

    因为DOM中有三个待添加事件的元素,所以需要遍历给他们添加事件,用到for循环

    let aLi = document.getElementsByTagName('li');
    for(let i = 0;i<aLi.length;i++){
        //给每个Li添加了一个timer属性  给一个空
        aLi[i].timer = null;
        aLi[i].onmouseover = function(){
          startMove(this,400);
        }
        aLi[i].onmouseout = function(){
          startMove(this,200);
        }
      }
    

    因为给不同的元素添加事件,需要用到this来把事件绑定到某一个元素的上下文环境;所以大家看到这个startMove()里面多了一个参数this。且涉及到给每个元素绑定事件的,不能公用timer。上一节匀速运动的代码中的timer变量就不能定义在外层,必须如上面代码中 每一个aLi都单独定义一个aLi[i].timer = null;只要是多物体运动,所有的变量都不能公用,必须是在私有作用域内。
    如果对this的机制不理解的,可以去看看《你不知道的JavaScript》这本书上册的第二部分,74页开始。JS代码如下:

      function startMove(obj,iTarget){   //obj是代指被添加事件的元素
        clearInterval(obj.timer);
        obj.timer = setInterval(function(){
          let speed = (iTarget - obj.offsetWidth)/8;
          speed = speed > 0?Math.ceil(speed):Math.floor(speed);
          if(obj.offsetWidth == iTarget){
            clearInterval(obj.timer);
          }else{
            obj.style.width = obj.offsetWidth + speed + 'px';
          }
        },30);
      }
    

    下面来看一下多物体下,透明度改变的代码。然后再把普通属性以及透明度整合一下。

      <div class="div11">
      </div>
      <div class="div11">
      </div>
      <div class="div11">
      </div>
      <div class="div11">
      </div>
      <div class="div11">
      </div>
    

    样式:

    .div11{
      width:200px;
      height:200px;
      margin:20px;
      float:left;
      background:red;
      filter:alpha(opacity = 30);
      opacity: 0.3;
      }
    

    JavaScript代码

    window.onload = function(){
      let oDiv = document.getElementsByTagName("div");
      for(let i = 0;i<oDiv.length;i++){
        oDiv[i].timer = null; 
        oDiv[i].alpha = 30;           //多物体运动  alpha 和 timer 不能公用
        oDiv[i].onmouseover = function () {
          startMove(this,100);
        }
        oDiv[i].onmouseout = function () {
          startMove(this,30);
        }
      }
    
    //从这里可以看到 之前的timer 和 alpha 变量没有了  因为是5个div的多物体运动  不能公用任何变量
    
      function startMove(obj,iTarget){
        clearInterval(obj.timer);
        obj.timer = setInterval(()=>{
          let speed = 0;
          if(obj.alpha > iTarget){
            speed = -10;
          }else{
            speed = 10;
          }
          if(obj.alpha == iTarget){
            clearInterval(obj.timer);
          }else{
            obj.alpha+=speed;
            obj.style.filter = "alpha(opacity =" + obj.alpha +")";  // ie
            obj.style.opacity = obj.alpha/100;  //火狐或者chrome
          }
        },30);
      }
    }
    

    以上就是多物体下 元素透明度的改变的代码 注意在startMove()函数中,透明度改变要写两种表达式,一种是obj.style.filter = "alpha(opacity =" + obj.alpha +")"; 是为了兼容ie浏览器,第二种是obj.style.opacity = obj.alpha/100; 是为了兼容firefox和chrome等浏览器。

    五.任意属性变化

    顾名思义就是可以让任何属性进行变化,包括边框,填充,边界,透明度等。因为透明度。先来说说透明度,这个要单独拿出来讲。整合上面多物体下的透明度以及一般属性的代码,看一下。dom结构如下:

      <ul class="ManyUl">
        <li class="manyli2" id="li1"></li>
      </ul>
    

    CSS代码如下:

    .manyli,.ManyUl{
      list-style:none;
    }
    .manyli{
      width:200px;
      height:100px;
      background:purple;
      margin-bottom:20px;
    }
    

    下面来看看JS代码:

    window.onload = function () {
        let li1 = document.querySelector("#li1");
        let li2 = document.querySelector("#li2");
        li1.onmouseover = function(){
          startMove(this,"opacity",100);
        };
        li1.onmouseout = function(){
          startMove(this,"opacity",30);
        };
        
      let alpha = 30;  //这是一个操作透明度的变量,这个例子暂时没涉及多物体,只是单个元素的改变
      function startMove(obj,attr,iTarget) {
        clearInterval(obj.timer);
        obj.timer = setInterval(function () {
          let icur = 0;
          if(attr == "opacity"){
            icur = Math.round(parseFloat(getStyle(obj,attr)) * 100);
          }else{
            icur = parseInt(getStyle(obj, attr));
          }
          let speed = (iTarget - icur) / 8;
          speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
          if (icur == iTarget) {
            clearInterval(obj.timer);
          } else {
            if(attr == "opacity"){
              obj.style.filter = "alph1(opacity =" + (icur + speed)+")";
              obj.style.opacity = (icur + speed)/100;
            }else{
              obj.style[attr] = icur + speed + 'px';
            }
          }
        }, 30);
      }
    
      function getStyle(obj,attr){
        if(obj.currentStyle){ //ie下
          return obj.currentStyle[attr];
        }else{//ff 和 chrome 下
          return getComputedStyle(obj,false)[attr];
        }
      }
    }
    

    可以看到多了一个函数 getStyle(obj,attr),这是自己封装的一个获取元素属性值的一个函数。兼容ie和firefox,chrome;
    startMove()内部我们可以看到定义了一个icur当前值,赋值为0。后面再来改变他。并且在属性非透明度(opacity)的时候,我们会给其用parseInt()函数取整,在这里速度是整数并不容易出错。如果属性是透明度属性,那么属性值肯定会是一个小数,我们用parseFloat()方法取得浮点型数据,然后还需要用数学方法Math.round()四舍五入。才能得到当前的速度。然后再来进行操作。
    obj.style[attr] = icur + speed + 'px';这一句中运用[]表示法来表示属性,为的是方便用参数代表要改变的属性。因为opacity属性和其他一般属性的不同之处,所以在判断icur和执行改变状态时都加了一层判断来判断attr是否等于"opacity"。到这里之后,我们就可以封装一个move.js,但是这并不完美,我们还是先封装出来 move.js:

      function startMove(obj,attr,iTarget) {
        clearInterval(obj.timer);
        obj.timer = setInterval(function () {
          let icur = 0;
          if(attr == "opacity"){
            icur = Math.round(parseFloat(getStyle(obj,attr)) * 100);
          }else{
            icur = parseInt(getStyle(obj, attr));
          }
          let speed = (iTarget - icur) / 8;
          speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
          if (icur == iTarget) {
            clearInterval(obj.timer);
          } else {
            if(attr == "opacity"){
              obj.style.filter = "alph1(opacity =" + (icur + speed)+")";
              obj.style.opacity = (icur + speed)/100;
            }else{
              obj.style[attr] = icur + speed + 'px';
            }
          }
        }, 30);
      }
    
      function getStyle(obj,attr){
        if(obj.currentStyle){ //ie下
          return obj.currentStyle[attr];
        }else{//ff 和 chrome 下
          return getComputedStyle(obj,false)[attr];
        }
      }
    }
    

    六.链式运动

    链式运动的意思就是执行完一个动作,马上开始执行另一个动作。动作的执行有顺序性和连贯性,不会再同一时间点执行2个或以上任务。html文件中引用一下move.js.上代码:
    dom:

    <ul>
      <li id="li3"></li>
    </ul>
    

    CSS:

    #li3{
      list-style: none;
      width:100px;
      height:100px;
      background:purple;
      margin-bottom:20px;
      border:4px solid #000;
      /* 加个透明度 */
      filter:alpha(opacity=30);
      opacity: 0.3;
    }
    

    运用上面一节我们封装的move.js,这一节的JavaScript代码会非常简单。首先,我们在上一节封装的代码中,并没有第四个参数,回调函数的参数。我们可以修改一下上面封装的move.js,只修改startMove()给其加一个参数fn,而且

      function startMove(obj,attr,iTarget,fn) {
        clearInterval(obj.timer);
        obj.timer = setInterval(function () {
          let icur = 0;
          if(attr == "opacity"){
            icur = Math.round(parseFloat(getStyle(obj,attr)) * 100);
          }else{
            icur = parseInt(getStyle(obj, attr));
          }
          let speed = (iTarget - icur) / 8;
          speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
          if (icur == iTarget) {
            clearInterval(obj.timer);
            if(fn){
              fn()
            }
          } else {
            if(attr == "opacity"){
              obj.style.filter = "alph1(opacity =" + (icur + speed)+")";
              obj.style.opacity = (icur + speed)/100;
            }else{
              obj.style[attr] = icur + speed + 'px';
            }
          }
        }, 30);
      }
    

    于是这一节的Js代码
    JavaScript:

      let Li = document.getElementById("li3");
      Li.onmouseover = function(){
        startMove(Li,'width',400,function(){
          startMove(Li,'height',200,function(){
            startMove(Li,"opacity",100);
          });
        });
      }
      Li.onmouseout = function(){
        startMove(Li,"opacity",30,function(){
          startMove(Li,'height',100,function(){
            startMove(Li,"width",100);
          })
        });
      }
    

    非常简单,就是用到函数封装,我们执行完第一次运动后马上回调第二次运动的函数,执行完第二次后,马上回调第三次执行的函数,就是这个原理。

    七.同时运动

    上一节是链式运动,执行完一个动作才能执行另一个动作。那么怎么能让2个或者多个动作同时执行呢。
    我们修改一下move.js:

    function startMove(obj,json,fn) {  //这里用到了Json数据格式,表达出要改变的属性集和成的对象
      let flag = true//新增了一个 判断是否停止执行定时器的标准。
      clearInterval(obj.timer);
      obj.timer = setInterval(function () {
        for(let attr in json){ //用for in 循环遍历要改变的属性。定时器每次执行,每个属性都可以改变。
          //取当前值
          let icur = 0;
          if (attr == "opacity") {
            icur = Math.round(parseFloat(getStyle(obj, attr)) * 100);
          } else {
            icur = parseInt(getStyle(obj, attr));
          }
          //算速度
          let speed = (json[attr] - icur) / 8;
          speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
          //停止检测
          if (icur != json[attr]) { //还没有执行完  还没到目标值的话,flag为false;
            flag = false;
          }
          if (attr == "opacity") {
            obj.style.filter = "alphl(opacity:" + (icur + speed) + ")";
            obj.style.opacity = (icur + speed) / 100;
          } else {
            obj.style[attr] = icur + speed + 'px';
          }
        }
        if(flag){ //flag为true  才执行fn。这里是回调执行的判断。
          clearInterval(obj.timer);
          if(fn){
            fn();
          }
        }
      }, 30);
    }
    

    这就是最完美的move.js代码。
    同时运动的html结构

       <ul>
        <li id="li4"></li>
      </ul>
    

    CSS代码:

    #li4{
      list-style: none;
      width:100px;
      height:100px;
      background:purple;
      margin-bottom:20px;
      border:4px solid #000;
      /* 加个透明度 */
      filter:alpha(opacity=30);
      opacity: 0.3;
    }
    

    事先要在html文件中引用move.js.
    JavaScript代码:

      let oLi = document.getElementById("li4");
      oLi.onmouseover = function(){
        startMove(oLi,{width:400,height:200,opacity:100});
      }
      oLi.onmouseout = function(){
        startMove(oLi,{width:100,height:100,opacity:30});
      }
    

    可以看到,第二个参数是一个json对象,代表你需要同时改变的属性的都有哪些。key为属性,value就是需要改变到的目标值。
    在此就整合完毕,move.js最完美的状态可以奉上:
    move.js

    function getStyle(obj, attr) {
      if (obj.currentStyle) { //ie下
        return obj.currentStyle[attr];
      } else {//ff 和 chrome 下
        return getComputedStyle(obj, false)[attr];
      }
    }
    function startMove(obj,json,fn) {
      let flag = true//判断是否停止执行定时器的标准
      clearInterval(obj.timer);
      obj.timer = setInterval(function () {
        for(let attr in json){
          //取当前值
          let icur = 0;
          if (attr == "opacity") {
            icur = Math.round(parseFloat(getStyle(obj, attr)) * 100);
          } else {
            icur = parseInt(getStyle(obj, attr));
          }
          //算速度
          let speed = (json[attr] - icur) / 8;
          speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
          //停止检测
          if (icur != json[attr]) { //还没有执行完  还没到目标值的话,flag为false;
            flag = false;
          }
          if (attr == "opacity") {
            obj.style.filter = "alphl(opacity:" + (icur + speed) + ")";
            obj.style.opacity = (icur + speed) / 100;
          } else {
            obj.style[attr] = icur + speed + 'px';
          }
        }
        if(flag){ //flag为true  才执行fn
          clearInterval(obj.timer);
          if(fn){
            fn();
          }
        }
      }, 30);
    }
    
    

    相关文章

      网友评论

          本文标题:温故而知新 回顾JavaScript完美运动框架.

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