使用无限滚动或大量DOM元素对性能有何影响?

83

我有一个关于大量dom元素与性能的问题。

假设我在页面上有6000个dom元素,随着用户与页面交互(用户滚动以创建新的dom元素)元素数量可能会增加,就像Twitter一样。

为了提高页面的性能,我可以想到只有两件事。

  1. 将不可见项的显示设置为none以避免回流
  2. 从dom中删除不可见项然后根据需要重新添加它们。

除了这些方法,还有其他方式可以改善具有大量dom元素的页面吗?


3
将display设置为none会触发回流,这与你想要避免回流的目的完全相反。不做任何事情不会触发回流。将visibility设置为hidden也不会触发回流。然而,不做任何事情更加简单。 - slebetman
3
请注意,您的窗口管理器已经删除屏幕上的不可见像素以加快用户界面交互速度。在JavaScript中自己处理这些像素可能会减慢速度而不是加速。 - slebetman
1
不会的。您可以通过在调试模式下编译Firefox或Chrome并通过分析器运行它来自行检查。滚动会触发重绘,所有程序中的所有滚动,甚至是Word或记事本中的滚动都会触发重绘。即使是移动鼠标指针也会触发重绘。但是重绘很便宜-自20世纪80年代以来已经进行了优化,并由操作系统/窗口管理器本身实现,而不是由程序实现。滚动不会触发回流。 - slebetman
1
@slebetman // 啊..我从http://paulirish.com/2011/dom-html5-css3-performance/视频中得到了滚动重排的印象。该视频列出了window.scrollBy会导致重排。我想知道为什么用户的滚动不会导致重排。 - Moon
@Moon,我已经发布了一个类似的问题http://stackoverflow.com/questions/33340901/how-do-i-get-dynamic-content-in-a-div-while-scrolling-and-still-keep-a-limit-on,请分享您可能拥有的任何见解。 - Lord Loh.
显示剩余7条评论
3个回答

105

FoldingText 上,我们遇到了一个类似的问题。随着文档变得越来越大,越来越多的行元素和相关的 span 元素被创建。浏览器引擎似乎无法处理,因此需要找到更好的解决方案。

这是我们所做的事情,可能对您有用,也可能没有:

将整个页面视为一个长文档,将浏览器视窗视为特定部分的镜头。您只需要显示镜头内的部分。

因此,第一部分是计算可见视口。(这取决于您的元素放置方式,是绝对 / 固定 / 默认)

var top = document.scrollTop;
var width = window.innerWidth;
var height = window.innerHeight;

以下是一些更加跨浏览器的视口资源:

如何使用JavaScript获取浏览器视口尺寸?

检测浏览器窗口的scrollTop的跨浏览器方法

其次,您需要一个数据结构来了解哪些元素在该区域中可见

我们已经有了一个用于文本编辑的平衡二叉搜索树,因此我们将其扩展到管理行高度,所以对我们而言这部分相对容易。我认为您不需要复杂的数据结构来管理元素高度;一个简单的数组或对象可能就足够了。只需确保可以轻松地查询其高度和尺寸即可。现在,您如何获取所有元素的高度数据。对于大量元素来说,非常简单(但计算成本很高!)

var boundingRect = element.getBoundingClientRect()

我在谈论纯javascript,但如果你使用jQuery的$.offset$.position以及列在这里的方法将非常有帮助。

同样,使用数据结构只是作为缓存很重要,但如果你愿意,也可以动态计算(尽管如我所述,这些操作是昂贵的)。此外,注意更改CSS样式和调用这些方法。这些函数会强制重新渲染,因此可能会出现性能问题。

最后,只需使用一个单独的元素(例如<div>)来替换屏幕外的元素,并计算高度即可

  • 现在,您已经将所有元素的高度存储在数据结构中,请查询所有位于可见视口之前的元素。

  • 创建一个css高度设置(以像素为单位)为元素高度总和的<div>

  • 用一个类名标记它,以便您知道它是填充器div

  • 从dom中删除此div覆盖的所有元素

  • 插入新创建的div

重复执行,直到处理完可见视口之后的元素。

查找滚动和调整大小事件。在每次滚动时,您需要返回到数据结构中,删除占位符div,创建以前从屏幕中移除的元素,并相应地添加新的占位符div。

:) 这是一种长而复杂的方法,但对于大文档,它可以显著提高性能。

tl;dr

我不确定是否已经恰当地解释了它,但这种方法的要点是:

  • 了解您的元素的垂直尺寸
  • 知道滚动视图端口
  • 用单个div表示所有屏幕外的元素(高度等于它覆盖的所有元素的高度总和)
  • 在任何给定时间,您将需要两个div,一个用于可见视口上方的元素,一个用于下方的元素。
  • 通过侦听滚动和调整大小事件来跟踪视口。相应地重新创建div和可见元素

3
顺便提一下,我发现这是处理大量DOM元素并仍然保持合理滚动体验的唯一方法。而且,在(jQuery)滚动处理程序中获取scrollTop不会导致重新绘制。实际上,它从不会重新绘制,只会清除重新绘制和重绘的队列,以便可以返回最新的scrollTop值。我猜测在处理滚动事件时它已经是最新的了。 - Joren Van Severen
1
@JorenVanSeveren 这取决于您对复杂数据结构的需求。正如我所说,我们使用了平衡二叉树,因为我们已经有了一个。我认为您不仅需要它来管理dom的高度。 - Mutahhir
@Mutahhir 谢谢,你能解释一下当用户向下滚动并且你删除了顶部的DOM节点时,你是如何避免重排/重绘的吗? - Gene Vayngrib
@GeneVayngrib,每当您从DOM中删除元素并插入其他元素时,浏览器都会进行回流处理。因此,我认为您无法使用此方法避免这种情况。话虽如此,您可以通过不仅显示一个屏幕的内容,而是显示三个屏幕的内容来减少这种情况。这样,对于+/- 1页滚动,就不会发生回流。 - Mutahhir
9
@Moon:这个答案中概述的同样技术已经以插件形式实现了。ClusterizeJS。它的使用效果完全符合预期,如果需要,它还支持项目奇偶性。而且它的确非常优秀!(附注:我不是它的开发者) - Robert Koritnik
显示剩余8条评论

30

我个人没有使用过这方面的经验,但是这里有一些很棒的技巧: http://engineering.linkedin.com/linkedin-ipad-5-techniques-smooth-infinite-scrolling-html5

我查看了Facebook,它在Firefox上似乎没有任何特别之处。当你向下滚动时,页面顶部的DOM元素不会改变。Firefox 的内存使用量在达到大约 500MB 后,Facebook 将不再允许你滚动。

Twitter 看起来与 Facebook 相同。

而Google Maps则是不同的情况 - 视野之外的地图瓦片从DOM中删除(虽然不是立即删除)。


1
// 感谢您抽出时间来调查您的发现。我很快也会这样做。 - Moon
2
很有趣的是看看Pinterest是如何做到的。当你向下滑动并返回时,它们似乎已经把元素动态加载完了。他们似乎总是在任何给定时间点上都有一组固定数量的Pins。 - alex

13

现在是2019年。这个问题很老了,但我认为它仍然相关和有趣,也许随着我们现在都倾向于使用React JS,一些东西已经改变了。

我注意到Facebook的时间线似乎使用了隐藏内容的群集,当群集超出视图时,就会使用display: none !important隐藏所有先前呈现的DOM元素,只是那些超出视图的元素被隐藏了display: none !important。 此外,隐藏群集的整体高度设置为隐藏群集的父div

以下是我制作的一些屏幕截图:

enter image description here

enter image description here

enter image description here

截至2019年,您认为这种方法如何?对于那些使用React的人来说,它如何在React中实现?非常期待收到您对这个棘手话题的看法和想法。感谢关注!

2
有一个名为react-window的库,它与上述解释非常接近。 - Esther Cuan

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