“display:none”会提高还是降低性能?

32

我有一个页面需要大量垂直滚动和数千个DOM元素。为了提高性能,我考虑将视口上方和下方的div内容设置为display: none;,也就是那些不可见的div(同时保持它们的高度,显然):

enter image description here

为了验证我的想法是否有道理,我在SO上搜索并找到了这个问题。根据评论和被接受的答案,最好的策略是什么也不做,因为display: none;会触发回流并可能产生相反的效果:

将显示设置为none会触发回流,这与您想要避免回流的目的完全相反。什么也不做不会触发回流。将可见性设置为hidden也不会触发回流。但是,什么也不做更容易。

然而,最近的答案(不幸的是,它似乎更像是一条评论甚至是一个问题)声称display: none;是像Facebook这样的网站当前使用的策略,其中垂直滚动几乎是无限的。
值得一提的是,与该问题中OP的描述不同,我的网站中每个可见的div都是交互式的:用户可以点击、拖动和对div的内容进行其他操作(我相信这会使浏览器重新绘制页面)。
考虑到所有这些信息,我的问题是:将display: none;应用于视口上方/下方的div是否可以改善性能,还是会降低性能?或者它没有影响?

10
关于性能的问题我不能回答,但需要注意的是display: none并不会保留元素的高度。 display: none将元素完全从流中移除。visibility: hidden将使它们不显示,但在布局中保留其位置和尺寸。如果Facebook正在这样做,他们一定有一个不是display:none的元素来维护高度。但是他们很可能在这些元素内部使用display:none的包装器,这样当重新流动发生时,这些元素中可能存在的(可能复杂的)布局逻辑就不必再次发生了。 - T.J. Crowder
@Megapteranovaeangliae 无限滚动的策略是删除DOM元素以减轻内存负担,因为对于非常大的列表来说,这往往是瓶颈。 - user120242
fb的情况与众不同,他们只是在页面上设了一个最大限制,而没有试图支持无限滚动,可能是基于这样的假设:没有人需要一次性滚动浏览他们的订阅内容。fb使用display:none只是因为他们不想让虚拟滚动占用屏幕空间,而回流并不重要,因为它们必须追加到底部。当你到达那个点时,允许加载占位符,这使得体验稍微好一些,因为你会立即得到反馈。 - user120242
1
"display: none; 触发回流" 不是的,至少不比样式中的其他更改更多。在您的情况下,由于您将内容放置在固定大小的容器内,将内容设置为 display none 只会影响此内容区域,并且由于它被设置为 display none,它将被短路为“什么也不做”,而 visibility: hidden 仍然必须检查所有内部节点以获取可能的 visibility: visible。现在,关于性能,唯一可以确保的是您的开发工具->测试两者,进行分析,保留最常用配置中的最佳结果。 - Kaiido
如果你正在使用React,那么react-virtualized是一个不错的解决方案:https://bvaughn.github.io/react-virtualized/#/components/Collection - Lucas D
显示剩余4条评论
5个回答

21
一个元素的"display: none"属性会将该元素从文档流中移除。
重新定义该元素的显示属性,从none更改为任何其他动态属性,反之亦然,都会再次强制更改文档流。
每次需要重新计算所有流式级联下的元素以进行新的渲染。
因此,对于非零维度和自由流或相对定位的元素应用"display: none"属性将是一项昂贵的操作,因此会降低性能
这不适用于position: absolute等情况,已经从自然和自由文档流中移除的元素,其显示属性可以设置为none并返回,而不会在文档主体上触发重新流动。
现在针对您的具体情况[见编辑后的图表],当您向下移动/滚动将“display:block”带回以下div时,不会导致整个文档上部的重新流动。因此,您可以在进行操作时安全地使它们可显示。因此,不会影响页面性能。同时,当您向上移动时,尾部元素的"display:none"将释放更多的显示内存。因此可能会提高性能。 但是,如果从HTML流的上部添加或删除元素,则永远不会出现这种情况! enter image description here

我不明白为什么上面的div会影响性能,而下面的div则不会。 如果我按照你说的将所有div的位置属性设置为absolute或fixed,无论div是在视口div的上方还是下方,这样做不会改善性能吗? - Vaibhav Chobisa

12
答案是,像几乎所有事情一样,这取决于情况。我认为您需要自己进行基准测试以了解具体情况。以下是如何运行“平滑度”基准测试,因为对于您来说,速度感知可能比实际系统性能更重要。
正如其他人所述,display:none会使DOM保留在内存中。通常,渲染是昂贵的部分,但这取决于更改时必须渲染多少元素。如果重新绘制操作仍然需要检查每个元素,则可能看不到巨大的性能提升。以下是要考虑的其他选项。
使用Virtual DOM
这就是为什么React和Vue之类的框架使用Virtual DOM。目标是接管浏览器的工作,决定更新什么并仅进行较小的更改。
完全添加/删除元素
您可以通过使用Intersection Observer来找出视口中的内容并实际添加/减去DOM,而不仅仅依赖于display:none,因为解析JavaScript通常比大型绘图更有效率。
添加GPU加速
另一方面,如果GPU正在接管渲染,则绘图可能不会成为性能问题,但这只适用于某些设备。您可以尝试通过添加transform: translate3d(0,0,0);来强制启用GPU加速。
给浏览器提示
通过利用CSS Will-Change属性,您还可以看到改进。其中一个输入是基于内容在视口之外。因此,在元素上使用will-change:scroll-position;
CSS Content-Visibility(前沿技术)
W3C的CSS工作组正在起草CSS包含模块。其目的是允许开发人员告诉浏览器何时以及如何绘制,包括绘制和布局限制。content-visibility:auto是一种非常有用的属性,专门设计用于解决这类问题。以下是更多背景信息编辑(2021年4月)现在可在Chrome 85+、Edge(Chromium)85+和Opera 71+中使用。我们仍在等待Firefox支持,但Can I Use显示它有65%的覆盖率。
值得一看,因为我看到的演示大大提高了性能和Lighthouse分数。

3
补充已经发布的答案。
我的测试结果要点:
- 将元素设置为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%         |
+---------+----------+----------+-------------+

将元素的显示属性从无到有或从有到无会导致该区域被重绘,但是您的元素区域通常会相对较小,因此性能成本也会很小。但是,根据您的布局,显示更改可能还会导致布局移位,这可能非常昂贵,因为它会导致页面的大部分重新绘制。
在未来(例如Chrome 85),您还可以使用 content-visibility 属性告诉浏览器哪些元素不必呈现。
另外,您可以使用开发工具设置浏览器显示重绘情况,在Chrome中打开渲染选项卡并勾选“闪烁绘制”。

2

当您有成千上万个需要滚动、交互等的DOM元素时,以下两个技巧可以提高性能。

  1. 尝试手动管理前端框架提供的绑定。前端框架可能需要进行很多额外的处理才能实现您所需的简单数据绑定。它们对于一定数量的DOM元素是有效的。但如果您的情况特殊,超过了平均情况下的DOM元素数量,则最好手动绑定并考虑周围环境。这可以确保消除任何延迟。

  2. 在视口范围内和周围缓存DOM元素。如果您的DOM元素代表表格中的数据,则使用限制条件获取它们并仅呈现所获取的内容。用户滚动操作应使获取和呈现向上或向下进行。

仅仅隐藏元素肯定不能解决您由于具有数千个DOM元素而导致的性能问题。即使您无法看到它们,它们也会占用DOM树和内存。只有浏览器不必绘制它们。

以下是一些文章:

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


我非常感谢你的回答,+1,它提供了很多对我的情况有用的信息,我一定会使用它。然而,在这个问题中,我只是在寻找关于display: none;和性能关系的明确解释。 - user12273078
@ Megapteranovaeangliae,对于这个问题,你不会有一个明确的答案,因为没有任何东西能阻止浏览器在其许多优化中切换到display:none,当它确定容器的框超出了视口框时。 (IIIRC至少一些最近的浏览器是这样做的)。所以你可能看不到什么区别,或者可能更好,因为它不必计算框何时离开,或者可能更糟,因为你的实现将比他们的不太优化。这个答案中的建议是你应该关注的,将优化留给浏览器开发人员。 - Kaiido

1
“虚拟滚动”的策略是在视口外移除HTML元素,这样可以提高性能,因为减少了DOM中的元素数量,并减少了重新绘制/重排整个文档所需的时间。
display none不会减小DOM的大小,只是使元素不可见,就像visible hidden一样,而不占用视觉空间。
display none不会提高性能,因为虚拟滚动的目标是减少DOM中的元素数量。
将元素设置为display none或删除元素会触发回流,但使用display none会降低性能,因为您没有减少DOM的好处。
关于性能,display none与visible hidden相同。
谷歌Lighthouse会标记具有以下DOM树的性能不良页面:
- 总节点数超过1,500个 - 深度大于32个节点 - 父节点具有超过60个子节点
一般来说,寻找在需要时仅创建DOM节点的方法,并在不再需要时销毁节点。
display none的唯一好处是:更改时不会导致重新绘制或回流。
来源:

1
有关这些说法的任何来源吗?根据经验,DOM 操作相对于回流和重绘来说真的微不足道,因此避免这些操作本身就是一个很好的性能提升。 - Kaiido
此外,visibility:hiddendisplay:none是两种完全不同的情况。前者可以完全忽略节点内部的所有内容,而对于后者,浏览器仍然必须检查每个内部元素是否可能为visibility:visible - Kaiido
我非常感谢你的回答,+1,就像其他答案一样,它对我的情况有很多有用的信息,我肯定会使用它。然而,正如我在另一个答案中评论的那样,在这个问题中,我只是在寻找关于display: none;和性能关系的清晰解释。 - user12273078
谢谢你提供的资源,你看过它们了吗?第一个根本没有提到 CCSOM,而且 https://developers.google.com/speed/images/reflow-chart.png 显示 reflow 不适用于 display: none - Kaiido
第一个链接讨论了减少DOM大小的性能优势。第二个链接讨论了重绘/回流。在最后一句话中,我说:“display none的唯一好处是:在更改时不会导致重绘或回流。”我的答案有什么问题? - Simone Nigro
1
关于我的答案有什么问题吗?“减少重绘/回流整个文档的时间”,“像visible hidden一样”,“display none不会提高性能”,“就性能而言,display none就像visible hidden一样。”你自相矛盾->“将元素显示为none或删除元素,触发回流”,然后“display none的唯一好处是:在更改时不会导致重绘或回流。” - Kaiido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接