在Web UI中渲染日志似乎很简单:它们只是纯文本的线条。但是,有很多额外的功能使它们对我们的用户更有用:着色,分组,搜索,永久链接等。但最重要的是,界面应该无论日志都有10个或成千上万的线路,都应该工作。这是我们在2019年制作GitHub行动的时候从一开始就优先考虑。我们当时没有使用量度指标,但很明显,我们不得不处理非常大的日志线的情况。浏览器可以在初始负载中冻结,如果我们没有正确解决此问题,可能是完全无法使用的。我们必须利用一种称为虚拟化的技术。
虚拟化包括仅渲染列表中的信息的子集,以使UI无缝行为,而没有用户注意到尚未呈现的可见视口中的数据。它需要更新用户滚动时的可见内容,计算布局位置,即使不是所有信息都呈现以保持滚动体验平滑等等。
当我们首次启动GitHub操作时,我们用基于React的库和vanilla JavaScript One测试。由于它们的实施方式,一些图书馆具有困难的限制。例如,许多库要求屏幕中呈现的所有项目具有固定高度。此约束使计算使它们更容易,因为如果用户想要滚动到指定的项目(可见或不可见),则它们只需乘以emply_index * items_height来计算位置,然后滚动到它。此外,为了计算整个可滚动的高度,他们可以做一些类似的项目_count *项目_height。而已!当然,在许多情况下,所有元素都有相同的高度,使这个限制不可接受。在GitHub操作的情况下,我们想要打破长的日志线,这意味着我们必须支持具有变量高度的日志行。
我们最终选择了一个拥有我们想要的大多数功能的vanilla javascript库:具有可变元素高度的能力,滚动到项目等。但是,由于各种原因,我们开始看到错误和差的UX体验的限制:
可滚动区域应具有固定的高度,这是另一个典型的限制,使内部实现容易。但在我们的情况下,这使得UX体验差,特别是因为在我们的日志中,一个作业将有多个步骤,每个步骤都有自己的虚拟化。这意味着页面必须为每个步骤都有单独的滚动区域以及整页的滚动条。
对于虚拟化列表必须将其从隐藏可见的可见性切换到可见的情况并不是很好的。对于GitHub操作,我们允许用户展开和折叠步骤,并自动展开日志。我们开始看到一步开始运行时有一个错误。当我们自动扩展日志以使它们可见时,此时浏览器选项卡不可见,有时用户无法在返回浏览器选项卡时正确地看到日志。
用户无法同时选择文本并滚动,因为选择将从DOM删除由于虚拟化而删除。
在某些情况下,体验很慢,因为我们必须在后台渲染日志行以便计算它们的高度。虚拟化并没有帮助多大,我们实际上是两次渲染两行,而不是根本没有渲染它们。但是,我们不得不这样做,因为不正确的高度计算导致在UI中被切断的日志线。
出于所有这些原因,我们决定改造日志经验。我们开始了一个问题:我们还需要虚拟化吗?如上所述,我们没有关于推出的用途的指标,但现在我们能够根据真实用法做出决定。例如,如果我们的绝大多数用户具有足够小的日志,我们可以删除虚拟化,以便在没有的情况下呈现它们,但允许单独下载更大的日志。
我们的数据显示,99.51%的现有就业机会少于50k线,但我们知道浏览器开始以超过20k的日志线挣扎。我们还发现即使存在低数量的日志线,也可能占用的空间太多。通过所有这些信息,我们决定我们不需要数据虚拟化,但我们仍然需要UI虚拟化。数据虚拟化需要仅在内存中加载日志的部分并将更多信息获取,但我们发现没有必要的复杂程度。在具有较少数量的日志线路的非常大的日志文件的非常边缘情况下,我们截断并提供下载它的链接。
一旦做出这些决定,我们试图寻找我们使用的替代图书馆,但他们都不适合我们的需求。我们必须从头开始实施。我们的目标是:
在大多数情况下使UI / UX平滑。包括在搜索结果或永久链接和流日志之间跳跃时。
使计算尽可能快,并使用更少的内存。不是完全准确的计算很好。
要达到这些目标,我们必须在少数地区的其他图书馆方面接近这一点不同:
估计高度在渲染前:虽然,大多数虚拟化库依赖于精确的计算或固定高度,并使用绝对定位使实现非常简单,我们决定在渲染之前估算高度以避免昂贵的计算。我们还使用相对定位,以避免如果这些计算不准确,则避免切断的日志线。
DOM结构:另一个挑战是如何构建DOM以允许粘性标头并只有一个可滚动区域。可滚动的容器必须包含两个,但粘性标头不得虚拟化。
我们快速实施以验证我们的策略。经过一些测试,我们发现我们发现的大日志,虽然我们验证了我们可以实现我们自己的虚拟化会议,但我们必须小心,因为它很容易做出错误的决策并破坏整个经历。例如,我们快速实现的一件事是,尽可能少的DOM节点渲染很重要,但在滚动时尽可能少的DOM突变也很重要。当用户滚动时,我们需要添加变得可见的节点,并删除不再可见的节点。如果用户快速滚动,尤其是在移动设备中,可能会有太多的DOM突变导致子达经验。
但是,我们可以通过几种方式解决此问题。例如,您可以节省您的代码并以批处理进行更新而不是频繁进行更新。但我们发现这种方法使UI不太顺利。我们提出了在集群中分组日志行的想法,因此我们将在N行的群集中放置数目,而不是删除和添加单个行,并添加或删除群集而不是单个线路。在某些测试之后,我们现在了解群集有多少行将有:每簇50行。
在一周左右的一周左右,我们能够获得初步实现,使我们能够在UX方面看到所有的好处。那时我们知道我们在正确的道路上。接下来的几周我们在其他UI / UX改进中工作,我们知道我们必须处理的边缘案件长长的尾部。
经过大量工作的内部,我们向所有用户发货,现在很高兴提供优越的日志体验:更快,更顺畅,更友好,更凝聚力和强大。大多数时候您不必重新发明轮子,但有时最好的解决方案是从头开始实施自己的解决方案,以便在您的控制中完全进行体验和表现。