19

碰撞检测(边界检测)在前端游戏,以及涉及拖拽交互的场景应用十分广泛。

那么啥叫碰撞?JavaScript 又是如何检测 DOM 发生碰撞的呢?

碰撞,顾名思义,就是两个物体碰撞在了一起,眼睛是可以直观的观察到碰撞的发生。但对于前端实现,如何让 JavaScript 代码理解两个独立的“物体”(DOM)碰撞在一起呢。这就涉及到碰撞检测(或者叫边界检测)的问题了。

碰撞检测的常见需求场景:

前端常见的的碰撞,我们可以粗略的分为几下几类:

两个矩形块的碰撞:

判断任意两个(水平)矩形的任意一边是否有间距,从而得之两个矩形块有没有发生碰撞。具体实现方式,可以选定一个矩形为参照物,计算另一矩形的与自己相近的边是否发生重合现象。若四边均未发生重合,则未发生碰撞,反之则发生碰撞。

图形示例:

clipboard.png

简单算法实现(非碰撞情况,else 分支就是碰撞情况):

    if( domA.left > domB.right 
        || domA.top > domB.bottom
        || domA.right < domB.left
        || domA.bottom < domB.top )
    {
        return false // 未碰撞
    } else {
        return true // 碰撞
    }
     

圆形与圆形的碰撞:

判断任意两个圆形碰撞比较简单,只需要判断两个圆的圆心距离是否小于两圆半径之和,如果小于半径和,就可以判定两个圆发生碰撞。

图形示例:

clipboard.png

简单算法实现

    let distance = Math.sqrt(Math.pow(x1 - x2) + Math.pow(y1 - y2) )
    if (distance < r1 + r2) { // r1、r2 分别为两圆的半径
        return true // 发生碰撞
    } else {
        return false //未发生碰撞
    }

圆形与矩形块的碰撞:

圆形与矩形发生碰撞的要点则是要找出矩形上距离圆心最近的点,然后通过判断该点与圆心的决定是否小于圆的半径,若小于则为碰撞。

点的位置,可以通过获取矩形某一个特定的定点坐标结合矩形宽高与圆的圆心进行比较确定。基本原理与两个矩形碰撞相似,但是略有差异,因为圆形有直接与矩形顶点碰撞的情况。

如下图所示,与矩形碰撞的区别在于,多出了处理矩形宽高延长线交叉区域的情况。当圆心位于两条平行边线之间时:矩形上与圆心最近的点为圆心垂直与矩形最近边线的交叉点;当圆心位于两条交叉边线之间时(如下图斜线区域),与圆心最近的点则是矩形交叉边线的顶点:

clipboard.png

简单算法实现(B 圆只是做辅助说明)

    // 假设 矩形为 Box, 圆的半径为 R
    let distance
    if (x1 < Box.left && y1 > Box.top && y1 < Box.bottom) {
        // 位于上图中 圆 A 的 位置
        distance = Box.left - x1
    } else if ( x1 > Box.Right && y1 < Box.top ) {
        // 位于 上图中 圆 B 的位置
        distance = Math.sqrt(Math.pow(x1 - Box.right) + Math.pow(y1 - Box.top))
    }
    // 其他几种情况类似...按着顺/逆时针的顺序,总共八个区域就能全覆盖
    
    if (distance < R) {
        return true // 碰撞
    } else {
        return false // 未碰撞
    }

还有一种涉及到多 DOM 碰撞的情况,一般业务需求是动态生成 DOM,占位,移动,然后再生成新 DOM,使他们不可重叠。通过查资料发现 地图碰撞 算法比较适合这样的场景。

地图碰撞算法

地图碰撞算法 主要是应用了矩阵的思想。即将拖拽区域进行数据化分割,划分成一个假想的数据地图,每一个小块算是一个独立的位置格子,通过标记状态来确定小格子是否被占用。然后应用最简单的 矩形碰撞 来判定拖动的 DOM 是否与格子发生碰撞,发生则将格子状态改为 1,未被占用则标记为 0。只有未被占用的格子可以被占用,占用的格子,释放后标记为 0。这种状态管理的方式,很适合结合 React、Vue 等前端框架做一些拖拽、碰撞的复杂业务交互。

图形示例:

clipboard.png

简单思路实现

 // 区域是否可用 标记 矩阵
 let map = [
        [0, 1, 0, 0],
        [0, 0, 0, 0]
    ]
 // 设置初始位置
 let NewDom = { left: 0, top: 0}
 // 判断是否可安置
 ...

以上是比较主流的几种碰撞情况,以及主要的思路实现。目前业务有遇到碰撞需求,所以抽时间整理了下。圆形和矩形碰撞的情况感觉还可以优化下,如果有好的思路,可以互相学习下。


mit
243 声望11 粉丝

把喜欢的事儿一件件的做到极致……