JavaScript动画框架封装(超爽)

553 阅读9分钟

匀速运动

运动是javascript动画的一个基本操作。通过css3属性的transition和animation可以实现运动。(后面的css3章节会详细讲解)。但是要进行更精细的操作,javascript运动是必不可少的。

简单运动

让一个元素在页面中运动起来很简单,设置定时器,改变定位元素的left和top值即可

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      #box{
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }
      #box span{
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <span>拉开</span>
    <div id="box">

    </div>
    <script>
      window.onload = function () {
        var oDiv = document.getElementById('box');
        oDiv.onmouseover = function () {
          setInterval(function () {
            oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
          },30)
        }

      }

    </script>

  </body>
</html>

查看效果:

会发现,鼠标悬浮的次数多,定时器是会累加的。并且运动会一直走下去。这不是我们想要的。归根结底还是定时器以及临界值的处理的缘故。

定时器管理

上面的代码没有进行定时器管理。当元素在运动的过程中,多次悬浮按钮,会开启定时器,从而使运动速度加快

两种定时器管理方式

  1. 开启新定时器前,消除旧定时器

  2. 当定时器未停止时,不允许开启新定时器

    注意:由于定时器开启时,其返回值是一个不为0的整数,所以可以通过判断其返回值,来确定是否使用return语句

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      #box {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }

      #box span {
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <span>拉开</span>
    </div>
    <script>
      window.onload = function() {
        var oDiv = document.getElementById('box');
        var timer = null;
        oDiv.onmouseover = function() {
          //1.先清除定时器,再开启定时器
          // clearInterval(timer);
          //2.当定时器未停止时,不允许开启新定时器
          if(timer) return;
          timer = setInterval(function() {
            if (oDiv.offsetLeft == 500) {
              clearInterval(timer);
            } else {
              oDiv.style.left = oDiv.offsetLeft + 5 + 'px';
            }
          }, 30)
        }

      }

    </script>

  </body>
</html>

分享效果

做一个类似于”分享到“侧边栏的效果

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style type="text/css">
      * {
        padding: 0;
        margin: 0;
      }

      #box {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }

      #box span {
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <span>拉开</span>
    </div>
    <script type="text/javascript">
      window.onload = function (){
        var oDiv = document.getElementById('box');
        oDiv.onmouseover = function (){
          oDiv.style.left = 0 + 'px';
        }
        oDiv.onmouseout = function (){
          oDiv.style.left = -200 + 'px';
        }
      }
    </script>
  </body>
</html>

效果显示:

移入移出效果

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      #box {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }

      #box span {
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <span>拉开</span>
    </div>
    <script>
      window.onload = function() {
        var oDiv = document.getElementById('box');
        var timer = null;
        oDiv.onmouseover = function() {
          //先清除定时器 再开启定时器
          clearInterval(timer);
          timer = setInterval(function() {
            //设置边界,清除定时器
            if (oDiv.offsetLeft === 0) {
              clearInterval(timer);
            } else {
              oDiv.style.left = oDiv.offsetLeft + 5 + 'px';
            }
          }, 30)

        }
        oDiv.onmouseout = function() {
          //先清除定时器 再开启定时器
          clearInterval(timer);
          timer = setInterval(function() {
            console.log(oDiv.offsetLeft);
            //设置边界,清除定时器
            if (oDiv.offsetLeft === -200) {
              clearInterval(timer);
            } else {
              oDiv.style.left = oDiv.offsetLeft - 5 + 'px';
            }
          }, 30)
        }
      }
    </script>

  </body>
</html>

运动函数

从上面的代码中,可以看出运动部分的重复代码较多,把运动封装为带参数的函数更合适

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      #box{
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }
      #box span{
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <span>拉开</span>
    </div>
    <script>
      window.onload = function () {
        var oDiv = document.getElementById('box');
        oDiv.onmouseover = function () {
          //匀速运动开始
          startMove(this,0)
        }
        oDiv.onmouseout = function () {
          //匀速运动开始
          startMove(this,-200)
        }
      }
      var timer = null,speed = 0;
      /**
             * @param {Object} oDiv 当前目标对象
             * @param {Object} target 运动到的目标位置
             */
      function startMove(oDiv,target) {
        //先清除定时器 再开启定时器
        clearInterval(timer);
        timer = setInterval(function () {
          //我们要求盒子既能向前又能向后,那么我们的步长就得有正有负
          //目标值如果大于当前值取正,目标值如果小于当前值取负
          speed  = target > oDiv.offsetLeft ? 10 : -10;
          //设置边界,清除定时器
          if(oDiv.offsetLeft === target){
            clearInterval(timer);

          }else{
            oDiv.style.left = oDiv.offsetLeft + speed + 'px';
          }
        },30)
      }

    </script>

  </body>
</html>

效果展示:

缓动运动

什么是缓动运动呢? 举个例子,比如火车在进站的时候,速度是由快到慢的过程,而火车在离站的时候是由慢到快的过程。

缓动动画公式: 速度 = (结束值-起始值) * 缓动系数。

缓动系数为0到1之间的数。

基本缓动动画

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      #box {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }

      #box span {
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <span>拉开</span>
    </div>
    <script>
      window.onload = function() {
        var oDiv = document.getElementById('box');
        var timer = null,target = 0,target1 = -200;

        oDiv.onmouseover = function() {
          clearInterval(timer);
          timer = setInterval(function() {
            speed = (target-oDiv.offsetLeft) / 20;		
            if (oDiv.offsetLeft == target) {
              clearInterval(timer);
            } else {
              oDiv.style.left = oDiv.offsetLeft + speed + 'px';
            }

          }, 30)
        }

        oDiv.onmouseout= function() {
          clearInterval(timer);
          timer = setInterval(function() {
            speed = (target1-oDiv.offsetLeft) / 20;		
            if (oDiv.offsetLeft == target1) {
              clearInterval(timer);
            } else {
              oDiv.style.left = oDiv.offsetLeft + speed + 'px';
            }

          }, 30)
        }


      }


    </script>

  </body>
</html>


缓动运动函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      #box {
        position: relative;
        width: 200px;
        height: 200px;
        background-color: red;
        left: -200px;
      }

      #box span {
        position: absolute;
        width: 40px;
        height: 60px;
        background-color: #000;
        color: #fff;
        right: -40px;
        top: 50%;
        margin-top: -30px;
        line-height: 60px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <span>拉开</span>
    </div>
    <script>
      window.onload = function() {
        //获取标签
        var oDiv = document.getElementById('box');
        //定时器变量设置全局,后面要处理
        var timer = null;

        oDiv.onmouseover = function() {
          slowMove(this,0);
        }
        //
        oDiv.onmouseout= function() {
          slowMove(this,-200);
        }
        function slowMove(obj,target){
          clearInterval(timer);
          timer = setInterval(function() {
            speed = (target-obj.offsetLeft) / 20;
            //如果速度是大于0,说明是往右走,向上取整。速度小于0,说明往左走,向下取整
            speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
            if (obj.offsetLeft == target) {
              clearInterval(timer);
            } else {
              obj.style.left = obj.offsetLeft + speed + 'px';
            }

          }, 30)
        }

      }
    </script>

  </body>
</html>

透明度动画

​ 透明度是一个比较特殊的样式,因为IE8浏览器不支持opacity,只能通过滤镜的方式写成filter:alpha(opacity=透明值)

​ 如果透明度做运动的话,则需要对运动函数进行重新封装

淡入淡出

代码如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style type="text/css">
      #box{
        width: 200px;
        height: 200px;
        background-color: red; 
        opacity: 0.3;
        filter: alpha(opactity:30);
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script type="text/javascript">
      window.onload = function (){
        var box = document.getElementById('box');
        box.onmouseover = function (){
          opacityAnimation(box,100);
        }
        box.onmouseout = function (){
          opacityAnimation(box,30);
        }
        var alpha = 30,timer = null,speed = 0;
        function opacityAnimation(ele,target){
          clearInterval(timer);
          timer = setInterval(function(){
            // 如果目标值大于当前变化的值,表示为真值,透明度在增大,反之亦然
            speed = target > alpha ? 10 : -10;
            // 当前变化的值等于了目标值,清除定时器
            if(alpha == target){
              clearInterval(timer);						
            }else{
              // 否则,透明度变化
              alpha+=speed;
              ele.style.filter = 'alpha(opactity:'+alpha+')';
              ele.style.opacity = alpha / 100;
            }
          },30)
        }
      }
    </script>
  </body>
</html>

​ 与物体运动不同的是,透明度的变化时改变的opacity值的变化。css属性中没有像offsetAlpha类似的值。所以我们在全局定义一个初始的alpha = 30的变量,来控制当前透明度数值的变化。

多物体动画

​ 如果在页面中有多个元素利用运动函数进行运动。由于定时器返回值在不同元素上时不同的返回值。所以要将全局的定时器挂载到当前的元素之上。

看个BUG

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      ul{
        list-style: none;
      }
      ul li{
        margin: 20px 0;
        width: 300px;
        height: 150px;
        background-color: yellowgreen;
      }
    </style>
    <script type="text/javascript">
      window.onload = function() {
        var allLis = document.getElementsByTagName('li');
        for (var i = 0; i < allLis.length; i++) {
          allLis[i].onmouseover = function() {
          /**
          * 第一个参数为当前的对象,第二个参数为目标值
          */
            startMove(this, 600);
          }
          allLis[i].onmouseout = function() {
            startMove(this, 300);
          }
        }
        var speed = 0,
            timer = null;

        function startMove(ele, target) {
          clearInterval(timer);
          timer = setInterval(function() {
            // 1.求出步长
            speed = (target - ele.offsetWidth) * 0.5;
            // 2. 判断speed的正负
            speed = target > ele.offsetWidth ? Math.ceil(speed) : Math.floor(speed);
            if (ele.offsetWidth == target) {
              clearInterval(ele.timer);
            } else {
              // 3.运动起来
              ele.style.width = ele.offsetWidth + speed + 'px';
            }

          }, 30)
        }
      }
    </script>
  </head>
  <body>
    <ul>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </body>
</html>

效果如下:

​ 这明显不是我们想要的效果。这是因为,当我们悬浮的比较快的时候,都会开启定时器。那么cleartInterVal()方法清除的定时器,它就很难知道清除的是哪个定时器了。导致出现了意想不到的效果。为了解决这一问题,我们应该将定时器的变量挂载到当前元素之上

解决代码如下:

window.onload = function() {
  var allLis = document.getElementsByTagName('li');
  for (var i = 0; i < allLis.length; i++) {
    allLis[i].onmouseover = function() {
      /**
			* 第一个参数为当前的对象,第二个参数为目标值
		    */
      startMove(this, 600);
    }
    allLis[i].onmouseout = function() {
      startMove(this, 300);
    }
  }
  var speed = 0;
  function startMove(ele, target) {
    clearInterval(ele.timer);
    ele.timer = setInterval(function() {
      // 1.求出步长
      speed = (target - ele.offsetWidth) * 0.5;
      // 2. 判断speed的正负
      speed = target > ele.offsetWidth ? Math.ceil(speed) : Math.floor(speed);
      if (speed == target) {
        clearInterval(ele.timer);
      } else {
        // 3.运动起来
        ele.style.width = ele.offsetWidth + speed + 'px';
      }
    }, 30)
  }
}

完美效果

获取样式

在上节课的多物体运动中,我们还是存在很多的问题的。比如如果我们给li标签设置border:4px solid #000;大家不妨看一下效果。(我们希望悬浮时width:600px;离开的时候width:300px)。但是bug由此产生了。这是因为offsetWidth的原因。那接下来我们研究一下它。

研究offsetWidth的坑

看个例子:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style type="text/css">
      #box{
        width: 200px;
        height: 200px;
        background-color: red;
        border: 1px solid #000;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script type="text/javascript">
      window.onload = function (){
        var oBox = document.getElementById('box');
        move(oBox);
      }
      function move(obj){
        setInterval(function (){
          obj.style.width = obj.offsetWidth-1 + 'px';
        },30)
      }
    </script>
  </body>
</html>

​ 我是希望让盒子的宽度逐渐减小。但是,一旦运行以上代码,你会发现匪夷所思的事情,天呢~这到底是为什么?

不妨alert(obj.offsetWidth);。会发现得到的结果是202。那obj.style.width = obj.offsetWidth - 1 + 'px';得到的width为201px。是要比之前一开始css样式中的width增大了1px。这是因为offsetWidth = border-left-width + padding-left + width + padding-right + border-right-width;所以我们不能通过offsetWidth来获取当前盒子的宽度。

两种办法解决

1.给当前盒子设置行内样式的宽度,并通过obj.style.width来获取当前盒子的宽度。但是我们以后所以的盒子都不可能全写在行内啊。所以不推荐使用。

2.封装自己的获取属性样式函数

/**
 * @param {Object} obj 哪个对象
 * @param {Object} attr 什么属性
*/
function getStyle(obj,attr){
  if(obj.currentStyle){
    // 针对IE浏览器
    return obj.currentStyle[attr];
  }else{
    // 针对于Firefox浏览器
    return getComputedStyle(obj,null)[attr];
  }
}

完整代码如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style type="text/css">
      #box{
        width: 200px;
        height: 200px;
        background-color: red;
        border: 1px solid #000;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script type="text/javascript">
      window.onload = function (){
        var oBox = document.getElementById('box');
        move(oBox);
      }
      function move(obj){
        setInterval(function (){
          obj.style.width = parseInt(getStyle(obj,'width'))-1 + 'px';
        },30)
      }
      /**
			 * @param {Object} obj 哪个对象
			 * @param {Object} attr 什么属性
			 */
      function getStyle(obj,attr){
        if(obj.currentStyle){
          // 针对IE浏览器
          return obj.currentStyle[attr];
        }else{
          // 针对于Firefox浏览器
          return getComputedStyle(obj,null)[attr];
        }
      }
    </script>
  </body>
</html>

多物体运动完整版

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      ul{
        list-style: none;
      }
      ul li{
        margin: 20px 0;
        width: 300px;
        height: 150px;
        background-color: yellowgreen;
        border: 4px solid #000;
      }
    </style>
    <script type="text/javascript">
      window.onload = function() {
        var allLis = document.getElementsByTagName('li');
        for (var i = 0; i < allLis.length; i++) {
          allLis[i].onmouseover = function() {
            /**
						 * 第一个参数为当前的对象,第二个参数为目标值
						 */
            startMove(this, 600);
          }
          allLis[i].onmouseout = function() {
            startMove(this, 300);
          }
        }
        var speed = 0;

        function startMove(ele, target) {
          //1.先关闭定时器 再开启定时器
          clearInterval(ele.timer);
          ele.timer = setInterval(function() {
            // 1.1 获取样式属性
            var cur = parseInt(getStyle(ele,'width'));
            // 1.2.求出步长
            speed = (target - cur) * 0.5;
            // 1.3 判断speed的正负
            speed = target > cur ? Math.ceil(speed) : Math.floor(speed);
            //1.4 临界值判断
            if (cur == target) {
              clearInterval(ele.timer);
            } else {
              // 1.5 运动起来
              ele.style.width = cur + speed + 'px';
            }

          }, 30)
        }
      }
    </script>
  </head>
  <body>
    <ul>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </body>
</html>

多值

​ 多物体运动同时改变的是同一个属性,如果我想既可以改变一个元素的宽度,又可以改变一个盒子的高度呢?如何去做?

​ 上述代码中,在最后修改元素的width时,可以有如下书写方式ele.style['width'] = cur + speed + 'px';这样的话,就可以将属性当做形参放到封装的函数中。

function startMove(ele,attr,target) {
  //1.先关闭定时器 再开启定时器
  clearInterval(ele.timer);
  ele.timer = setInterval(function() {
    // 1.1 获取样式属性
    var cur = parseInt(getStyle(ele,attr));
    // 1.2.求出步长
    speed = (target - cur) * 0.5;
    // 1.3 判断speed的正负
    speed = target > cur ? Math.ceil(speed) : Math.floor(speed);
    //1.4 临界值判断
    if (cur == target) {
      clearInterval(ele.timer);
    } else {
      // 1.5 运动起来
      ele.style[attr] = cur + speed + 'px';
    }

  }, 30)
}

那么外部在调用的时候

var allLis = document.getElementsByTagName('li');
allLis[0].onmouseover = function (){
  startMove(this,'width',600);
}
allLis[0].onmouseout = function (){
  startMove(this,'width',300);
}
allLis[1].onmouseover = function (){
  startMove(this,'height',400);
}
allLis[1].onmouseout = function (){
  startMove(this,'height',150);
}

想一个问题,之前咱们封装的getStyle获取样式的功能函数,css中什么属性不能实现呢?

透明度样式处理

​ 在上节课我们留下了一个问题,getStyle函数不可以获取什么属性呢?上节课的代码我们的运动框架逐渐成型,但是这远远还不够,比如看个bug。上节课代码修改

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      ul{
        list-style: none;
      }
      ul li{
        margin: 20px 0;
        width: 300px;
        height: 150px;
        background-color: yellowgreen;
        border: 4px solid #000;
        opacity: 0.3;
        filter: alpha(opacity: 30);
      }
    </style>
    <script type="text/javascript">
      window.onload = function() {
        var allLis = document.getElementsByTagName('li');
        allLis[0].onmouseover = function (){
          startMove(this,'opacity',100);
        }
        allLis[0].onmouseout = function (){
          startMove(this,'opacity',30);
        }
        var speed = 0;

        function startMove(ele, attr,target) {
          //1.先关闭定时器 再开启定时器
          clearInterval(ele.timer);
          ele.timer = setInterval(function() {
            // 1.1 获取样式属性
            var cur = parseInt(getStyle(ele,attr));
            // 1.2.求出步长
            speed = (target - cur) * 0.5;
            // 1.3 判断speed的正负
            speed = target > cur ? Math.ceil(speed) : Math.floor(speed);
            //1.4 临界值判断
            if (cur == target) {
              clearInterval(ele.timer);
            } else {
              // 1.5 运动起来
              ele.style[attr] = cur + speed + 'px';
            }

          }, 30)
        }
        /**
				 * @param {Object} obj 哪个对象
				 * @param {Object} attr 什么属性
				 */
        function getStyle(obj,attr){
          if(obj.currentStyle){
            // 针对IE浏览器
            return obj.currentStyle[attr];
          }else{
            // 针对于Firefox浏览器
            return getComputedStyle(obj,null)[attr];
          }
        }
      }
    </script>
  </head>
  <body>
    <ul>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </body>
</html>


效果如下:

发现,opacity属性值根本没有发生变化,两个原因吧!看图

知道了这两点原因,现在我们解决这两点。

修改代码如下:


var speed = 0;
function startMove(ele, attr,target) {
  //1.先关闭定时器 再开启定时器
  clearInterval(ele.timer);
  ele.timer = setInterval(function() {
    // 1.1 获取样式属性 处理透明度的问题 
    var cur = 0;
    if(attr === 'opacity'){
      cur = Math.round(parseFloat(getStyle(ele,attr))*100);
    }else{
      cur = parseInt(getStyle(ele,attr));
    }
    // 1.2.求出步长
    speed = (target - cur) * 0.5;
    // 1.3 判断speed的正负
    speed = target > cur ? Math.ceil(speed) : Math.floor(speed);
    //1.4 临界值判断
    if (cur == target) {
      clearInterval(ele.timer);
    } else {
      if(attr === 'opacity'){
        ele.style[attr]  = 'alpha(opacity:'+(cur+speed)+')';
        ele.style[attr] = (cur + speed)/100;
      }else{
        // 1.5 运动起来
        ele.style[attr] = cur + speed + 'px';
      }

    }
  }, 30)
}
/**
 * @param {Object} obj 哪个对象
 * @param {Object} attr 什么属性
*/
function getStyle(obj,attr){
  if(obj.currentStyle){
    // 针对IE浏览器
    return obj.currentStyle[attr];
  }else{
    // 针对于Firefox浏览器
    return getComputedStyle(obj,null)[attr];
  }
}

注意:由于透明度涉及小数计算,如0.07*100=> 7.000000000000001,所以需要用Math.round()去掉尾巴

链式动画

物体的多个属性可能是同时运动,也可能是一个属性运动完成之后,另一个属性再运动。如果要完成这种效果,就需要用到回调函数。

效果如下

如果想实现以上效果。我们现在将封装的运动框架存在一个myAnimation.js文件中。

myAnimation.js

var speed = 0;
/**
 * @param {Object} ele 当前元素
 * @param {Object} attr 当前元素的某个属性
 * @param {Object} target 目标
 */
function startMove(ele, attr, target,fn) {
  //1.先关闭定时器 再开启定时器
  clearInterval(ele.timer);
  ele.timer = setInterval(function() {
    // 1.1 获取样式属性
    var cur = 0;
    if (attr === 'opacity') {
      cur = Math.round(parseFloat(getStyle(ele, attr)) * 100);
    } else {
      cur = parseInt(getStyle(ele, attr));
    }
    // 1.2.求出步长
    speed = (target - cur) * 0.5;
    // 1.3 判断speed的正负
    speed = target > cur ? Math.ceil(speed) : Math.floor(speed);
    //1.4 临界值判断
    if (cur == target) {
      clearInterval(ele.timer);
      //修改的地方
      // 结束的时候做判断,调用fn
      if(fn){
        fn();
      }
    } else {
      if (attr === 'opacity') {
        ele.style[attr] = 'alpha(opacity:' + (cur + speed) + ')';
        ele.style[attr] = (cur + speed) / 100;
      } else {
        // 1.5 运动起来
        ele.style[attr] = cur + speed + 'px';
      }

    }
  }, 30)
}
/**
 * @param {Object} obj 哪个对象
 * @param {Object} attr 什么属性
 */
function getStyle(obj, attr) {
  if (obj.currentStyle) {
    // 针对IE浏览器
    return obj.currentStyle[attr];
  } else {
    // 针对于Firefox浏览器
    return getComputedStyle(obj, null)[attr];
  }
}

外部引用的时候。

<!--1.先引入外部的模块-->
<script src="js/myAnimation.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
  window.onload = function() {
    //2.获取元素
    var oBox = document.getElementById('box');
    //3.监听事件
    oBox.onmouseover = function() {
      //小心回调函数中this的指向。在回调函数中this的指向指向了window对象
      var that = this;
      //3.运动
      startMove(this, 'width', 500,function(){
        startMove(that,'height',400);
      });
    }
    oBox.onmouseout = function() {
      var that = this;
      startMove(this, 'height', 200,function(){
        startMove(that,'width',200);
      });
    }
  }
</script>

同时运动

如果之前的运动框架,我们想让元素既可以改变宽度,又可以改变透明度。这个时候我们用到JSON。关于JSON的详细讲解,在后面课程中我们会详细讲解,现在我们先简单用一下

var json = {
  "name":"mjj",
  "age":29
}
for(var attr in json){
  alert(attr); //获取属性名
  alert(json[attr]);//获取属性值
}

有了上面JSON的简单使用之后,我们将myAnimation.js进行修改。

var speed = 0;
/**
 * @param {Object} ele 当前元素
 * @param {Object} json 当前元素数值键值对 json数据
 * @param {Object} fn 当前回调函数
 */
function startMove(ele, json,fn) {
  //1.先关闭定时器 再开启定时器
  clearInterval(ele.timer);
  ele.timer = setInterval(function() {
    for(var attr in json){
      // 1.1 获取样式属性
      var cur = 0;
      if (attr === 'opacity') {
        cur = Math.round(parseFloat(getStyle(ele, attr)) * 100);
      } else {
        cur = parseInt(getStyle(ele, attr));
      }
      // 1.2.求出步长
      speed = (json[attr] - cur) * 0.5;
      // 1.3 判断speed的正负
      speed = json[attr] > cur ? Math.ceil(speed) : Math.floor(speed);
      //1.4 临界值判断
      if (cur == json[attr]) {
        clearInterval(ele.timer);
        // 结束的时候做判断,调用fn
        if(fn){
          fn();
        }
      } else {
        if (attr === 'opacity') {
          ele.style[attr] = 'alpha(opacity:' + (cur + speed) + ')';
          ele.style[attr] = (cur + speed) / 100;
        } else {
          // 1.5 运动起来
          ele.style[attr] = cur + speed + 'px';
        }

      }
    }
  }, 30)
}
/**
 * @param {Object} obj 哪个对象
 * @param {Object} attr 什么属性
 */
function getStyle(obj, attr) {
  if (obj.currentStyle) {
    // 针对IE浏览器
    return obj.currentStyle[attr];
  } else {
    // 针对于Firefox浏览器
    return getComputedStyle(obj, null)[attr];
  }
}

<script src="js/myAnimation2.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
  window.onload = function() {
    var oBox = document.getElementById('box');
    oBox.onmouseover = function() {
      startMove(oBox,{"width":400,"height":400,'opacity': 100})
    }
    oBox.onmouseout = function() {
      startMove(oBox,{"width":200,"height":200,'opacity': 30})
    }
  }
</script>

效果如下:

通过以上修改,实现了我们想要的效果。真的么??????看个bug。将代码修改

<script src="js/myAnimation2.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
  window.onload = function() {
    var oBox = document.getElementById('box');
    oBox.onmouseover = function() {
      startMove(oBox,{"width":201,"height":400,'opacity': 100})
    }
    oBox.onmouseout = function() {
      startMove(oBox,{"width":200,"height":200,'opacity': 30})
    }
  }
</script>

BUG效果如下:

看来啊还是封装的myAnimation.js运动框架存在着问题。分析原因如图所示:

也就是,当json中的所有的属性达到终点值的时候,才关闭定时器。换句话说,只要json中属性没有达到终点值的之后,就不能关闭定时器。

修改代码如下:

var speed = 0;
/**
 * @param {Object} ele 当前元素
 * @param {Object} json 当前元素数值键值对 json数据
 * @param {Object} fn 当前回调函数
 */
function startMove(ele, json, fn) {
  //1.先关闭定时器 再开启定时器
  clearInterval(ele.timer);
  ele.timer = setInterval(function() {
    // 做个标杆,如果为true,证明所有的属性已到达终点值
    var flag = true;
    for (var attr in json) {
      // console.log(attr,json[attr]);
      // 1.1 获取样式属性
      var cur = 0;
      if (attr === 'opacity') {
        cur = Math.round(parseFloat(getStyle(ele, attr)) * 100);
      } else {
        cur = parseInt(getStyle(ele, attr));
      }
      // 1.2.求出步长
      speed = (json[attr] - cur) / 20;
      // 1.3 判断speed的正负
      speed = json[attr] > cur ? Math.ceil(speed) : Math.floor(speed);

      // 1.4 如果所有的属性没到达终点值。继续执行下面的代码
      if(cur !== json[attr]){
        flag = false;
      }
      //1.5 处理属性名为opacity
      if (attr === 'opacity') {
        //1.5.1 兼容IE
        ele.style[attr] = 'alpha(opacity:' + (cur + speed) + ')';
        //1.5.2 w3c浏览器
        ele.style[attr] = (cur + speed) / 100;
      } else {
        // 1.6 运动起来
        ele.style[attr] = cur + speed + 'px';
      }

    }
    // 1.6 如果flag是成立的,证明所有的属性都到达终点,此时清除定时器,执行回调函数
    if (flag) {
      clearInterval(ele.timer);
      if (fn) {
        fn();
      }
    }


  }, 30)
}
/**
 * @param {Object} obj 哪个对象
 * @param {Object} attr 什么属性
 */
function getStyle(obj, attr) {
  if (obj.currentStyle) {
    // 针对IE浏览器
    return obj.currentStyle[attr];
  } else {
    // 针对于Firefox浏览器
    return getComputedStyle(obj, null)[attr];
  }
}

到目前为止我们的运动框架已基本完成,这个运动框架可以适用于网页上的很多效果。比如:无限轮播图效果、联动效果、导航动画特效