Tile布局三部曲:功能分析、实现分析、性能优化第1部分——功能分析。因为要做freelayout和tile布局混合,tile布局嵌套,所以需要实现一套tile解析功能,所以本系列不是简单介绍react-grid-layout库的使用,而是一个深入分析tile布局的特点,从头开始实施。如果你对tile布局不熟悉,react-grid-layout也是一个不错的demo体验页,可以先体验一下再看文章。精读SimpleCollisiontile布局最重要的就是碰撞。使用demo后,你会发现tile不会左右碰撞,只会上下碰撞。这是因为网页自然是从上到下阅读的,所以垂直碰撞更自然,水平碰撞更自然。那么垂直碰撞方向是什么样的呢?其实只有自上而下的碰撞,没有自下而上的碰撞。为了把这个原理解释清楚,我们来看下面的例子:[-----][-----]|一个|→|乙|[-----][-----]如上图,将块A移动到块B的位置,如果此时A的Y轴位置小于等于B,则B为推下去。结果如下:[-----]|一个|[-----][-----]|乙|[-----]如果A的Y轴位置大于B的Y轴位置,那么碰撞的结果就是A跑到B下面:[-----][-----]|乙||一个|→[-----][-----]结果如下:[-----]|乙|[-----][-----]|一个|[-----]如果A挤在B和C之间会怎样?见下图:[-----][-----]|乙||一个|→[-----][-----][-----][C][-----]很容易认为A会落在B和C的中间。问题是,在实现的时候,是把A放在B下面,还是认为A放在C上面?乍一看你可能会想,这不是不一样吗?对于此示例是的,但对于其他示例则不是。其实总应该认为A放在B的下面。至于原因,我们就举个反例吧,假设A放在C的上面,再看下面的例子:[-----][-----]|乙||一个|→[------][-----][X][-----][C][-----]如上图,中间有一个狭长的XB和C,A拖在B和C的中间,和X没有碰撞,所以结果肯定是A落在B的下面,如果落在C的上面,那么A就是漂浮的。所以在tile布局模式下,一个组件只能落在另一个组件的下方,除非Y轴为0时,才能位于组件上方。连续碰撞连续碰撞是指当tile布局发生碰撞,位置发生变化,需要重新调整整体位置,或者继续与其他组件位置发生碰撞的情况。首先,看下面这个简单的例子:[-----]||[-----]↓[-----]|B|[-----][-----]|C|[-----]如果把A拖到B的位置遵循简单的碰撞原理,Y轴必须高于B的Y轴才能放在B的下方,此时C会被推上去。但是只做这一步,原来A的位置会有一个空位,需要重新吸附到上面,就是连续碰撞:[][-----]|Empty||B|[][-----][-----][-----]|乙|[A][-----]→删除空[-----][-----][-----]|一个|[C][-----][-----][-----]|C|[-----]这时候你可能会认为结果B和A的位置已经互换了。其实和react-grid-layout好像效果是一样的。那么实现代码不是那么麻烦吗?直接判断A和B之间是否有换仓,如果换了,按换仓方式处理不行吗?听起来不错,因为按照交换A和B的思路处理效果是一样的,性能更好,因为不需要重新计算要挤出的C分量,然后A,B,和C又被挤进去了。但实际上交换方案是行不通的,我们看下面的例子:[-----]|一个|[-----]↓[-------------]|B|[-------------][-----]|C|[-----]交换一下A和B的位置,你会发现C被悬空了。上面的例子之所以可以互换,是因为A和B互换后,A仍然可以“挡住”C的向上运动。但是在这个例子中,因为B很长,而A很短,A和B交换后,A无法阻止C向上移动:[------------]|B|[-----------][-----][-----]|丙||A|[-----][-----]所以为了保证任何时候位移都不会有bug,需要老老实实分两步判断:1.判断A有移到B的底部。2.新的A把下面的组件挤走,如果上面还有空间,需要整体向上位移。看起来还是比较消耗性能的,但是通过一些优化的方法,可以大大减少计算量。我们将在本系列的“性能优化”部分中进行讨论。在碰撞边界的情况下,让我们考虑两种极端情况。第一种是被碰撞的组件太短,第二种是被碰撞的组件太高。首先是太短的情况,我们来看以下5种情况:[-----]||←[A]|乙|||[-----][-----]|||C|||[-----]上面的case在B上面插入(假设B上面没有更多的元素,如果有的话,假设X在B上面,那么A应该被认为是插入在X的底部)。[-----]|||乙|||←[A][-----][-----]|||C|||[-----]上面的case插入到B的底部。[-----]|||乙|||[-----][-----]||←[A]|C|||[-----]上面的case插入到B的底部。[-----]|||乙|||[-----][-----]|||C|||←[A][-----]将上面的情况插入到C的底部。[-----]|||乙|||[---][-----]←[A][-----][---]|||C|||[-----]上面的情况和SimpleCollision中提到的例子是一样的。如果碰撞位置在B和C之间,仍然会被认为是插在B下面。综上所述,拖动组件太短时,很多情况下只会碰撞到一个组件。当拖动的中心点在碰撞组件中心点上方时,会插入到碰撞组件上方的组件下方(如果上方没有组件,则插入到顶部)。当然,插在uppercomponent下面并不能真正找出uppercomponent是什么。我们等到【实现分析】一章再说怎么做。反之,如果中心点相对靠下,则会插入到碰撞组件的下方。如果多个组件同时发生碰撞,则忽略与中心点上偏移的碰撞,只考虑与中心点下偏移的碰撞。其实中心点的上半部分还可以进一步优化。例如,当目标碰撞组件过长时,可能难以移动到底部。这时必须在拖动低于中心点之前进行底部碰撞判断。这时候判断的依据可以优化如下:当发生碰撞时,只要拖动组件的Y大于目标组件的Y(或者加一个常量阈值,由高度决定)拖拽组件的大小,比如高度的1/3),则认为拖拽回车到目标组件的底部,例如:[-----]||[---]||←[A]|乙|[---]||||[-----]如上图所示虽然A的中心点在B的中心点上方,但是因为A.y-B.y>A.height/3,所以决定插入在B的下方。当然,这也会导致难以拖入超高组件的顶部,所以是否这样设置取决于用户的喜好。看分量过高的情况:[---][-----][]|乙|←[A][-----][][-----][---]|C|[-----]上面的case插入到B上面(如果B上有组件X,则判断插入到这个X下面)。[-----][---]|乙|[][-----]←[A][-----][]|丙|[---][-----]将上面的情况插入到B下面。[-----]|B|[-----][-----][---]|丙|[][-----]←[A][][---]上面的case插在C下面,综上所述,拖拽分量过高时,仍然保持中心点判断规则,但是它更有可能同时与多个组件发生碰撞。在这种情况下,“与上中心点偏移碰撞”的原则就可以了。但是这里有很大的不同。拖动组件短时,最多同时碰撞两个组件,但拖动组件高时,可能同时碰撞N个组件,如下图:[-----][---]|乙|[][-----][][-----][]|丙|[][-----]←[A][-----][]|D|[][-----][][-----][]|乙|[---][-----]此时要看哪个组件的Collision优先级最高。从B、C、D、E的角度来看,A应该放在B的下面,C的下面,D的上面,E的上面,B下面的位置和C上面的一样,只是在D上面,上面E。不是同一个位置。这时候就要看拖动到哪个位置产生最小的位移,因为最小的位移最不突兀,最符合用户的期望。另一种边界情况是拖动组件过高时,如果中心点没有移到下面,但是高度超过了下面组件的底部,也被认为是被拖动到下面:[-----]|||||||一个|||||||[-----]↓[-----]|B|[-----]如上图,A很高,B很矮。A向下移动时,A的底部可能会超过B的底部(可以优化到B的中间),但是A的中心点还是在B的中心点之上,这时候用户已经想到了可以交换位置,所以判断是否移动到底部有一个额外的优先判断条件:拖动组件的底部超过目标组件的底部。拖动到顶部也是如此。需要注意的是,这个例子和下面的例子是不一致的。下例中A向左移动时,应该放在B的上方,而上面的例子是放在B的下方:[-----]||||||←||[-----]|||乙|||[-----]||[---]找到它?单从垂直位置来看,A底超过B底,但有时与B互换,有时又不互换。区分的方法是碰撞发生时两个块是否已经碰撞。如果没有碰撞,严格按照中心点的偏移量判断。如果偏移量较高,则放在上面,反之亦然;如果已经处于碰撞状态,则根据顶部或底部进行判断。如果顶部超过了目标中心点,就放在上面,如果底部超过了目标中心点,就放在下面。碰撞边界和静态块如果没有静态组件,碰撞边界只是容器的顶部。添加静态分量后,当发生位移时,需要判断添加一定的位移是否会将静态分量挤走。如果会被挤走,则拖动位置无效。为了方便对齐,固定步长的tile布局往往将父容器分成12或6等份。这时候拖动的位置就不会完全跟着手了。当拖动没有超过临界点时,实际拖动的位置会有所不同。会跟随运动。总结tile布局的作用主要集中在组件之间的碰撞逻辑上。目标是让用户自然布局,所以组件之间的碰撞逻辑要尽可能自然,符合直觉。讨论地址为:Jingdu《磁贴布局 - 功能分析》·Issue#458·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)