优雅的 requestAnimationFrame

动画

这篇文章咱们来聊聊动画的实现方法。前端动画总体上分为两类 css 动画和 JS 动画,css 动画就是通过设置 css 属性实现,而 JS 动画就是利用 JavaScript 操控 DOM 的能力实现动画

css动画

这里简单介绍一下 css 动画

transition

transition 过渡动画与类名绑定,不会自动执行只能在满足触发条件是才会执行,并且不能重复执行,除非你再次满足触发条件。并且每次只能设置一个属性的变化。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
    #test:hover {
      width: 200px;
      transition: width 1s;
    }
  </style>
</head>
<body>
  <div id="test"></div>
</body>
</html>

animation

animation 动画就很好的解决了上述问题,它能够自动执行、重复执行,也能够同时设置多个属性的变化,并且还可以设置多个中间态

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
      animation: animate 1s linear;
    }
    @keyframes animate {
      0% {
        width: 100px;
        height: 100px;
      }
      50% {
        width: 160px;
        height: 160px;
      }
      100% {
        width: 300px;
        height: 300px;
      }
    }
  </style>
</head>
<body>
  <div id="test"></div>
</body>
</html>

但是因为 css 动画只能在现有的属性上让我们设置不同的值,然后执行。这就给 css 动画带来了限制,因为在开发中可能会遇到变化的属性是不固定的情况,此时 css 动画就很乏力了。并且 css 不能定义很复杂的动画。

JS 动画

因为 css 动画由以上诸多限制,所以有的时候我们不得不使用 JS 来控制动画流程。

早期JS动画

早期的 JS 动画是通过 settimeout 和 setInterval 执行的。这里时间间隔设置为 16.6ms 的原因是一般电脑的刷新率为 60 hz,也就是说 1s 屏幕会刷新 60 次,1000ms / 60 近似等于 16.6ms,这样理想情况下的动画最为平滑流畅。

setInterval 动画

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100;
    const animate = setInterval(() => {
      width++;
      oDiv.style.width = width + 'px';
      if (width >= 200) {
        clearInterval(animate);
      }
    }, 16.6) 
  </script>
</body>
</html>

setTimeout 动画

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100;
    const animate = () => {
      width++;
      oDiv.style.width = width + 'px';
      if (width <= 200) {
        setTimeout(animate, 16.6);
      }
    }
    setTimeout(animate, 16.6);
  </script>
</body>
</html>

上面说了时间间隔为 16.6ms 的动画最为流畅平滑,但是使用 setInterval 和 setTimeout 并一定能满足要求,我们知道 JS 的事件循环机制(不知道的可以看看这篇博客JS基础——捋一捋 JavaScript 事件循环机制), setInterval 和 setTimeout 是两个宏任务,这样就会每次间隔 16.6ms 就将回调函数推进宏任务队列,等待执行,注意这里并不是间隔 16.6ms 一定会执行回调函数,因此若一轮事件循环中同步任务执行时间过长就会导致动画卡顿。

requestAnimationFrame

因为 setInterval 和 setTimeout 实现动画的缺陷,我们就在思考,因为屏幕刷新率电脑的一个物理特性,并不会随你使用的编程语言变化而变化,那么有没有可能拿到屏幕的刷新时机,在每次刷新的时候执行一次回调函数呢?

基于这个想法,有人提出了 requestAnimationFrame 方法,接收一个函数参数,这个函数参数就是每次屏幕刷新前要执行的回调函数。requestAnimationFrame 方法会通知浏览器 JS 要执行动画了,在屏幕每次刷新前调用函数参数

requestAnimationFrame 方法本质上是浏览器有一个没有长度限制的队列,当调用 requestAnimationFrame 方法就会向队列中推入回调函数,该回调函数会在下一次重绘之前调用。个人理解它和 setInterval 原理上非常类似,只是回调函数的执行时机不同。

requestAnimationFrame 用法

它的用法和 setTimeout 的用法很像。但是它不用传入时间间隔(因为时间间隔是固定的),它的返回值是一个整数,是回调列表中的唯一 ID,作用是用来取消动画的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100;
    const animate = () => {
      width++;
      oDiv.style.width = width + 'px';
      if (width <= 200) {
        requestAnimationFrame(animate);
      }
    }
    requestAnimationFrame(animate);
  </script>
</body>
</html>

cancelAnimationFrame

本方法是用来取消动画的。上面的 demo 使用本方法如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100, end = 0;
    const animate = () => {
      width++;
      oDiv.style.width = width + 'px';
      end = window.requestAnimationFrame(animate);
      if (width >= 200) {
        window.cancelAnimationFrame(end);
      }
    }
    window.requestAnimationFrame(animate);
  </script>
</body>
</html>

requestAnimationFrame 还有两个优势:

  • 当你在执行动画时,但是你并没有浏览动画那个页面,那么该函数就会暂停执行,也就是动画会暂停,当你切回来后会继续执行动画,这将提高电脑的性能,让你的电脑有精力去做别的事。
  • 当在一帧中频繁操作 DOM 后,使用本方法 DOM 将只会被修改一次,最后的值就是所有 DOM 操作最后的结果,相当于一个节流的操作了。

测测电脑刷新率

运行将下面这段代码,你就可以得到你的电脑刷新率哦,可以多运行几次,更准确。

let count = 0, m = null;
function refresh() {
  count++;
  m = window.requestAnimationFrame(refresh);
}

window.requestAnimationFrame(refresh)
function getResult() {
  console.log('你的电脑刷新率为:' + count + 'Hz');
  window.cancelAnimationFrame(m);
}
setTimeout(getResult, 1000)

总结

requestAnimationFrame 方法解决了传统 JS 动画的问题,它的 API 也很简单,同时还有节流和离开暂停动画的效果,实在优雅啊。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值