当前位置: 首页 > Web前端 > HTML

精读《磁贴布局 - 性能优化》

时间:2023-03-28 17:45:10 HTML

经过上一篇精读《磁贴布局 - 功能实现》的介绍,这次我们进入性能优化环节。优化精读tile布局性能的方法有很多,比如用空间换时间,存储父子关系索引,从而快速找到目标组件。但是有一个核心的性能优化点,就是碰撞性能优化。试想一下,判断组件碰撞最简单的方法是什么?一般会遍历画布的所有组件,根据当前组件位置和目标组件位置的相对位置来判断是否发生碰撞,所以只判断单个组件发生碰撞时,时间复杂度为在)。但是tile布局的碰撞判断涉及到整个canvas,因为一个组件的移动可能会引起另一个组件的移动,形成一系列的链式布局变化,比如下面的情况:[---][][A][]↑[---][--------][B][--------][---][C][---][-------][D][------]例如B向上移动,每个组件在下降时必须独立判断。由于最终的碰撞结果难以预料,只能逐个组件判断。比如上面的例子,结果如下:[--------][B][--------][---][---][C][][---][A][][---][--------][D][------]可以看出D本来就接近C,但是因为A组件已经下移,A比C高,所以靠近D的组件由C变成了A。在C进行独立的碰撞判断之前,很难分析画布的结构,更不用说结合画布的整体尺寸缩放,网格数量变化的影响,以及组件的最终落点必须在每个组件以正确的顺序碰撞后确定。因此,瓦片碰撞的时间复杂度为O(n2)。比如页面中有100个组件,至少要遍历10000次才能完成一次布局计算。极端情况下,比如页面有1000个组件,Layout计算肯定是很耗时的。考虑网格碰撞确定方法的另一个问题。正是因为tile布局的碰撞判定,使得tile布局不可能重叠组件。因此,即使canvas上有1000个组件,只要组件宽高不是特别小(比如每个宽1px高1000px的A组件是不可能聚集在一个小区域的,但是分散在一个大的范围内。那么离当前组件太远的组件根本不需要做碰撞判断,因为它们没有可能相交。类比人的视角判断碰撞,当canvas有1000组件,我们也可以一眼看出某个组件与哪些组件相交,但这种判断来自于肉眼扫过可见区域,而不是1000个组件。检查所有组件。这表明人眼对碰撞优化:以这个组件为圆心,上下左右扩大一定范围就可以了,看是否有碰撞。因此,我们模拟人眼的思路es寻找碰撞,将画布分成若干个格子,记录每个组件所在的格子,这样判断碰撞时,我们只需要在组件所在的格子里进行判断即可。画布分为如下几个格子:[---]││││[A]││││[---]││││────────────────────────────────────────────────────────———————————————││[b]│[---]│[│-----][C]│[G]││──────────┼──────────┼────[---]┼──────────┼──────────││[E]│[F]│││[-----------]│││[]│──────────┼──────────┼────[D]──┼──────────││[]│││[----------]│││││so当确定以下组件发生碰撞时,待比较的组件如下:A:没有被比较的组件。B:比较组件C。D:比较组件E、F和G。由于一个区域承载的组件数量是固定的,所以O(n2)时间复杂度优化为O(nxP),其中P是常数每个组件,所以时间复杂度最终为O(n)。当然,这里有几点需要注意:需要用空间换取时间,即存储每个组件属于哪些区域,每个区域有哪些组件,这样拖拽的时候就不需要遍历所有组件了下降。网格的大小不宜太大。如果格子太大,划分格子的意义就不会太大,因为一个格子里面的组件还是很多的。网格的尺寸不能太小,这样每个组件可能跨越很多网格,导致网格本身的数量循环次数甚至超过组件树,成为负优化。关于网格的大小,一般tile布局都会设置colsrowHeight两个选项。将这两个选项的正整数倍的网格设置为跨度比较合适,这样会尽量减少网格的无效区域。不同场景下的网格计算我们上面提到了如何使用网格计算来处理组件碰撞。再总结一下:判断组件碰撞,只需要找到当前组件所在的网格区域,遍历每个网格区域中的组件即可。除了碰撞判断之外,还有两个场景需要在瓦片拖动过程中计算组件之间的碰撞关系,主要包括落点位置和落点后组件排序这两个场景。比如下面这个例子:蓝色方框是鼠标拖动组件,鼠标实时位置,红色背景方块表示着陆点位置。红色方块下方的组件属于后置组件。这些组件需要重新计算位置,因为插入了红色方块的位置。为了充分利用栅格优化性能,需要分别判断这两种情况。落点位置由于tile布局的重力是垂直向上的,落点只会落在当前组件的上方,即落点只会与上方的组件发生碰撞,所以考虑垂直向上的网格区域。而且这个过程还是可以优化的,就是一个一个往上搜索,只要在某个cell中找到碰撞分量,就可以终止搜索:[---]││[A]││[---]...──────────────────────────────[----------------------[c]││[--------]││────────────────────────────--------------------[d]│[---------------------如上例,移动D时:首先考虑D所在区域是否有可以碰撞的组件垂直区域,因为D所在区域只有自己,所以跳过。考虑到D区域上方一格的区域,找到了组件C,它可以在垂直位置与D发生碰撞,所以D的着陆点位于C下方。搜索结束后,直接跳过向上区域。因此,寻找落点位置的时间复杂度为O(1)。落点后的组件排序落点位置确定后,由于落点位置毕竟发生了变化,落点后的组件必须根据瓦片向上的重力重新排序,所以组件搜索范围在这时间包括掉落点所在的区域,垂直向下的所有区域:[---]││[A]││[---]││──────────┼────────────┼────────────[-----]│[B]│[-----]│──────────┼────────────┼────────────[-----]││[C]││[-----]││──────────┼───────────┼───────────[-----]│[D]│[-----]│如上例,移动A时,A所在区域以下的所有区域都要重新判断落点,即B,C,D组件所在的区域。其他地区不受影响。我们假设所有的组件在所有区域都是均匀平铺的,那么在最坏的情况下(移动组件在最上面,那么需要搜索整个高度区域)垂直区域的组件个数为logn,所以时间复杂性理论上面是O(logn)。但一般情况下,tile布局的高度远大于宽度,因此可能会向更差的O(n)复杂度发展,但无论如何,这种线性性能是可以接受的。总结经过优化,tile布局在拖动前、拖动中和拖动后每个阶段的计算复杂度都是O(n),即一个有500个组件实例的复杂画布每次拖动只需要循环500次计算位置,并配合一些空间换时间的Map映射关系,500次计算加起来最多消耗2~3ms,1000个组件实例也最多消耗4~6ms,但是1000多个组件实例的画布是almost是不可能存在的,这里的log(n)的n指的是每个容器中的组件,所以只要单个容器中的组件数量几乎不超过很多,性能上是没有问题的.讨论地址为:精读《磁贴布局 - 性能优化》·第461期·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)