长列表滚动优化:通过优化合成层优化性能

638 阅读2分钟

Web 性能优化特别是长列表滚动优化是一个老生常谈的问题,一般我们的思路是通过虚拟滚动、GPU 加速、fragment 复用等方式优化性能。

本文主要讲解另一种方式:压缩合成层。关于合成层,网上已有许多文章,但大多冗长地讲解为何创建合成层。本文略过此部分,因为我们可以通过Chrome开发者工具分析具体情况以了解创建合成层的原因。Blink也已在chromium库中给出创建合成层的具体逻辑。

合成层是什么

对于 blink 渲染引擎的渲染流程,大致可以分为以下几个阶段:

Dom Tree -> Layout Object -> Paint Layer -> Graphics Layers Tree -> Paint
DOM 树 -> 布局对象 -> 绘制层 -> 图形层树 -> 绘制

简述如下:

  • DOM 树到渲染树基本一一对应,除display:none的元素。
  • 布局对象会按条件创建绘制层。
  • 绘制层在变为图形层的过程中,会创建合成层,对应独立图形层。
  • 图形层把结果渲染到纹理,最终通过Chrome渲染层和系统显示。

实际上我们可以发现,合成层的多少会比较影响我们的渲染性能,合成层比较多的情况下,当我们对页面进行交互(比如滚动),触发重新渲染,就会有卡顿的风险。

分析合成层

Chrome 的 DevTools 工具可以让我们比较方便地进行合成层分析,例如我们通过一个 demo 来进行分析:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dda844feee724afeb235af02e6c9dd5d~tplv-k3u1fbpfcp-zoom-1.image

在上图中,我们会发现这个 demo 的合成层比较多,我们点进去可以查看到是因为 overflow 导致创建了新的合成层。

也就是说,对该 demo 而言我们可以尝试在这些 Demo 中去掉或者修改 overflow 的相关设置,从而进行合成层优化。

优化合成层

我们尝试去掉 overflow: scroll;。( Demo 源代码会在本文最后给出)

然后我们设置页面的列表元素为 500 个,通过模拟页面持续滚动,来检查去掉前后的性能。

去掉前,cpu 保持在 50%+,这实际上已经是一个比较高的数值了:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/843a15c994564736a195ff6266e9f5e5~tplv-k3u1fbpfcp-zoom-1.image

去掉后,cpu 保持在 2% 左右:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4745b602581b463fbae1292beac3d56c~tplv-k3u1fbpfcp-zoom-1.image

我们可以看到,优化后有巨大的性能提升。

demo代码参考

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,minimal-ui:ios"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      .container {
        width: 100vw;
        height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .list {
        width: 500px;
        height: 90vh;
        overflow: scroll;
      }
      .li {
        width: 100%;
        height: 50px;
        border-bottom: 2px;
        border-style: solid;
        border-color: grey;
        /* 加上下面这行代码,CPU占用50%+;去掉下面这行代码,CPU占用5%+ */
        /* overflow: scroll; */
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="list"></div>
    </div>
  </body>
  <script>
    const totalListCount = 500;
    const list = document.querySelector(".list");

    for (let i = 0; i < totalListCount; i += 1) {
      let fragment = document.createElement("div");
      fragment.classList.add("li");
      fragment.innerHTML = `<p>this is the ${i} element</p>`;
      list.appendChild(fragment);
    }
    let curr = 0;
    const renderScroll = function () {
      curr += 5;
      if (curr >= totalListCount) curr = 0;
      list.children[curr].scrollIntoView();
      window.requestAnimationFrame(renderScroll);
    };
    renderScroll();
  </script>
</html>

参考