磁贴布局三部曲:功能分析、实现分析、性能优化的第一部 - 功能分析。

因为需要做自由布局与磁贴布局混排,以及磁贴布局嵌套,所以要实现一套磁贴分析功能,所以本系列不是简单的介绍使用 react-grid-layout 这个库就行了,而是深入分析磁贴布局的特性,以及重头实现一遍。

对磁贴布局不熟悉的话,react-grid-layout 也是个很好的 Demo 体验页,大家可以先体验一下再阅读文章。

精读

简单碰撞

磁贴布局最重要的就是碰撞了,用过 Demo 就会发现,磁贴左右不会碰撞,只有上下会产生碰撞,这是因为网页天然是从上而下阅读的,因此垂直碰撞比水平碰撞更自然。

那么垂直的碰撞方向是什么样的呢?实际上只有自上而下的碰撞,没有自下而上的碰撞

为了讲清楚这个原理,先看下面的例子:

[-----]   [-----]
|  A  | → |  B  |
[-----]   [-----]

如上所示,将方块 A 移动到方块 B 的位置,如果此时 A 的 Y 轴位置小于等于 B,则会将 B 挤下去。结果如下所示:

[-----]|  A  |[-----][-----]|  B  |[-----]

如果 A 的 Y 轴位置比 B 大,则碰撞结果是 A 跑到了 B 的下面:

[-----]
[-----]   |  B  |
|  A  | → [-----]
[-----]

结果如下所示:

[-----]|  B  |[-----][-----]|  A  |[-----]

如果 A 挤到 B 和 C 中间会如何呢?见下图:

[-----]
[-----]   |  B  |
|  A  | → [-----]
[-----]   [-----][  C  ][-----]

很容易想到,A 会落到 B 与 C 的中间位置。那问题来了,实现的时候,当时 A 放到 B 的下方,还是认为 A 放到 C 的上方?

乍一看会觉得,这不一样吗?对这个例子来说是的,但对其他例子就不同了。实际上应该始终认为是 A 放到了 B 的下方。原因的话,我们举一个反例就行,假设认为 A 放到了 C 的上方,那么看下面的例子:

[-----]
[-----]   |  B  |
|  A  | → [-----]
[-----]       [    X    ][-----][  C  ][-----]

如上图所示,B 和 C 中间夹了一个狭长的 X,此时 A 拖入 B 和 C 的中间,并未与 X 产生碰撞,那结果一定是 A 落在了 B 的下方。如果落在 C 的上方,A 就悬空了。

所以磁贴布局模式下,组件始终只能落在另一个组件下面,除了 Y 轴为 0 的情况下,可以定到组件上方。

连续碰撞

连续碰撞是指当磁贴布局产生碰撞而导致位置变化后,需要重新调整整体位置,或者继续与其他组件位置产生碰撞的情况,首先看下面这个简单例子:

[-----]
|  A  |
[-----]↓
[-----]
|  B  |
[-----]
[-----]
|  C  |
[-----]

如果把 A 拖动到 B 位置,遵循简单碰撞原理,必须 Y 轴高于 B 的 Y 轴才会置于 B 下方,此时会把 C 顶上去。但仅做到这一步,A 原来的位置会产生空缺,需要重新吸附到顶部,这就是连续碰撞:

[     ]                    [-----]
|Empty|                    |  B  |
[     ]                    [-----]
[-----]                    [-----]
|  B  |                    [  A  ]
[-----] → Remove Empty     [-----]
[-----]                    [-----]
|  A  |                    [  C  ]
[-----]                    [-----]
[-----]
|  C  |
[-----]

这时候你可能会想,结果不就是 B 和 A 交换了位置嘛,实际上用 react-grid-layout 看起来效果也是如此,那么代码实现时是不是不用这么麻烦?直接判断 A 与 B 是否产生位置交换,如果交换了,按照交换的方式处理不就行了吗?

听上去很美好,因为按照 A 与 B 交换的思路处理效果一致,而且性能更优,因为不用重新计算 C 组件被挤走,然后 A、B、C 再重新挤上去。但实际上交换方案是不可行的,我们看下面的例子:

[-----]|  A  |[-----]↓
[-------------]
|      B      |
[-------------]
[-----]
|  C  |
[-----]

如果把 A 和 B 位置交换,会发现 C 悬空了。之所以上面的例子可以用交换思路,是因为 A 与 B 交换后,A 还可以 “挡住” C 的上移。但这个例子因为 B 很长,但 A 很短,A、B 交换后,A 就挡不住 C 的上移了:

[-------------]
|      B      |
[-------------]
[-----] [-----]
|  C  | |  A  |
[-----] [-----]

所以为了保证任何时候位移都不会产生 BUG,需要老老实实的分两步来判断:1. 判断 A 移到 B 的底部。2. 新的 A 把下面组件挤走,同时如果上面还有空位置,需要整体向上位移。

看起来还是比较消耗性能的,但通过一些优化手段是可以极大减少计算量的,我们到系列的 “性能优化” 部分再说。

碰撞边界 case

我们再考虑两个极端情况,第一种是要碰撞的组件过于矮的时候,第二种是要碰撞的组件过高的时候。

首先是过矮的情况,我们看下面 5 种情况:

[-----]
|     | ← [ A ]
|  B  |
|     |
[-----]
[-----]
|     |
|  C  |
|     |
[-----]

上面的情况插入到 B 的上方(假设 B 上方没有元素了,如果有的话,假设 B 上方为 X,那么应该认为 A 插入到 X 的底部)。

[-----]
|     |
|  B  |
|     | ← [ A ]
[-----]
[-----]
|     |
|  C  |
|     |
[-----]

上面的情况插入到 B 的下方。

[-----]
|     |
|  B  |
|     |
[-----]
[-----]
|     | ← [ A ]
|  C  |
|     |
[-----]

上面的情况插入到 B 的下方。

[-----]
|     |
|  B  |
|     |
[-----]
[-----]
|     |
|  C  |
|     | ← [ A ]
[-----]

上面的情况插入到 C 的下方。

[-----]
|     |
|  B  |
|     |   [---]
[-----] ← [ A ]
[-----]   [---]
|     |
|  C  |
|     |
[-----]

上面的情况和简单碰撞里提到的例子一样,碰撞位置在 B 与 C 之间,还是会认为插入到 B 的下方。

总结一下,过矮的情况下很多时候拖动组件只会与一个组件产生碰撞,当拖拽中心点在碰撞组件中心点上方时,插入到碰撞组件上方的组件下面(如果上方没有组件则插入到顶部)。当然插入到上方组件下面也不是真的找到上方组件是什么,具体如何做我们等到【实现分析】篇再讲。反之,如果中心点相对在下方,就插入到碰撞组件的下方。如果同时碰撞了多个组件,则忽略中心点偏移量靠上的碰撞,仅考虑中心点偏移量靠下的碰撞。

关于中心点上方其实也可以进一步优化,比如当目标碰撞组件太长的时候,可能比较难移到下方,此时在还没有拖拽到中心点下方时就要做下方碰撞判定了,此时判断依据可以优化为:碰撞时,拖拽组件的 Y 只要比目标组件的 Y 大(或者再加一个常数阈值,该阈值由拖拽组件高度决定,比如是高度的 1/3),那么就认为拖入到目标组件底部,比如:

[-----]
|     |   [---]
|     | ← [ A ]
|  B  |   [---]
|     |
|     |
[-----]

如上图所示,虽然 A 的中心点在 B 中心点上方,但因为 A.y - B.y > A.height / 3,所以判定插入到 B 的下方。当然这也会导致拖入超高组件上方很困难,所以要不要这么设定看用户喜好。

再看组件过高的情况:

[---]
[-----]   [   ]
|  B  | ← [ A ]
[-----]   [   ]
[-----]   [---]
|  C  |
[-----]

上面的情况插入 B 的上方(如果 B 上面还有组件 X,则判定为插入该 X 下方)。

[-----]   [---]
|  B  |   [   ]
[-----] ← [ A ]
[-----]   [   ]
|  C  |   [---]
[-----]

上面的情况插入 B 的下方。

[-----]
|  B  |
[-----]
[-----]   [---]
|  C  |   [   ]
[-----] ← [ A ][   ][---]

上面的情况插入 C 的下方。

总结一下,当拖拽组件过高时,还是维持中心点判断规则,但更可能同时碰撞到多个组件,此时沿用 “中心点偏移量靠上的碰撞” 的原则就行了。但这里有一个较大的区别,拖拽组件矮的时候最多同时和两个组件碰撞,但拖拽组件高的时候,可能同时和 N 个组件碰撞,如下图所示:

[-----]   [---]
|  B  |   [   ]
[-----]   [   ]
[-----]   [   ]
|  C  |   [   ]
[-----] ← [ A ]
[-----]   [   ]
|  D  |   [   ]
[-----]   [   ]
[-----]   [   ]
|  E  |   [---]
[-----]

此时就要看和哪个组件碰撞的优先级最高了。我们单从 B、C、D、E 的角度看,A 分别应该放在 B 下方、C 下方、D 上方、E 上方,其中 B 下方与 C 上方是同一个位置,但与 D 上方、E 上方都不是同一个位置,此时就要看拖拽到哪个位置产生的位移最小了,因为最小的位移是最不突兀的,最符合用户的预期。

另一个边界情况就是拖拽组件过高时,如果中心点还未移动到下方,但高度却超出了下面组件下方,也要视为拖拽到下方:

[-----]
|     |
|     |
|     |
|  A  |
|     |
|     |
|     |
[-----]↓
[-----]
|  B  |
[-----]

如上图所示,A 非常高,B 很矮,当 A 往下移动时,可能 A 的底部都超出 B 底部了(可以优化为 B 的中间),但 A 的中心点仍然在 B 中心点上方,此时在用户已经认为可以交换位置了,所以判断是否移动到下方多了一个优先判断条件:拖拽组件底部超出目标组件底部。同理拖拽到上方也类似。

要注意的是,这个例子与下面的例子表现并不一致,下面的例子 A 向左移时,应该放置 B 的上方,而上面的例子却放置 B 的下方:

[-----]|     ||     ||     |← |  A  |
[-----]   |     |
|  B  |   |     |
[-----]   |     |[-----]

发现了吗?单从垂直位置来看,都是 A 的底部超过了 B 底部,但有时候和 B 互换,有时候却不互换。区分方法就是该碰撞发生时,这两个区块是否已经发生过碰撞。如果未发生过碰撞则严格根据中心点偏移量判断,偏移量靠上则放在上方,反之下方;已经处于碰撞状态则根据顶部或底部判断,顶部超出目标中心点则放上方,底部超出目标中心点则放下方。

碰撞边界与静态区块

如果没有静态组件,碰撞边界就只有容器顶部。加上静态组件后,产生位移时要判断加上一段位移是否会把静态组件挤走,如果会挤走,则该拖拽位置无效。

固定步长

磁贴布局为了方便对齐,往往会把父容器切割为 12 或者 6 等分,此时拖拽位置就不会完全跟手,当拖拽没有超过临界点的时候,实际拖拽位置不会跟随移动。

总结

磁贴布局的功能主要聚焦在组件间碰撞逻辑上,目标是让用户能够自然的布局,所以组件间碰撞逻辑也要尽可能自然,符合直觉。

讨论地址是:精读《磁贴布局 - 功能分析》· Issue #458 · dt-fe/weekly

如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)

精读《磁贴布局 - 功能分析》相关推荐

  1. 【韩松】Deep Gradient Comression_一只神秘的大金毛_新浪博客

    <Deep Gradient Compression> 作者韩松,清华电子系本科,Stanford PhD,深鉴科技联合创始人.主要的研究方向是,神经网络模型压缩以及硬件架构加速. 论文链 ...

  2. 【韩松】Deep Gradient Comression

    <Deep Gradient Compression> 作者韩松,清华电子系本科,Stanford PhD,深鉴科技联合创始人.主要的研究方向是,神经网络模型压缩以及硬件架构加速. 论文链 ...

  3. [文献阅读] Sparsity in Deep Learning: Pruning and growth for efficient inference and training in NN

    文章目录 1. 前言 2. Overview of Sparsity in Deep Learning 2.1 Generalization 2.2 performance and model sto ...

  4. 【翻译】Batch Normalization: Accelerating Deep Network Trainingby Reducing Internal Covariate Shift

    Batch Normalization: Accelerating Deep Network Trainingby Reducing Internal Covariate Shift Sergey I ...

  5. 模型加速--CLIP-Q: Deep Network Compression Learning by In-Parallel Pruning-Quantization

    CLIP-Q: Deep Network Compression Learning by In-Parallel Pruning-Quantization CVPR2018 http://www.sf ...

  6. 论文笔记30 -- (视频压缩)【CVPR2021】FVC: A New Framework towards Deep Video Compression in Feature Space

    <FVC: A New Framework towards Deep Video Compression in Feature Space> CVPR 2021 的一篇Oral 提出了特征 ...

  7. 端到端图像压缩《Asymmetric Gained Deep Image Compression With Continuous Rate Adaptation》

    Asymmetric Gained Deep Image Compression With Continuous Rate Adaptation 一 简介 二 内容 2.1 目前方法的缺陷 2.2 整 ...

  8. 深度学习视频压缩1—DVC: An End-to-end Deep Video Compression Framework

    本文是第一篇端到端使用神经网络来进行视频压缩的论文, github地址:GitHub - GuoLusjtu/DVC: DVC: An End-to-end Deep Video Compressio ...

  9. 【论文阅读】Deep Compositional Captioning: Describing Novel Object Categories without Paired Training Data

    [论文阅读]Deep Compositional Captioning: Describing Novel Object Categories without Paired Training Data ...

  10. CVPR 2018 TRACA:《Context-aware Deep Feature Compression for High-speed Visual Tracking》论文笔记

    理解出错之处望不吝指正. 本文的模型叫做TRACA.模型中使用多个expert auto-encoder,在预训练阶段,每个expert auto-encoder针对一个特定类进行训练:在tracki ...

最新文章

  1. UICollectionView
  2. druid抛出的异常------javax.management.InstanceAlreadyExistsException引发的一系列探索
  3. 去中心化交易所前路明朗,基于EOS的去中心化交易所力拔头筹
  4. python PyQt5 slot插槽(pyqtSignal、pyqtSlot)
  5. 集成算法——Adaboost代码
  6. servlet实现新闻控制
  7. sendmail 常见报错总结
  8. mac php gd(mac osx 10.9.4)
  9. 为什么黑客都用python-python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)...
  10. 镇魂街武神躯怎么修改服务器,镇魂街武神躯怎么重置守护灵_守护灵重置方法_3DM手游...
  11. mysql.h说明文档,mysql.h:没有文件或目录
  12. 基于C#的房屋租赁管理系统设计与实现
  13. 编程验证足球预测算法的准确概率
  14. 【东华初中编程试题2206】病毒复制 N 分数 快递哥
  15. gcc 编译隐藏符号
  16. 百度翻译API的调用
  17. DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution
  18. 各大网站的主题色RGB值,做icon专用
  19. 乐高机器人纲要_人工智能与机器人课程纲要
  20. 如何隐藏Android模拟器的虚拟按键

热门文章

  1. 回文数,回文字符串的判断
  2. 组合测试法是什么 软件测试,组合测试模型方法
  3. 飞猪 Serverless 体系从无到有,落地10余个业务场景
  4. Incorrect string value: ‘\xE4\xBB\x8E\xE5\x85\xA5...‘ for column ‘detail‘ at row 1
  5. 中国社会为何多犬儒?
  6. 百度地图线路查询路线样式自定义
  7. jodconverter下载地址
  8. EasyOrtho卫星影像处理软件
  9. 仿真 steam linux 安装教程,Ubuntu安装Steam游戏平台的解决方案
  10. XML文件的操作--上