83

How to detect if two <div> elements have collided?

The two divs are simple coloured boxes travelling perpendicular to each other, so no complicated shapes or angles.

Gajus
  • 69,002
  • 70
  • 275
  • 438
Chris Armstrong
  • 3,585
  • 12
  • 42
  • 47
  • Wow, what a nice web-page. And the animations are pure CSS. :) – Šime Vidas Nov 20 '10 at 01:58
  • thanks, it's rough as anything for now but you get the idea. I'll pretty it up when I get the basics working. Turns out CSS is fantastic for level design... classes are a real easy way to layer behaviours. Gonna try your sample code now thanks – Chris Armstrong Nov 20 '10 at 03:14
  • 4
    Warning, the page crashes FireFox 12. JavaScript hangs and it never asks to stop the script. – Potatoswatter May 20 '12 at 08:44
  • As a tip for your game you might want to disable page down and scroll bar. – Andrew Jun 12 '14 at 02:52
  • Possible duplicate of [Javascript: Collision detection](http://stackoverflow.com/questions/2440377/javascript-collision-detection) – Pavlo Feb 06 '17 at 16:07
  • It's funny, a question like this would get closed in today's stackoverflow – Huangism Feb 15 '18 at 21:42

7 Answers7

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>

General idea - you get the offset and dimension of the boxes and check whether they overlap.

If you want it to update, you can use setInterval:

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

detectOverlapping();  

Also, note that you can optimize the function for your specific example.

  • you don't have to read the box dimensions repeatedly (like I do in my code) since they are fixed. You can read them on page load (into a variable) and then just read the variable

  • the horizontal position of the little box does not change (unless the user resizes the window). The vertical positions of the car boxes does not change. Therefore, those values also do not have to be read repeatedly, but can also be stored into variables.

  • you don't have to test whether the little box overlaps with all car boxes at all times. You can - based on its vertical position - figure out in which lane the box is currently, and test only the specific car box from that lane.

Gajus
  • 69,002
  • 70
  • 275
  • 438
Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • that looks great, trying to test it on my sample now... how would I get it to update every frame? – Chris Armstrong Nov 20 '10 at 03:30
  • Hmm... The Player's position is from (649, 75) to (674, 100) and the car position is from (649, 50) to (749, 100), so they're overlapping, yet the match is returning as false, any idea why this would be? – Chris Armstrong Nov 20 '10 at 03:52
  • @Chris Yea, I just realized that I took into account only 2 of overall 9 scenarios of overlapping. But it should be easy to incorporate all 9 scenarios. Give me a sec. – Šime Vidas Nov 20 '10 at 13:18
  • @Chris Done. I believe it should work now for all possible cases. – Šime Vidas Nov 20 '10 at 13:24
  • Thanks! Yeah looks like I'm going to need to do a lot of optimisation, it's detecting the collisions but framerate has dropped hugely – Chris Armstrong Nov 20 '10 at 14:32
  • 1
    @Chris Concentrate on detecting which "car" to test based on the vertical position of the "player". You have 15 cars, testing all 15 cars every time is 15x slower than testing only the one car that possibly could overlap with the player. – Šime Vidas Nov 20 '10 at 14:38
  • Sorry for delay in marking it as correct, thanks for your help! I'll post a link here when I get it working. – Chris Armstrong Nov 28 '10 at 22:09
  • @nbsp Thanks for letting me know. I've updated my answer with a jsFiddle demo. – Šime Vidas Nov 28 '11 at 18:16
  • 4
    This is a really nice fiddle but don't you think it is better to use `$.offset()` rather than `$.position()` on line 4? That way it should work for checking collision of all elements in the DOM rather than only working on sibling elements. – Steven Lu Sep 19 '12 at 15:04
  • `.width()` and `.height()` needs to be replaced with `.outerWidth()` and `.outerHeight()` if your element has padding or borders, otherwise the calculations will be off. – SeinopSys Feb 10 '18 at 22:46
  • what about rotated div overlap case ? Please check http://jsfiddle.net/zch7uLr3/3/ – user3731362 Jun 22 '23 at 15:07
17

I believe this is the easiest way: https://plugins.jquery.com/overlaps/

Here is another one, in German: http://www.48design.de/news/2009/11/20/kollisionsabfrage-per-jquery-plugin-update-v11-8/

I'd give those a try.

--UPDATE--

I can't really spend anytime on it right now, but i can when i get home if no one answers but you;d do something like:

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);
Noel Yap
  • 18,822
  • 21
  • 92
  • 144
Oscar Godson
  • 31,662
  • 41
  • 121
  • 201
  • Thanks! With that collidable one, do I have to make the objects draggable for it to work? – Chris Armstrong Nov 19 '10 at 22:46
  • Crap, you know what, i think it does, HOWEVER... you could possibly set it to draggable with jQuery UI (which i think is what it's using) but then set return false to click events on the object making it not clickable (therefore undraggable) but the collision would work. – Oscar Godson Nov 19 '10 at 22:49
  • 1
    You know... you could also (and it might be easier)... im updating my answer. – Oscar Godson Nov 19 '10 at 22:50
10

You can do this using 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)
}
Rajat Bhatt
  • 1,545
  • 15
  • 18
7

Its a little late on this but I guess you could use this approach that I tried when I was faced with the similar situation. The advantage here is that there are no additional plugin, or scripts involved and neither do you have to introduce performance hungry polling into it. This technique uses the the built-in methods and events that Jquery's droppable has to offer.

Ok, enough said, here's the solution technique: Say if you have two elements (images in my case) and you don't want them to overlap or detect when they do, make the two elements a droppable and make them to 'accept' each other:

$([div1, div2]).droppable(CONFIG_COLLISSION_PREVENTION_DROPPABLE);

The 'CONFIG_COLLISSION_PREVENTION_DROPPABLE' looks like this:

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);
     }
}

The 'activate' and 'drop' handlers refer to the 'dropactivate' and 'drop' events of "droppable" plugin

Here, the key is the 'drop' callback. Whenever any of the two elements overlap and they are dropped over each other, the 'drop' will be called. This is the place to detect and take actions, may be sending out custom events or calling other actions (I here chose to revert the overlapping element's positions to the initial position when the drag started, which was captured in 'activate' callback).

That's it. No polling, no plugins, just the built-in events.

Well, there can be other optimizations/extensions done to it, this was simply the first shot out of my head that worked :)

You can also use the 'dropover' and 'dropout' events to signal and create a visual feedback to the user that two elements are overlapping, while they may be still on the move.

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);
    }
});

Hope this helps!

Murtaza H
  • 71
  • 1
  • 1
  • IMPORTANT: also note, the two divs should be made "draggable" first. – Murtaza H Apr 01 '12 at 14:14
  • I was looking for something like this! Trying to avoid having a game loop and I didnt want to introduce polling into the equation (like you mention). Thanks! – Chris Dolphin Mar 09 '15 at 09:09
5

EDIT: I have written a blog post on my website. Here a link to it. http://area36.nl/2014/12/creating-your-own-collision-detection-function-in-javascript/

Well I had the same problem but thanks to the answer of Oscar Godson I got a function that works. I used Jquery for easy coding and because i'm lazy ;p. I put the function in a other function that is fired every second so keep that in mind.

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
    }
}

What it does is basically it gets all the values of element1 and then get all the values of element2. Then with the help of some calculations it figures out all the values. Then in the if statement it compares the square of element1 to the square of element2. If the values of element1 are between the left, right, top and bottom values of element2. If that is true the code in the bottom is executed.

Dennis Heiden
  • 757
  • 8
  • 16
NelusTheNerd
  • 93
  • 1
  • 6
3

I ran into this generalized issue myself, so (full disclosure) I made a plugin for it. For simple collision queries about static objects, try this:

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

Which allows you to get a list of overlapping collision boxes (or none if there's no collision):

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

Or to get a collision event during "dragging", use this:

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

Which gives you a "collision" event to connect to. (Or a "protrusion" event, to see if a div escapes another div that currently contains it.)

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

If you are checking collisions during motion other than dragging, just call the original repeatedly, it's pretty quick. Note: the dragging one doesn't play nicely with resizing.

eruciform
  • 7,680
  • 1
  • 35
  • 47
  • Hi so do you have an example with the collision working on drag? I didnt manage to use it to make one div stop when it is touching another without overlapping them. On protrusion I do mounseup so I wont be able to drag it outside anymore - I need to do mouseup before protrusion. – alex toader Jan 01 '15 at 19:27
1

Post is old, May be it help someone...

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");
    }
}
Anoop B.K
  • 1,484
  • 2
  • 17
  • 31