打造你自己的虚拟卷轴——第二部分
自动高度调节?
动态高度
性能优化
例子
树上的虚拟卷轴
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
在第一部分中,我们学习了构建虚拟滚动机制的基本原理、背后的数学原理,并看到了一些伪代码和一些实际代码。
如果你还没读过,我建议你从那里开始,以便了解相关术语:第一部分
在第一部分中,我们假设行高始终是固定的,这大大简化了我们的工作。但是,我们如何支持动态高度呢?我们需要让用户提供高度值,还是可以自动计算?
自动高度调节?
我们先来回答第二个问题。计算未渲染节点的高度并非易事,而且成本很高。这需要将它们渲染到屏幕外,并处理一些潜在的特殊情况,本身就是一个很大的问题。本教程中我们不讨论这个问题。
动态高度
不过,我们可以通过允许用户提供一个返回每行高度的函数来支持动态高度。用户可以离线计算,方法是为每种行类型渲染不同类型的行。
这使得我们的计算更加复杂,因为我们不能直接乘以或除以行高。
计算节点位置
当我们初始化组件时,我们将计算每个节点的位置,这将有助于我们完成第 1 部分中的所有计算步骤。
我们还需要对元素数组的变化做出反应,并在数组发生变化时重新计算整个数组的位置。这通常在现代前端框架中可以实现。
要计算节点的位置,我们取前一个节点的位置,再加上前一个节点的高度。
容器高度
现在我们已经知道了节点的位置,下一步就非常简单了。我们只需取最后一个节点的位置,然后加上它的高度即可。
找出可见节点
为了解决这个问题,我们需要从第一个可见节点开始。现在我们已经计算出了节点的位置,接下来要做的就是找到滚动位置上方最底部的节点。
听起来很简单,但由于节点的位置是动态的,我们不能简单地通过数学计算来定位该节点。
朴素解法
最简单的解决方法是从头开始遍历所有节点,直到找到一个位置大于 scrollTop 的节点。但这显然不是一个好策略。用户滚动页面时,这种计算会非常频繁地执行,因此必须保证极高的性能。
二分查找
由于我们的节点已经排序,我们可以进行二分查找。
二分查找:通过将搜索区间不断二等分来搜索已排序数组。首先,使用覆盖整个数组的区间。如果搜索键的值小于区间中间的元素,则将区间缩小到下半部分;否则,将区间缩小到上半部分。重复此过程,直到找到目标值或区间为空为止。https ://www.geeksforgeeks.org/binary-search/
其优势在于复杂度为 O(log n)。这意味着即使有上百万个节点,也只需要进行大约 20 次比较。
通常,在二分查找中,我们寻找的是特定值。在这里,我们寻找的是滚动位置上方的一个节点,而下一个节点的位置则在其下方。
找到第一个节点后,我们像在第 1 部分中那样减少节点填充。
现在,为了确定可见节点的数量,我们只需添加节点,直到节点位置大于scrollTop +视口高度,然后加上内边距。
向下移动节点
由于我们已经知道节点的位置,因此我们的offsetY就是第一个节点的位置。
瞧!
就这样,我们得到了与第一部分相同的数字,我们可以渲染可见节点并向下移动它们。
性能优化
您可能已经意识到,进行所有这些计算会消耗大量资源。
滚动事件会在用户滚动页面时快速触发,我们需要确保不会进行过多的额外计算,否则用户界面可能会变得卡顿。
大多数显示器使用 60fps 的帧率,以高于此速度重新计算只会浪费资源。
节流
实现这一目标的一种方法就是限流。
节流允许我们控制函数的调用频率。例如,如果一个事件每毫秒触发一次,我们希望回调函数每 17 毫秒处理一次事件,并忽略中间的事件。
因此,您可以将滚动事件回调限制在 17 毫秒,并确保最后一个事件(尾部)也得到处理。
请求动画帧
我更喜欢使用 requestAnimationFrame。
`window.requestAnimationFrame()` 方法告诉浏览器您希望执行动画,并请求浏览器在下次重绘之前调用指定的函数来更新动画。该方法接受一个回调函数作为参数,该回调函数将在重绘之前被调用。更多信息:https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
这将确保你的计算以 60fps 的帧率运行。这意味着滚动事件需要在 requestAnimationFrame 中运行计算。但是如何避免在同一个动画帧中注册多个回调呢?你可以简单地取消之前的回调(如果存在),然后请求另一个回调。
例子
这里有一个使用 React、二分查找和虚拟滚动实现动态高度的示例,其中使用了 React 和 Hooks:
https://codesandbox.io/s/virtual-scroll-dynamic-heights-using-hooks-6gmgu
树上的虚拟卷轴
我开发过的最复杂的功能之一是树状结构的虚拟滚动。树状结构增加了另一个复杂性,即每一层都可以展开或折叠,而且树的遍历是嵌套的。
解决这个问题的一种方法是将树扁平化。这意味着每次展开或折叠节点时,都需要从扁平数组中插入或删除节点,并重新计算节点的位置。这样,虚拟滚动条的行为就和列表上的普通虚拟滚动条一样了。
另一种方法(我采用的方法)是根据树的当前状态(哪些树已展开,哪些树已折叠)来计算节点位置。遍历树以查找可见节点的过程是递归进行的。
您可以在这里查看源代码:
https://github.com/500tech/angular-tree-component/blob/master/lib/models/tree-virtual-scroll.model.ts
谢谢,希望您喜欢这篇分为两部分的博客!
文章来源:https://dev.to/adamklein/build-your-own-virtual-scroll-part-ii-3j86

