jQuery/JavaScript 碰撞检测

84

如何检测两个<div>元素是否发生了碰撞?

这两个

是简单的彩色方块,相互垂直移动,没有复杂的形状或角度。


哇,多么漂亮的网页啊。而且这些动画都是纯CSS做的。 :) - Šime Vidas
谢谢,目前它还很粗糙,但你可以理解我的意思。当我完成基本功能时,我会美化它。事实证明CSS非常适合关卡设计...类是一种真正简单的分层行为方式。现在我要尝试你的示例代码,谢谢。 - Chris Armstrong
4
警告,该页面会导致 FireFox 12 崩溃。JavaScript 会卡死并永远不会询问是否停止脚本。 - Potatoswatter
作为游戏提示,您可能希望禁用“Page Down”和滚动条。 - Andrew
可能是Javascript:碰撞检测的重复问题。 - Pavlo
这很有趣,像这样的问题在今天的stackoverflow上会被关闭。 - Huangism
7个回答

74

var overlaps = (function () {
    function getPositions( elem ) {
        var pos, width, height;
        pos = $( elem ).position();
        width = $( elem ).width();
        height = $( elem ).height();
        return [ [ pos.left, pos.left + width ], [ pos.top, pos.top + height ] ];
    }

    function comparePositions( p1, p2 ) {
        var r1, r2;
        r1 = p1[0] < p2[0] ? p1 : p2;
        r2 = p1[0] < p2[0] ? p2 : p1;
        return r1[1] > r2[0] || r1[0] === r2[0];
    }

    return function ( a, b ) {
        var pos1 = getPositions( a ),
            pos2 = getPositions( b );
        return comparePositions( pos1[0], pos2[0] ) && comparePositions( pos1[1], pos2[1] );
    };
})();

$(function () {
    var area = $( '#area' )[0],
        box = $( '#box0' )[0],
        html;
    
    html = $( area ).children().not( box ).map( function ( i ) {
        return '<p>Red box + Box ' + ( i + 1 ) + ' = ' + overlaps( box, this ) + '</p>';
    }).get().join( '' );

    $( 'body' ).append( html );
});
body {
    padding: 30px;
    color: #444;
    font-family: Arial, sans-serif;
}

h1 {
    font-size: 24px;
    margin-bottom: 20px;
}

#area {
    border: 2px solid gray;
    width: 500px;
    height: 400px;
    position: relative;
}

#area > div {
    background-color: rgba(122, 122, 122, 0.3);
    position: absolute;
    text-align: center;
    font-size: 50px;
    width: 60px;
    height: 60px;
}

#box0 {
    background-color: rgba(255, 0, 0, 0.5) !important;
    top: 150px;
    left: 150px;
}

#box1 {
    top: 260px;
    left: 50px;
}

#box2 {
    top: 110px;
    left: 160px;
}

#box3 {
    top: 200px;
    left: 200px;
}

#box4 {
    top: 50px;
    left: 400px;
}

p {
    margin: 5px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<h1>Detect overlapping with JavaScript</h1>
<div id="area">
    <div id="box0"></div>
    <div id="box1">1</div>
    <div id="box2">2</div>
    <div id="box3">3</div>
    <div id="box4">4</div>
</div>

总体思路-获取框的偏移和尺寸,并检查它们是否重叠。

如果要更新,可以使用setInterval

function detectOverlapping() {
    // code that detects if the box overlaps with a moving box
    setInterval(detectOverlapping, 25);
}

detectOverlapping();  

此外,请注意您可以针对特定示例优化该函数。
  • 您不必反复阅读盒子尺寸(就像我在代码中所做的那样),因为它们是固定的。您可以在页面加载时将它们读入变量,然后只需读取变量即可。
  • 小框的水平位置不会改变(除非用户调整窗口大小)。汽车框的垂直位置也不会改变。因此,这些值也不必反复读取,而是可以存储到变量中。
  • 您不必始终测试小框是否与所有汽车框重叠。您可以根据其垂直位置确定框当前位于哪个车道,并仅测试该车道的特定汽车框。

看起来很不错,现在想在我的样本上测试一下...我该如何让它每帧更新一次? - Chris Armstrong
1
@Chris 集中注意力检测基于"player"垂直位置的哪个“car”需要测试。你有15辆车,每次测试所有15辆车比仅测试可能与玩家重叠的一辆车慢了15倍。 - Šime Vidas
抱歉耽搁了标记为正确的时间,感谢您的帮助!我在让它正常工作后会在这里发布一个链接。 - Chris Armstrong
4
这是一把非常好的小提琴,但你认为第4行使用$.offset()而不是$.position()更好吗?这样就可以检查DOM中所有元素的碰撞,而不仅仅是同级元素。 - Steven Lu
如果你的元素具有padding或border,则需要使用.outerWidth().outerHeight()替换.width().height(),否则计算结果会出错。 - SeinopSys
显示剩余6条评论

17

我认为这是最简单的方法:https://plugins.jquery.com/overlaps/

这里还有另一个德语版本:http://www.48design.de/news/2009/11/20/kollisionsabfrage-per-jquery-plugin-update-v11-8/

你可以试试这些。

--更新--

我现在实在没有时间去处理它,但如果没有人回答你的话,我回家后可以帮你做一些事情:

setInterval(function(){
            //First step would be to get the offset of item 1 and item 2
            //Second would be to get the width of each
            //Third would be to check if the offset+width ever overlaps
                //the offset+width of the 2nd
            //Fourth would be, if so, do X or set a class...
        },10);

谢谢!有了这个可碰撞的对象,我是否必须使对象可拖动才能让它起作用? - Chris Armstrong
糟糕,你知道吗,我认为它可以,但是......你可能可以使用jQuery UI将其设置为可拖动(我认为它正在使用这个),然后在对象上设置返回false以使其不可点击(因此不可拖动),但碰撞仍然会起作用。 - Oscar Godson
1
你知道吗,你也可以(而且可能更容易)...我正在更新我的答案。 - Oscar Godson

10
你可以使用 getBoundingClientRect() 来实现此功能。
function isOverlapping(div1, div2){
    const div1 = div1.getBoundingClientRect();
    const div2 = div2.getBoundingClientRect();
    return (div1.right > div2.left && 
            div1.left < div2.right && 
            div1.bottom > div2.top && 
            div1.top < div2.bottom)
}

7
这可能有点晚了,但我想当我面临类似情况时尝试过这种方法。优点在于没有额外的插件或脚本,也不需要引入性能消耗大的轮询。
此技术利用了 JQuery droppable 提供的内置方法和事件。
好的,言归正传,以下是解决方案技巧:
比如你有两个元素(我的情况下是图片),你不希望它们重叠或者检测到重叠,把这两个元素作为droppable,并使它们'accept'对方即可。
$([div1, div2]).droppable(CONFIG_COLLISSION_PREVENTION_DROPPABLE);

“CONFIG_COLLISSION_PREVENTION_DROPPABLE”看起来像这样:
var originatingOffset = null;
CONFIG_COLLISSION_PREVENTION_DROPPABLE = {
    tolerance: "touch",
    activate : function (event, ui) {
        // note the initial position/offset when drag starts
        // will be usedful in drop handler to check if the move
        // occurred and in cae overlap occurred, restore the original positions.
        originatingOffset = ui.offset;
    },
    drop : function (event, ui) {
            // If this callback gets invoked, the overlap has occurred. 
            // Use this method to either generate a custom event etc.

            // Here, i used it to nullify the move and resetting the dragged element's 
            // position back to it's original position/offset
            // (which was captured in the 'activate' handler)
        $(ui.draggable).animate({
            top: originatingOffset.top + "px",
            left: originatingOffset.left + "px"
        }, 300);
     }
}

'activate'和'drop'处理程序是指“droppable”插件的'dropactivate'和'drop'事件。
这里,关键是'drop'回调。每当两个元素中的任何一个重叠并且它们被彼此放下时,将调用'drop'。这是检测并采取行动的地方,可以发送自定义事件或调用其他操作(我选择在'activate'回调中捕获的拖动开始时将重叠元素的位置恢复到初始位置)。
就是这样。没有轮询,没有插件,只有内置事件。
好吧,还可以对其进行其他优化/扩展,这只是我头脑中的第一个有效方法 :)
您还可以使用'dropover'和'dropout'事件来向用户发出信号并创建视觉反馈,表明两个元素正在重叠,虽然它们可能仍在移动中。
var CLASS_INVALID = "invalid";
// .invalid { border: 1px solid red; }
...
$.extend(CONFIG_COLLISSION_PREVENTION_DROPPABLE, {
   over : function (event, ui) {
        // When an element is over another, it gets detected here;
        // while it may still be moved.
        // the draggable element becomes 'invalid' and so apply the class here
        $(ui.draggable).addClass(CLASS_INVALID);
    },
    out : function(event, ui) {               
         // the element has exited the overlapped droppable now
         // So element is valid now and so remove the invalid class from it
         $(ui.draggable).removeClass(CLASS_INVALID);
    }
});

希望这能帮到你!

重要提示:还要注意,首先应该使这两个div可拖动。 - Murtaza H
我一直在寻找这样的东西!试图避免使用游戏循环,也不想引入轮询(就像你提到的那样)。谢谢! - Chris Dolphin

5

编辑:我在我的网站上写了一篇博客文章,这是链接。 http://area36.nl/2014/12/creating-your-own-collision-detection-function-in-javascript/

我也遇到了同样的问题,但幸好有Oscar Godson的答案,我得到了一个可行的函数。我使用Jquery进行编码,因为我比较懒;p。我把这个函数放在另一个每秒钟触发一次的函数中,所以请记住这一点。

function collidesWith (element1, element2) {
    var Element1 = {};
    var Element2 = {};

    Element1.top = $(element1).offset().top;
    Element1.left = $(element1).offset().left;
    Element1.right = Number($(element1).offset().left) + Number($(element1).width());
    Element1.bottom = Number($(element1).offset().top) + Number($(element1).height());

    Element2.top = $(element2).offset().top;
    Element2.left = $(element2).offset().left;
    Element2.right = Number($(element2).offset().left) + Number($(element2).width());
    Element2.bottom = Number($(element2).offset().top) + Number($(element2).height());

    if (Element1.right > Element2.left && Element1.left < Element2.right && Element1.top < Element2.bottom && Element1.bottom > Element2.top) {
        // Do your stuff here
    }
}

它的作用基本上是获取所有element1的值,然后获取所有element2的值。然后通过一些计算来计算出所有的值。然后在if语句中,它将比较element1的平方和element2的平方。如果element1的值介于element2的左、右、上和下值之间,则执行底部的代码。

请您能否提供一个例子?比如一个JS fiddle? - Andy

3

我自己也遇到了这个普遍问题,所以(完全公开透明)我为此编写了一个插件。对于静态对象的简单碰撞查询,请尝试使用以下内容:

http://sourceforge.net/projects/jquerycollision/

这个插件可以让你获取一组重叠的碰撞框(如果没有碰撞则返回空):

hits = $("#collider").collision(".obstacles");

或者如果你想在“拖动”时获取碰撞事件,请使用以下内容:

http://sourceforge.net/apps/mediawiki/jquidragcollide/?source=navbar#collision

这个插件提供了一个“碰撞”事件,你可以将其与代码连接起来。(或者一个“突出”事件,以查看一个 div 是否跳出当前包含它的另一个 div)

$(draggable).bind( 
   "collision",
   function(event,ui) {
      ...
   }
);

如果您在运动过程中检查碰撞,请重复调用原始碰撞检查函数,这非常快速。请注意:与调整大小不兼容。

你好,你有没有一个关于拖动时碰撞检测的例子?我试过了但是没能做到当一个div接触到另一个div时停止而不重叠。在突出部分,我会使用mouseup事件,这样就不能再将其拖到外面了 - 我需要在突出之前执行mouseup事件。 - alex toader

1

这篇帖子已经有些年头了,但也许对某些人还有所帮助...

function CheckDiv()
{
var ediv1 = document.getElementById('DIV1');
var ediv2 = document.getElementById('DIV2');

 ediv1.top = $(ediv1).offset().top;
 ediv1.left = $(ediv1).offset().left;
 ediv1.right = Number($(ediv1).offset().left) + Number($(ediv1).width());
 ediv1.bottom = Number($(ediv1).offset().top) + Number($(ediv1).height());

 ediv2.top = $(ediv2).offset().top;
 ediv2.left = $(ediv2).offset().left;
 ediv2.right = Number($(ediv2).offset().left) + Number($(ediv2).width());
 ediv2.bottom = Number($(ediv2).offset().top) + Number($(ediv2).height());

if (ediv1.right > ediv2.left && ediv1.left < ediv2.right && ediv1.top < ediv2.bottom && ediv1.bottom > ediv2.top)
 {
alert("hi");
}

if (ediv1.left > ediv2.left && ediv1.top > ediv2.top && ediv1.right < ediv2.right && ediv1.bottom < ediv2.bottom)
 {
alert("hello");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接