# 使用 fastdom 解决布局抖动

避免回流主要考虑两点

# 避免回流

在清除哪些操作会导致回流的情况下,尽量不要去做这些操作。比如需要改变一个元素位置的时候,使用left,top,bottom,right这些值一定是会触发页面回流的,可以考虑使用translate代替。translate既不会触发回流也不会触发重绘,仅仅会触发复合图层,这样就大大节省了性能和布局重绘的开销了。

现代前端中常用的一些框架,比如react,vue,等。内部使用了 VDOM,他会将一些 DOM 修改的操作进行批量的操作通过计算去减少回流。

TIP

其实translate,opacity等属性是不会触发回流和重绘的,仅仅只会触发复合的过程,这是因为对于这些属性现代浏览器会为这些属性自动调用GPU加速,从而导致抽离到单独的图层交给GPU处理。

之所以强调配合使用will-change,是因为在一些旧版本浏览器中,对于translate等等属性并不会自动调用 GPU 加速从而抽离到单独图层中,所以需要配合will-change告诉浏览器抽离单独图层交给GPU处理。

总而言之,在使用translate``opacity属性的时候,尽量还是添加上will-change提起告诉浏览器发生需要交给GPU哪些属性需要交给它。

# 读写分离

但是有时候回流无法避免,那么就尽量将读写分离。将读和写分离开来,从而更好的节能。

# FastDom

TIP

fastDom不是把 1000 次读变成 1 次读,它主要作用是将读操作和写操作缓存后分别批量进行。这样就不会因为某个读操作的时候发现前面有写操作要先强制回流一下。

fastDom (opens new window)

fastDom就是针对读写分离的库,当然也可以选择自己做。

当使用 fastDom 改写之前的 Demo 之后可以明显看到性能的区别以及页面更加流程

使用fastdom.measure批量读取操作,然后在通过fastdom.mutate进行批量写。

使用fastdom就可以解决布局抖动,因为可以合并读和写,将多次 layout 让浏览器自动合并。

在必须要进行频繁的 DOM 操作时,可以使用 fastdom 这样的工具,它的思路是将对页面的读取和改写放进队列,在页面重绘的时候批量执行,先进行读取后改写。因为如果将读取与改写交织在一起可能引起多次页面的重排。而利用 fastdom 就可以避免这样的情况发生。

DETAILS
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./fastDom.js"></script>
    <style>
      .app {
        /* will-change: transform; */
        width: 300px;
        height: 300px;
        background-color: red;
        /* animation: wang 2s infinite; */
      }

      @keyframes wang {
        from {
          transform: translate(0);
        }

        to {
          transform: translate(100px);
        }
      }
    </style>
  </head>

  <body>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>
    <div class="app"></div>

    <script>
      const divs = document.getElementsByClassName("app");
      const update = () => {
        for (let div of divs) {
          fastdom.measure(() => {
            const top = div.offsetTop; // 批量读
            fastdom.mutate(() => {
              div.style.width = top * Math.random() + "px"; // 批量写
            });
          });
        }
        window.requestAnimationFrame(update);
      };
      window.addEventListener("click", () => {
        update();
      });
    </script>
  </body>
</html>