'display: none;' 是提高还是降低了性能?

Does 'display: none;' improve or worsen performance?

我的页面有很多垂直滚动和数千个 DOM 元素。为了提高性能,我想到了将display: none;设置为视口上方和下方div的内容,即不可见的div(同时保持高度,显然):

为了检查我的想法是否有意义,我搜索了 SO 并找到了 this question。根据评论和接受的答案,最好的策略是 什么都不做 ,因为 display: none; 触发回流并可能产生相反的效果:

Setting display to none triggers reflow which is completely opposite of what you want if what you want is to avoid reflow. Not doing anything doesn't trigger reflow. Setting visibility to hidden will also not trigger reflow. However, not doing anything a much easier.

然而,a recent answer(不幸的是它看起来更像是评论甚至是问题)声称 display: none; 是 Facebook 等网站当前使用的策略,其中垂直滚动是几乎无限。

值得一提的是,与 OP 在该问题中的描述不同,我站点中每个可见的 div 都是交互式的:用户可以单击、拖动和使用 div 进行其他操作内容(我相信,这会使浏览器重新绘制页面)。

鉴于所有这些信息,我的问题是:display: none; 应用于 div 的 above/below 视口会提高性能还是会降低性能?又或者没有效果?

“虚拟滚动”的策略是在 HTML 元素超出视口时移除它,这提高了性能,因为减少了进入 dom 的元素数量并减少了时间repaint/reflow所有文档。

显示none不缩小dom,只是让元素不可见,像可见隐藏一样,不占用视觉space.

显示 none 不会提高性能,因为虚拟滚动的目标是减少进入 dom 的元素数量。

显示一个元素 none 或删除一个元素,触发重排,但是显示 none 你的性能最差,因为你没有减少 dom.

关于性能,显示none就像隐藏的一样。

Google Lighthouse 将具有 DOM 树的性能差的页面标记为:

  • 总共有超过 1,500 个节点
  • 深​​度大于 32 个节点
  • 有一个父节点有超过60个子节点

一般来说,想方设法只在需要时创建 DOM 个节点,并在不再需要时销毁节点。

显示 none 的唯一好处是:更改时不会导致重绘或回流。

来源:

当您有数千个 DOM 元素并且需要滚动浏览、交互等时提高性能的两个技巧

  1. 尝试手动管理前端框架提供的绑定。前端框架可能需要为您需要的简单数据绑定进行大量额外处理。它们适用于一定数量的 DOM 元素。但是如果你的情况比较特殊,超过了一般情况下DOM个元素的个数,那么只能考虑手动绑定。这当然可以消除任何滞后。

  2. 缓冲视口内和周围的 DOM 元素。如果您的 DOM 元素表示 table(s) 中的数据,请限制获取它们并且仅呈现您获取的内容。用户滚动动作应该使抓取和呈现向上或向下。

仅仅隐藏元素绝对不能解决您拥有数千个 DOM 元素带来的性能问题。即使您看不到它们,它们也会占用 DOM 树和内存。只有浏览器不必绘制它们。

这里有一些文章:

https://codeburst.io/taming-huge-collections-of-dom-nodes-bebafdba332 https://areknawo.com/dom-performance-case-study/

答案是,就像所有事情一样,这取决于情况。我认为您将不得不自己对其进行基准测试以了解具体情况。这是 how to run 一个“流畅度”基准,因为对您来说速度的感知可能比实际系统性能更重要。

正如其他人所说 display:none 将 DOM 留在内存中。通常渲染是昂贵的部分,但这取决于事情发生变化时必须渲染多少元素。如果重绘操作仍然需要检查每个元素,您可能看不到巨大的性能提升。这里有一些其他选项需要考虑。

使用虚拟 DOM

这就是 React 和 Vue 等框架使用 Virtual DOM 的原因。目标是接管浏览器决定更新什么的工作,并且只进行较小的更改。

完整 Add/Remove 个元素

你可以通过使用 Intersection Observer 来复制类似的东西来找出视口的 in/out 和 DOM 中的 add/subtract 而不是仅仅依赖 display:none 单独因为解析 javascript 通常比大型绘画更有效。

添加 GPU 加速

另一方面,如果 GPU is taking over rendering 油漆可能不会影响性能,但仅在某些设备上如此。您可以通过添加 transform: translate3d(0,0,0); 强制 GPU 加速来尝试。

给浏览器提示

您还可以通过使用 CSS Will-Change attribute 看到改进。其中一个输入基于视口之外的内容。所以 will-change:scroll-position; 在元素上。

CSS 内容可见性(前沿)

W3C 的 CSS 工作组有 CSS containment module in draft form. The idea is to allow the developer to tell the browser what to paint and when. This includes paint & layout containment. content-visibility:auto is a super helpful property designed for just this type of problem. Here's more background

编辑(2021 年 4 月)现在可在 Chrome 85+、Edge (Chromium) 85+ 和 Opera 71+ 中使用。 我们仍在等待在 Firefox 支持上,但我可以使用它的覆盖率为 65%。

值得一看,因为 demos I saw 在性能和 Lighthouse 得分方面产生了巨大差异。

元素"display: none" 属性 将该元素从文档流中删除。

将该元素显示 属性 从 none 重新定义为任何其他 动态 ,反之亦然,将再次强制更改文档流。

每次都需要重新计算流级联下的所有元素以进行新渲染。

所以是的,"display: none" 属性 应用于非零维度和自由流动或相对 定位 元素,将是一项代价高昂的操作,因此 会降低性能 !

这不会是说 position: absolute 的情况,否则,从自然和自由文档流中删除的元素显示 属性 可能会设置为 none 并返回而不触发e 重流在文件正文上。


现在,在您的特定情况下 [参见编辑后的图表],当您 move/scroll 将 'display: block' 恢复到以下 div 时,不会导致重新流 到文档上部的其余部分。因此,让它们可以随时显示是 安全的。因此不会影响页面性能。当您向上移动时,还有 display: none 个尾部元素,因为这将释放更多的显示内存。因此可能会提高性能。 在 HTML 流的上部和内部添加或删除元素时绝不会出现这种情况!

添加到已发布的答案中。

我测试的主要收获:

  • 将元素设置为 display: none; 会减少 ram 使用量
  • 未显示的元素不受布局变化的影响,因此在这方面没有(或很少)性能成本
  • Firefox 在处理大量元素方面做得更好 (~x50)

同时尽量减少布局更改。

这是我的测试设置:

<!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>
        body {
            margin: 0;
        }
        

        .testEl {
            width: 100%;
            height: 10px;
        }

    </style>
</head>
<body>
    <main id="main"></main>
</body>

<script>
    // firefox Max
    // const elementCount = 12200000;

    // chrome Max
    const elementCount = 231000;

    const main = document.getElementById("main");

    const onlyShowFirst1000 = true;

    let _content = ""
    for (let i = 0; i < elementCount; i++) {
        _content += `<div class="testEl" style="background-color: hsl(${(Math.random() * 360)|0}, 100%, 50%); display: ${!onlyShowFirst1000 || i < 1000 ? "block" : "none"}"></div>`;
    }
    main.innerHTML = _content;

    const addOneEnd = () => {
        const newEl = document.createElement("div");
        newEl.classList.add("testEl");
        newEl.style.backgroundColor = `hsl(${(Math.random() * 360)|0}, 100%, 50%)`

        requestAnimationFrame(() => {
            main.appendChild(newEl);
        })
    };

    const addOneBeginning = () => {
        const newEl = document.createElement("div");
        newEl.classList.add("testEl");
        newEl.style.backgroundColor = `hsl(${(Math.random() * 360)|0}, 100%, 50%)`

        requestAnimationFrame(() => {
            main.insertBefore(newEl, main.firstChild);
        })
    };

    const loop = (front = true) => {
        front ? addOneBeginning() : addOneEnd();
        setTimeout(() => loop(front), 100);
    };
</script>
</html>

我创建了很多元素,并且可以选择使用 onlyShowFirst1000 标志仅显示其中的前 1000 个。当显示所有元素时,firefox 允许最多 ~12200000 个元素(使用 10gb 的 RAM)和 chrome 最多 ~231000.

内存使用(231000 个元素):

          +----------+----------+-------------+
          | false    | true     | reduction % |
+---------+----------+----------+-------------+
| Chrome  | 415,764k | 243,096k | 42%         |
+---------+----------+----------+-------------+
| Firefox | 169.9MB  | 105.7MB  | 38%         |
+---------+----------+----------+-------------+

将元素的显示 属性 更改为 none 或从 none 更改会导致重新绘制区域,但您的元素区域通常相对较小,因此性能成本也会小的。 但是,根据您的布局,显示更改也可能会导致布局偏移,这可能会造成很大的代价,因为它会导致页面的很大一部分重新绘制。

将来(例如 chrome 85)您还可以使用 content-visibility 属性 来告诉浏览器哪些元素不必渲染。

此外,您使用开发工具将浏览器设置为显示重绘,chrome 打开渲染选项卡并选中“Paint flashing”。