jQuery / JavaScript-Kollisionserkennung

77

Wie erkennt man, ob zwei <div> Elemente kollidiert sind?

Die beiden Divs sind einfache farbige Kästchen, die senkrecht zueinander verlaufen, also keine komplizierten Formen oder Winkel.

Chris Armstrong
quelle
Wow, was für eine schöne Webseite. Und die Animationen sind reines CSS. :)
Šime Vidas
Danke, es ist im Moment so rau wie alles andere, aber Sie haben die Idee. Ich werde es schön machen, wenn ich die Grundlagen zum Laufen bringe. Es stellt sich heraus, dass CSS für das Level-Design fantastisch ist ... Klassen sind eine wirklich einfache Möglichkeit, Verhaltensweisen zu überlagern. Ich werde jetzt deinen Beispielcode ausprobieren, danke
Chris Armstrong,
4
Achtung, die Seite stürzt in FireFox 12 ab. JavaScript bleibt hängen und fordert niemals auf, das Skript zu stoppen.
Potatoswatter
Als Tipp für Ihr Spiel möchten Sie möglicherweise die Seite nach unten und die Bildlaufleiste deaktivieren.
Andrew
Mögliches Duplikat von Javascript: Kollisionserkennung
Pavlo

Antworten:

71

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>

Allgemeine Idee - Sie erhalten den Versatz und die Abmessung der Felder und prüfen, ob sie sich überlappen.

Wenn Sie möchten, dass es aktualisiert wird, können Sie Folgendes verwenden setInterval:

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

detectOverlapping();  

Beachten Sie außerdem, dass Sie die Funktion für Ihr spezielles Beispiel optimieren können.

  • Sie müssen die Abmessungen der Box nicht wiederholt lesen (wie ich es in meinem Code tue), da sie fest sind. Sie können sie beim Laden der Seite (in eine Variable) lesen und dann einfach die Variable lesen

  • Die horizontale Position des Kästchens ändert sich nicht (es sei denn, der Benutzer ändert die Größe des Fensters). Die vertikalen Positionen der Fahrzeugboxen ändern sich nicht. Daher müssen diese Werte auch nicht wiederholt gelesen werden, sondern können auch in Variablen gespeichert werden.

  • Sie müssen nicht immer testen, ob sich die kleine Box mit allen Auto-Boxen überschneidet. Sie können anhand der vertikalen Position herausfinden, auf welcher Fahrspur sich die Box gerade befindet, und nur die spezifische Fahrzeugbox dieser Fahrspur testen.

Šime Vidas
quelle
Das sieht gut aus, wenn ich versuche, es jetzt an meinem Beispiel zu testen. Wie würde ich es dazu bringen, jeden Frame zu aktualisieren?
Chris Armstrong
Hmm ... Die Position des Spielers reicht von (649, 75) bis (674, 100) und die Position des Autos von (649, 50) bis (749, 100). Sie überlappen sich also, aber das Spiel kehrt als zurück falsch, eine Idee, warum das sein würde?
Chris Armstrong
@ Chris Ja, ich habe gerade festgestellt, dass ich nur 2 der insgesamt 9 Überlappungsszenarien berücksichtigt habe. Es sollte jedoch einfach sein, alle 9 Szenarien zu integrieren. Gib mir eine Sekunde.
Šime Vidas
1
@Chris Konzentrieren Sie sich darauf, anhand der vertikalen Position des "Spielers" zu ermitteln, welches "Auto" getestet werden soll. Sie haben 15 Autos. Das Testen aller 15 Autos ist jedes Mal 15x langsamer als das Testen nur des einen Autos, das sich möglicherweise mit dem Spieler überschneiden könnte.
Šime Vidas
4
Dies ist eine wirklich nette Geige, aber denkst du nicht, dass es besser ist, sie zu verwenden, $.offset()als $.position()in Zeile 4? Auf diese Weise sollte es funktionieren, um die Kollision aller Elemente im DOM zu überprüfen, anstatt nur an Geschwisterelementen zu arbeiten.
Steven Lu
17

Ich glaube, das ist der einfachste Weg: http://plugins.jquery.com/project/collidable

Hier ist noch eine auf Deutsch: http://www.48design.de/news/2009/11/20/kollisionsabfrage-per-jquery-plugin-update-v11-8/

Ich würde es versuchen.

--AKTUALISIEREN--

Ich kann momentan nicht wirklich viel Zeit damit verbringen, aber ich kann es, wenn ich nach Hause komme, wenn niemand außer dir antwortet; ich würde etwas tun wie:

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);
Oscar Patensohn
quelle
Vielen Dank! Muss ich bei diesem kollidierbaren Objekt die Objekte ziehbar machen, damit es funktioniert?
Chris Armstrong
Mist, weißt du was, ich denke es funktioniert, JEDOCH ... du könntest es möglicherweise mit der jQuery-Benutzeroberfläche (die meiner Meinung nach verwendet wird) auf ziehbar setzen, aber dann return false setzen, um Ereignisse auf das Objekt zu klicken, wodurch es nicht anklickbar wird ( daher nicht zerlegbar), aber die Kollision würde funktionieren.
Oscar Godson
1
Weißt du ... du könntest auch (und es könnte einfacher sein) ... ich aktualisiere meine Antwort.
Oscar Godson
7

Es ist etwas spät, aber ich denke, Sie könnten diesen Ansatz verwenden, den ich versucht habe, als ich mit einer ähnlichen Situation konfrontiert war. Der Vorteil hierbei ist, dass kein zusätzliches Plugin oder Skripte beteiligt sind und Sie auch keine leistungshungrigen Abfragen einführen müssen. Diese Technik verwendet die integrierten Methoden und Ereignisse, die Jquery's Droppable zu bieten hat.

Ok, genug gesagt, hier ist die Lösungstechnik: Sagen Sie, wenn Sie zwei Elemente haben (Bilder in meinem Fall) und Sie nicht möchten, dass sie sich überlappen oder erkennen, wenn sie dies tun, machen Sie die beiden Elemente zu einem Droppable und lassen Sie sie "akzeptieren". gegenseitig:

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

Die 'CONFIG_COLLISSION_PREVENTION_DROPPABLE' sieht folgendermaßen aus:

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

Die Handler "Aktivieren" und "Löschen" beziehen sich auf die Ereignisse "Dropaktivieren" und "Löschen" des Plugins "Droppable"

Hier ist der Schlüssel der Drop-Callback. Immer wenn sich eines der beiden Elemente überlappt und sie übereinander fallen, wird der "Tropfen" aufgerufen. Dies ist der Ort, an dem Aktionen erkannt und ausgeführt werden können, benutzerdefinierte Ereignisse gesendet oder andere Aktionen aufgerufen werden können (ich habe mich hier dafür entschieden, die Positionen des überlappenden Elements auf die ursprüngliche Position zurückzusetzen, als das Ziehen gestartet wurde, die im Rückruf "Aktivieren" erfasst wurde).

Das ist es. Keine Abfrage, keine Plugins, nur die eingebauten Ereignisse.

Nun, es können andere Optimierungen / Erweiterungen vorgenommen werden, dies war einfach der erste Schuss aus meinem Kopf, der funktioniert hat :)

Sie können auch die Ereignisse "Dropover" und "Dropout" verwenden, um dem Benutzer zu signalisieren und ein visuelles Feedback zu erstellen, dass sich zwei Elemente überlappen, während sie sich möglicherweise noch in Bewegung befinden.

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

Hoffe das hilft!

Murtaza H.
quelle
WICHTIG: Beachten Sie auch, dass die beiden Divs zuerst "ziehbar" gemacht werden sollten.
Murtaza H
Ich habe so etwas gesucht! Ich habe versucht, eine Spielschleife zu vermeiden, und ich wollte kein Polling in die Gleichung einführen (wie Sie bereits erwähnt haben). Vielen Dank!
Chris Dolphin
5

EDIT: Ich habe einen Blog-Beitrag auf meiner Website geschrieben. Hier ein Link dazu. http://area36.nl/2014/12/creating-your-own-collision-detection-function-in-javascript/

Nun, ich hatte das gleiche Problem, aber dank der Antwort von Oscar Godson bekam ich eine Funktion, die funktioniert. Ich habe Jquery für einfaches Codieren verwendet und weil ich faul bin; p. Ich habe die Funktion in eine andere Funktion eingefügt, die jede Sekunde ausgelöst wird. Denken Sie also daran.

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

Was es tut, ist im Grunde, dass es alle Werte von element1und dann alle Werte von erhält element2. Dann werden mit Hilfe einiger Berechnungen alle Werte ermittelt. Dann ifvergleicht es in der Aussage das Quadrat von element1mit dem Quadrat von element2. Wenn die Werte von element1zwischen den linken, rechten, oberen und unteren Werten von liegen element2. Wenn dies zutrifft, wird der Code unten ausgeführt.

Geke_Sulen
quelle
Könnten Sie bitte ein Beispiel nennen? Vielleicht eine JS-Geige?
Andy
3

Ich bin selbst auf dieses allgemeine Problem gestoßen, also habe ich (vollständige Offenlegung) ein Plugin dafür erstellt. Versuchen Sie Folgendes für einfache Kollisionsabfragen zu statischen Objekten:

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

So können Sie eine Liste überlappender Kollisionsfelder abrufen (oder keine, wenn keine Kollision vorliegt):

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

Oder verwenden Sie Folgendes, um beim "Ziehen" ein Kollisionsereignis zu erhalten:

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

Dadurch erhalten Sie ein "Kollisions" -Ereignis, mit dem Sie eine Verbindung herstellen können. (Oder ein "Protrusions" -Ereignis, um zu sehen, ob ein Div einem anderen Div entgeht, das es derzeit enthält.)

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

Wenn Sie Kollisionen während einer anderen Bewegung als dem Ziehen überprüfen, rufen Sie einfach das Original wiederholt auf, es ist ziemlich schnell. Hinweis: Das Ziehen spielt beim Ändern der Größe nicht gut.

eruciform
quelle
Hallo, haben Sie ein Beispiel für die Kollision beim Ziehen? Ich habe es nicht geschafft, ein Div anzuhalten, wenn es ein anderes berührt, ohne sie zu überlappen. Beim Vorspringen mache ich Mounseup, damit ich es nicht mehr nach draußen ziehen kann - ich muss Mouseup vor dem Vorspringen machen.
Alex Toader
1

Post ist alt, kann es jemandem helfen ...

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 BK
quelle
1

Sie können dies mit getBoundingClientRect () tun

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
quelle