Klick außerhalb des Elements erkennen (Vanille-JavaScript)

84

Ich habe überall nach einer guten Lösung gesucht, kann aber keine finden, die jQuery nicht verwendet .

Gibt es eine browserübergreifende, normale Methode (ohne seltsame Hacks oder leicht zu brechenden Code), um einen Klick außerhalb eines Elements zu erkennen (das möglicherweise untergeordnete Elemente hat oder nicht)?

Tiberiu-Ionuț Stan
quelle
Würde das Festlegen eines Onclick-Ereignishandlers für das Body-Tag den Job erledigen?
Benjamin Toueg

Antworten:

192

Fügen Sie einen Ereignis-Listener hinzu documentund verwenden Sie ihn Node.contains(), um festzustellen, ob sich das Ziel des Ereignisses (das am innersten angeklickte Element) innerhalb des angegebenen Elements befindet. Es funktioniert sogar in IE5

var specifiedElement = document.getElementById('a');

//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
  var isClickInside = specifiedElement.contains(event.target);

  if (!isClickInside) {
    //the click was outside the specifiedElement, do something
  }
});

fregante
quelle
14
Scheint mir die eleganteste Lösung zu sein.
Marian
3
Es ist die beste Lösung, die ich je gesehen habe.
HelloWorld
25

Sie müssen das Klickereignis auf Dokumentebene behandeln. Im Ereignisobjekt haben Sie eine targetEigenschaft, das innerste DOM-Element, auf das geklickt wurde. Damit überprüfen Sie sich selbst und gehen die Eltern bis zum Dokumentelement hoch, wenn eines davon Ihr überwachtes Element ist.

Siehe das Beispiel auf jsFiddle

document.addEventListener("click", function (e) {
  var level = 0;
  for (var element = e.target; element; element = element.parentNode) {
    if (element.id === 'x') {
      document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
      return;
    }
    level++;
  }
  document.getElementById("out").innerHTML = "not x clicked";
});

Wie immer ist dies aufgrund von addEventListener / attachEvent nicht Cross-Bad-Browser-kompatibel, aber es funktioniert so.

Ein Kind wird angeklickt, wenn es nicht event.target ist, aber eines seiner Eltern ist das beobachtete Element (ich zähle einfach die Stufe dafür). Sie können auch eine boolesche Variable haben, wenn das Element gefunden wird oder nicht, um den Handler nicht aus der for-Klausel zurückzugeben. Mein Beispiel beschränkt sich darauf, dass der Handler nur dann beendet wird, wenn nichts übereinstimmt.

Wenn ich die Cross-Browser-Kompatibilität hinzufüge, mache ich das normalerweise so:

var addEvent = function (element, eventName, fn, useCapture) {
  if (element.addEventListener) {
    element.addEventListener(eventName, fn, useCapture);
  }
  else if (element.attachEvent) {
    element.attachEvent(eventName, function (e) {
      fn.apply(element, arguments);
    }, useCapture);
  }
};

Dies ist browserübergreifender kompatibler Code zum Anhängen eines Ereignis-Listeners / -Handlers, einschließlich des Umschreibens thisim IE, als Element, wie dies jQuery für seine Ereignishandler tut. Es gibt viele Argumente, um ein bisschen jQuery im Auge zu behalten;)

Metadings
quelle
4
Was passiert, wenn der Benutzer auf ein untergeordnetes Element des Elements geklickt hat?
Tiberiu-Ionuț Stan
3
Mit dieser Lösung überprüfen Sie die targetEigenschaft (die, wie Metadings sagt, das innerste DOM-Element ist) - und alle übergeordneten Elemente. Entweder erreichen Sie Ihr überwachtes Element, dh der Benutzer hat darauf geklickt, oder Sie erreichen das Dokumentelement. In diesem Fall hat er nach außen geklickt. Alternativ können Sie zwei Klick-Listener hinzufügen - einen für das Dokumentelement und einen für das überwachte Element, wobei event.stopPropagation()/ verwendet wird, cancelBubblesodass der Listener für das Dokumentelement nur durch einen Klick außerhalb des überwachten Elements ausgelöst wird.
JimmiTh
2
Das ist eine kluge Verwendung von for. Gibt es aus Neugier einen Grund, ein Element aus einer Variablen nicht mit dem Ziel des Ereignisses zu vergleichen (unter Verwendung eines strengen Vergleichs)?
Tiberiu-Ionuț Stan
@ Tiberiu-IonuțStan Ja, wenn Sie diese 'Uhr' mit einer Referenz anstelle der ID 'x' erstellen, empfehle ich Ihnen, das Element === watchElement mit Triple Equals zu verwenden.
Metadings
Ihre Lösung funktioniert hervorragend. Dies kann ein persönliches Problem sein, aber ich möchte es lieber teilen. Ich habe das gleiche Bedürfnis, aber eine Schaltfläche, auf die geklickt wird, zeigt ein Div, das ausgeblendet werden sollte, wenn der Benutzer nach draußen klickt. Das Problem bei dieser Lösung ist, dass das div nie wieder angezeigt wird. Ich habe Ihren Code hier für ein Beispiel gegabelt: jsfiddle.net/bx5hnL2h
Alexandre Schmidt
7

Wie wäre es damit:

jsBin Demo

document.onclick = function(event){
  var hasParent = false;
    for(var node = event.target; node != document.body; node = node.parentNode)
    {
      if(node.id == 'div1'){
        hasParent = true;
        break;
      }
    }
  if(hasParent)
    alert('inside');
  else
    alert('outside');
} 
Akhil Sekharan
quelle
0

Um ein Element durch Klicken außerhalb auszublenden, wende ich normalerweise einen so einfachen Code an:

var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element'); 
function clickedOrNot(e) {
	if (e.target !== element) {
		// action in the case of click outside 
		bodyTag[0].removeEventListener('click', clickedOrNot, true);
	}	
}
bodyTag[0].addEventListener('click', clickedOrNot, true);

Aleks Sid
quelle
0

Eine andere sehr einfache und schnelle Lösung für dieses Problem besteht darin, das Array von pathdem vom Listener zurückgegebenen Ereignisobjekt zuzuordnen . Wenn der Name idoder className Ihres Elements mit einem Namen im Array übereinstimmt, befindet sich der Klick in Ihrem Element.

(Diese Lösung kann nützlich sein, wenn Sie das Element nicht direkt document.getElementById('...')abrufen möchten (z. B. in einer reactjs / nextjs-App in ssr ..).

Hier ist ein Beispiel:

   document.addEventListener('click', e => {
      let clickedOutside = true;

      e.path.forEach(item => {
        if (!clickedOutside)
          return;

        if (item.className === 'your-element-class')
          clickedOutside = false;
      });

      if (clickedOutside)
        // Make an action if it's clicked outside..
    });

Ich hoffe diese Antwort wird Ihnen helfen! (Lassen Sie mich wissen, ob meine Lösung keine gute Lösung ist oder ob Sie Verbesserungen sehen.)

hpapier
quelle
Was ist mit verschachtelten Elementen?
Tiberiu-Ionuț Stan
@ Tiberiu-IonuțStan Sie haben Zugriff auf die gesamte Knotenliste, vom übergeordneten bis zum untergeordneten Knoten, sodass Sie sie problemlos bearbeiten können.
Papier
Plötzlich ist Ihre Lösung nicht mehr einfach.
Tiberiu-Ionuț Stan
@ Tiberiu-IonuțStan Warum? Persönlich finde ich, dass diese Lösung die einfachste in einer React- oder komplizierten JS-Umgebung ist. Benötigen Sie keine Referenz, müssen Sie keine Lösung implementieren, wenn der Dom Zeit zum Laden hat, Sie haben mehr Kontrolle.
Papier
0

Ich habe viel darüber recherchiert, um eine bessere Methode zu finden. Die JavaScript-Methode .contains wird im DOM rekursiv überprüft, ob sie ein Ziel enthält oder nicht. Ich habe es in einem der React-Projekte verwendet, aber wenn sich das React-DOM im festgelegten Status ändert, funktioniert die .contains-Methode nicht. Also habe ich mir diese Lösung ausgedacht

//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
<div id="mydiv">
  <h2>
    click outside this div to test
  </h2>
  Check click outside 
</div>
</body>
</html>


//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'

let isCursorInside = false

//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
  isCursorInside = true
  console.log('cursor inside')
})

/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
    isCursorInside = false
  console.log('cursor outside')
})


document.addEventListener('click', function() {
  //And if isCursorInside = false it means cursor is outside 
    if(!isCursorInside) {
    alert('Outside div click detected')
  }
})

ARBEITSDEMO jsfiddle

Amrendra Kumar
quelle