JS 动画优化 代码篇

动画优化

匀速运动

将box匀速移动到屏幕右边界, box的右边框

边界处理问题

每次移动之前都判定,这次移动是否碰撞到边界, 如果碰撞到就将它的位置设置为边界.client - 盒子.offsetWidth; 否则移动一个步长

代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style type="text/css">
      * {
        margin: 0;
        padding: 0;
      }
      #box {
        position: absolute;
        left: 0px;
         100px;
        height: 100px;
        background-color: lightblue;
        padding: 10px;
      }
      #box2 {
        position: absolute;
        left: 0px;
         100px;
        height: 100px;
        background-color: lightblue;
        padding: 10px;
      }
      div {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div><div id="box"></div></div>
    <div><div id="box2"></div></div>
  </body>
  <script type="text/javascript">
    var oBox = document.getElementById("box");
    var maxLeft =
      (document.documentElement.clientWidth || document.body.clientWidth) -
      oBox.clientWidth;
    var duration = 2000;
    var setpt = (maxLeft / duration) * 10;
    var timer = window.setInterval(function () {
      var curLeft = window.getComputedStyle(oBox, null).left;
      var left = parseInt(curLeft);
      console.log(curLeft);
      if (left + setpt >= maxLeft) {
        window.clearInterval(timer);
        oBox.style.left =
          document.documentElement.clientWidth - oBox.offsetWidth + "px";
        return;
      }
      left += setpt;
      console.log(left);
      oBox.style.left = left + "px";
    }, 10);
    function Linear(t, b, c, d) {
      return (c * t) / d + b;
    }
    var oBox2 = document.getElementById("box2");
    var begin = parseInt(window.getComputedStyle(oBox2, null).left);
    var target =
      (document.documentElement.clientWidth || document.body.clientWidth) -
      oBox.clientWidth;
    var change = parseFloat(target) - parseFloat(begin);
    var duration = 2000;
    var time = null;
    var timer2 = setInterval(function () {
      time += 10;
      if (time >= duration) {
        clearInterval(timer2);
        return;
      }
      var curPos = Linear(time, begin, change, duration);
      console.log(time, begin, change, duration);
      console.log("curPos", curPos);
      box2.style.left = curPos + "px";
    }, 10);
  </script>
</html>

setTimeout实现轮循动画

使用递归思想完成setTimeout的轮循动画: 每一次在执行动画之前首先把上一次设置的那个没有用的定时器清除掉, 节约我们的内存空间

代码

<script type="text/javascript">
    var oBox = document.getElementById("box");
    var maxLeft =
      (document.documentElement.clientWidth || document.body.clientWidth) -
      oBox.clientWidth;
    var duration = 2000;
    var setpt = (maxLeft / duration) * 10;
    var timer = window.setInterval(function () {}, 10);
    var timer = null;
    function move() {
      clearTimeout(timer);
      var curLeft = window.getComputedStyle(oBox, null).left;
      var left = parseInt(curLeft);
      console.log(curLeft);
      if (left + setpt >= maxLeft) {
        window.clearTimeout(timer);
        oBox.style.left =
          document.documentElement.clientWidth - oBox.offsetWidth + "px";
        return;
      }
      left += setpt;
      console.log(left);
      oBox.style.left = left + "px";
      timer = setTimeout(move, 10);
    }
    move();
  </script>

实现反弹动画(作用域积累优化问题)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style type="text/css">
      * {
        margin: 0;
        padding: 0;
      }
      #box {
        position: absolute;
        left: 0px;
         100px;
        height: 100px;
        background-color: lightblue;
        padding: 10px;
      }
      #box2 {
        position: absolute;
        left: 0px;
         100px;
        height: 100px;
        background-color: lightblue;
        padding: 10px;
      }
      div {
        height: 200px;
      }
      .btn {
        position: absolute;
        top: 200px;
        left: 100px;
        height: 50px;
      }
      .btn input {
        outline: none;
        display: inline-block;
        margin-left: 50px;
         100px;
        height: 50px;
        border: 1px solid green;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div><div id="box"></div></div>
    <div class="btn">
      <input type="button" value="向左" id="btnLeft" />
      <input type="button" value="向右" id="btnRight" />
    </div>
  </body>
  <script type="text/javascript">
    var oBox = document.getElementById("box");
    var maxLeft =
      (document.documentElement.clientWidth || document.body.clientWidth) -
      oBox.clientWidth;
    var minLeft = 0;
    var timer = null;
    function move(target) {
      window.clearTimeout(timer);
      var curLeft = window.getComputedStyle(oBox, null).left;
      var left = parseFloat(curLeft);
      var step = 20;
      if (left < target) {
        // 向右走
        if (left + step >= target) return;
        left += step;
        oBox.style.left = left + "px";
      } else if (left > target) {
        // 向左走
        if (left + step <= target) return;
        left -= step;
        oBox.style.left = left + "px";
      } else {
        // 不需要运动
      }
      timer = window.setTimeout(function () {
        move(target);
      }, 10);
    }
    document.getElementById("btnLeft").onclick = function () {
      move(minLeft);
    };
    document.getElementById("btnRight").onclick = function () {
      move(maxLeft);
    };
  </script>
</html>

性能问题

这样写性能不好, 因为每一次到达时间的时候, 都需要先执行一次匿名函数(形成一个私有的作用域), 在匿名函数中在执行move, 但是move中需要用到的数据在第一次执行的move方法中, 需要把匿名函数形成的这个私有的作用域作为跳板找到之前的,这样就导致了匿名函数形成的这个私有的作用域不能被销毁。。。

解决

_move下的私有变量没有被其他地方引用,所以_move执行完栈销毁。
当所有_move执行完都被销毁时,move中的target没有被其他地方引用,move形成的执行栈也销毁。

function move(target) {
      function _move() {
        window.clearTimeout(timer);
        var curLeft = window.getComputedStyle(oBox, null).left;
        var left = parseFloat(curLeft);
        var step = 20;
        if (left < target) {
          // 向右走
          if (left + step >= target) return;
          left += step;
          oBox.style.left = left + "px";
        } else if (left > target) {
          // 向左走
          if (left + step <= target) return;
          left -= step;
          oBox.style.left = left + "px";
        } else {
          // 不需要运动
        }
        timer = window.setTimeout(function () {
          _move();
        }, 10);
      }
      _move();
    }

保证当前元素同一时间只能执行一个动画

为了让当前的元素在同一个时间只运行一个动画(下一个动画开始的时候首先把上一个动画的定时器清除掉):保证当前元素所有的动画接受定时器返回值的那个变量需要共享(把这个值放在当前元素的自定义属性上)

代码

function move(target) {
      function _move() {
        window.clearTimeout(oBox._timer);
        var curLeft = window.getComputedStyle(oBox, null).left;
        var left = parseFloat(curLeft);
        var step = 20;
        if (left < target) {
          // 向右走
          if (left + step >= target) return;
          left += step;
          oBox.style.left = left + "px";
        } else if (left > target) {
          // 向左走
          if (left + step <= target) return;
          left -= step;
          oBox.style.left = left + "px";
        } else {
          // 不需要运动
        }
        oBox._timer = window.setTimeout(function () {
          _move();
        }, 10);
      }
      _move();
}

JS实现高性能动画法则

  • 边界判断+步长
  • 清除没有用的定时器
  • 大move(target),里面写一个_move(),避免作用域的累积
  • 把定时器的返回值放到自定义属性上,避免同一个元素在同一时间执行多个动画

实现多方向匀速运动动画

简单版代码

<!--
 * @Author: lemon
 * @Date: 2020-09-16 11:40:20
 * @LastEditTime: 2020-09-16 12:15:51
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: React前端准备CSS基础动画简单匀速运动动画库.html
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style type="text/css">
      * {
        margin: 0;
        padding: 0;
      }
      #box {
        position: absolute;
        left: 0px;
         100px;
        height: 100px;
        background-color: lightblue;
        padding: 10px;
        opacity: 1;
        filter: alpha(opacity=100);
      }
      div {
        height: 200px;
      }
      .btn {
        position: absolute;
        top: 200px;
        left: 100px;
        height: 50px;
      }
      .btn input {
        outline: none;
        display: inline-block;
        margin-left: 50px;
         100px;
        height: 50px;
        border: 1px solid green;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div><div id="box"></div></div>
    <div class="btn">
      <input type="button" value="向左" id="btnLeft" />
      <input type="button" value="向右" id="btnRight" />
    </div>
  </body>
  <script type="text/javascript">
    function setCSS(curEle, style) {
      for (var key in style) {
        if (curEle.hasOwnProperty(key)) {
          curEle.style[key] = style[key];
        }
      }
    }
    (function () {
      var effect = {
        Linear: function (t, b, c, d) {
          return (c * t) / d + b;
        },
      };
      // move:实现多方向的运动动画  curEle:当前要操作运动的元素 target:当前动画的目标位置, 存储每一个方向的目标位置{left:xxx,top:xxx,xxx}
      // duration:当前动画的总时间
      function move(curEle, target, duration) {
        // 在每一次执行方法之前首先把元素之前正在运行的动画结束掉
        window.clearInterval(curEle._timer);
        // 根据target获取每一个方向的起始值和总距离change
        var begin = {},
          change = {};
        for (var key in target) {
          if (target.hasOwnProperty(key)) {
            begin[key] = parseFloat(window.getComputedStyle(curEle, null).key);
            if (isNaN(begin[key])) {
              begin[key] = 0;
            }
            change[key] = target[key] - begin[key];
          }
        }
        console.log(begin);
        var time = 0;
        curEle._timer = window.setInterval(function () {
          time += 10;
          // 到达目标: 结束动画, 让当前元素的样式等于目标央视值
          if (time >= duration) {
            setCSS(curEle, target);
            window.clearInterval(curEle._timer);
            return;
          }
          // 没达到目标: 分别的获取每一个方向的当前位置, 给当前元素设置样式即可
          for (var key in target) {
            if (target.hasOwnProperty(key)) {
              var curPos = effect.Linear(
                time,
                begin[key],
                change[key],
                duration
              );
              console.log(begin[key], change[key]);
              curEle.style[key] = curPos + "px";
            }
          }
          console.log(curPos);
        }, 10);
      }
      window.Animate = move;
    })();
    var oBox = document.getElementById("box");
    Animate(
      oBox,
      {
        left: 1000,
        top: 500,
        opacity: 0,
         0,
        height: 0,
      },
      1000
    );
  </script>
</html>

支持回调函数

.....
  if (time >= duration) {
            setCSS(curEle, target);
            window.clearInterval(curEle._timer);
            if (typeof callBack === "function") {
              callBack.call(curEle);
            }
            return;
          }

......
 Animate(
      oBox,
      {
        left: 1000,
        top: 500,
        opacity: 0,
         0,
        height: 0,
      },
      1000,
      function () {
        console.log(this);
        this.style.background = "red";
      }
);
原文地址:https://www.cnblogs.com/xiaoxu-xmy/p/13678511.html