第二弹:防抖节流与 rAF

大量频繁的操作会消耗性能,比如在 input 中输入内容的同时发送请求。

相信前端工程师们都知道这种情况下可以使用防抖和节流。

但本节的重点并非仅仅如此,而是另一个 JS 动画 API—— requestAnimationFrame。

关于防抖和节流的代码和分析,在这里也贴一下作为复习巩固之用

简介:手写防抖节流

细节:手写防抖和节流

requestAnimationFrame 是标准的动画时序,它的语法是这样的:

js
1let requestId = requestAnimationFrame(callback)

callback 会在浏览器每次重绘的最近时间运行。

如果我们在 callback 中对元素进行变化,这些变化将与其他requestAnimationFrame回调和 CSS 动画组合在一起。因此只会有一次几何重新计算和重绘,而不是多次。

rAF 设计的初衷就是给开发者用来进行时间调度的,我们可以将对元素产生变化的操作放在 rAF 中统一调度,这样有助于提高浏览器渲染的效率。

用代码示例来解释一下:

html
1<body>
2  <style>
3    .container {
4      width: 200px;
5      height: 200px;
6      overflow: hidden;
7      display: inline-block;
8    }
9  </style>
10  <div id="test"></div>
11  <script>
12    function changeWidth(rand) {
13      const imgs = document.querySelectorAll('img')
14      for (let img of imgs) {
15        img.style.width = (Math.sin(rand / 1000) + 1) * 200 + 'px'
16      }
17    }
18
19    const createElements = function () {
20      let html = ''
21      for (let i = 0; i < 1000; i++) {
22        html += `<div class="container">
23          <img src="./dp.png" alt="" />
24        </div>`
25      }
26      test.innerHTML = html
27    }
28    createElements()
29
30    window.addEventListener('pointermove', (e) => {
31      changeWidth(e.clientX)
32    })
33  </script>
34</body>

上面的代码会生成非常多的图片,当鼠标移动时,会慢慢将图片放大,效果如下:

Nov-22-2022 21-29-24

可以看到在pointermove事件执行时,大量的 DOM 回流导致页面明显卡顿。

为了优化它,这里加入 requestAnimationFrame API。它会在 layout 和 paint 之前执行且它会将所有 DOM 回流的几何运算组合到一起,这样就只会有一次重新运算和回流重绘。

让我们加入代码:

js
1window.addEventListener('pointermove', (e) => {
2  // changeWidth(e.clientX);
3  requestAnimationFrame(() => {
4    changeWidth(e.clientX)
5  })
6})

现在查看结果:

Nov-22-2022 21-43-16.gif

问题得到了解决。

不过我们再次审视一下这段代码:

js
1window.addEventListener('pointermove', (e) => {
2  requestAnimationFrame(() => {
3    changeWidth(e.clientX)
4  })
5})

当移动 pointer 时,requestAnimationFrame 会大量执行,我们可以给 pointermove增加防抖来节省回调频率。

代码如下:

js
1let timer = false
2window.addEventListener('pointermove', (e) => {
3  // changeWidth(e.clientX);
4  if (timer) return
5  timer = true
6  requestAnimationFrame(() => {
7    changeWidth(e.clientX)
8    timer = false
9  })
10})

最终的结果就是我们不但减少了事件回调的频率,还用 requestAnimationFrame 一次性让所有 DOM 回流,提高了浏览器的渲染效率。