Wie erkenne ich einen Klick außerhalb eines Elements?

2486

Ich habe einige HTML-Menüs, die ich vollständig zeige, wenn ein Benutzer auf den Kopf dieser Menüs klickt. Ich möchte diese Elemente ausblenden, wenn der Benutzer außerhalb des Menübereichs klickt.

Ist so etwas mit jQuery möglich?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});
Sergio del Amo
quelle
47
Hier ist ein Beispiel dieser Strategie: jsfiddle.net/tedp/aL7Xe/1
Ted
18
Wie Tom bereits erwähnt hat, sollten Sie css-tricks.com/dangers-stopping-event-propagation lesen, bevor Sie diesen Ansatz verwenden. Das jsfiddle Tool ist allerdings ziemlich cool.
Jon Coombs
3
Holen Sie sich einen Verweis auf das Element und dann event.target und schließlich! = oder == beide führen dann den Code entsprechend aus.
Rohit Kumar
Ich empfehle die Verwendung von github.com/gsantiago/jquery-clickout :)
Rômulo M. Farias
2
Vanilla JS-Lösung mit event.targetund ohne event.stopPropagation .
Lowtechsun

Antworten:

1812

HINWEIS: Die Verwendung stopEventPropagation()sollte vermieden werden, da sie den normalen Ereignisfluss im DOM unterbricht. Weitere Informationen finden Sie in diesem Artikel . Erwägen Sie die Verwendung dieser Methode anstelle

Fügen Sie dem Dokumentkörper ein Klickereignis hinzu, das das Fenster schließt. Fügen Sie dem Container ein separates Klickereignis hinzu, das die Weitergabe an den Dokumentkörper stoppt.

$(window).click(function() {
//Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
Eran Galperin
quelle
708
Dies bricht das Standardverhalten vieler Dinge, einschließlich Schaltflächen und Links, die in #menucontainer enthalten sind. Ich bin überrascht, dass diese Antwort so beliebt ist.
Art
75
Dies unterbricht das Verhalten von nichts in #menucontainer, da es sich für alles in #menucontainer am Ende der Ausbreitungskette befindet.
Eran Galperin
94
Es ist sehr schön, aber Sie sollten $('html').click()nicht Körper verwenden. Der Körper hat immer die Höhe seines Inhalts. Wenn es nicht viel Inhalt gibt oder der Bildschirm sehr hoch ist, funktioniert er nur an dem Teil, den der Körper ausfüllt.
Meo
103
Ich bin auch überrascht, dass diese Lösung so viele Stimmen erhalten hat. Dies wird für jedes Element außerhalb von stopPropagation jsfiddle.net/Flandre/vaNFw/3
Andre
140
Philip Walton erklärt sehr gut, warum diese Antwort nicht die beste Lösung ist: css-tricks.com/dangers-stopping-event-propagation
Tom
1386

Sie können auf ein Klickereignis warten documentund dann #menucontainermithilfe von sicherstellen, dass es sich nicht um einen Vorfahren oder das Ziel des angeklickten Elements handelt .closest() .

Ist dies nicht der Fall, befindet sich das angeklickte Element außerhalb des #menucontainerund Sie können es sicher ausblenden.

$(document).click(function(event) { 
  $target = $(event.target);
  if(!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

Bearbeiten - 2017-06-23

Sie können auch nach dem Ereignis-Listener bereinigen, wenn Sie das Menü schließen möchten und nicht mehr auf Ereignisse warten möchten. Diese Funktion bereinigt nur den neu erstellten Listener und behält alle anderen Klick-Listener bei document. Mit ES2015-Syntax:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener)
  }

  document.addEventListener('click', outsideClickListener)
}

Bearbeiten - 2018-03-11

Für diejenigen, die jQuery nicht verwenden möchten. Hier ist der obige Code in einfachem vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener)
    }

    document.addEventListener('click', outsideClickListener)
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

HINWEIS: Dies basiert auf einem Alex-Kommentar, der nur verwendet werden soll!element.contains(event.target) anstelle des jQuery-Teils verwendet wird.

Ist element.closest()jetzt aber auch in allen gängigen Browsern verfügbar (die W3C-Version unterscheidet sich ein wenig von der jQuery-Version). Polyfills finden Sie hier: Element.closest ()

Bearbeiten - 2020-05-21

Wenn Sie möchten, dass der Benutzer innerhalb des Elements klicken und ziehen kann, lassen Sie die Maus außerhalb des Elements los, ohne das Element zu schließen:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => {
        lastMouseDownX = event.offsetX
        lastMouseDownY = event.offsetY
        lastMouseDownWasOutside = !$(event.target).closest(element).length
      }
      document.addEventListener('mousedown', mouseDownListener);

Und in outsideClickListener:

const outsideClickListener = event => {
        const deltaX = event.offsetX - lastMouseDownX
        const deltaY = event.offsetY - lastMouseDownY
        const distSq = (deltaX * deltaX) + (deltaY * deltaY)
        const isDrag = distSq > 3
        const isDragException = isDrag && !lastMouseDownWasOutside

        if (!element.contains(event.target) && isVisible(element) && !isDragException) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        }
    }
Art.-Nr.
quelle
28
Ich habe viele der anderen Antworten ausprobiert, aber nur diese hat funktioniert. Vielen Dank. Der Code, den ich letztendlich verwendet habe, war folgender: $ (Dokument) .click (Funktion (Ereignis) {if ($ (Ereignis.Ziel) .closest ('. Fenster'). Länge == 0) {$ ('. Fenster') ) .fadeOut ('fast');}});
Pistos
39
Ich habe mich letztendlich für diese Lösung entschieden, weil sie mehrere Menüs auf derselben Seite besser unterstützt, bei denen durch Klicken auf ein zweites Menü, während ein erstes geöffnet ist, das erste in der stopPropagation-Lösung geöffnet bleibt.
umassthrower
13
Hervorragende Antwort. Dies ist der richtige Weg, wenn Sie mehrere Elemente haben, die Sie schließen möchten.
John
5
Dies sollte die akzeptierte Antwort sein, da die andere Lösung einen Fehler mit event.stopPropagation () aufweist.
Tomate
20
Ohne jQuery - !element.contains(event.target)mit Node.contains ()
Alex Ross
303

Wie erkenne ich einen Klick außerhalb eines Elements?

Der Grund, warum diese Frage so beliebt ist und so viele Antworten hat, ist, dass sie täuschend komplex ist. Nach fast acht Jahren und Dutzenden von Antworten bin ich wirklich überrascht zu sehen, wie wenig Sorgfalt auf die Zugänglichkeit gelegt wurde.

Ich möchte diese Elemente ausblenden, wenn der Benutzer außerhalb des Menübereichs klickt.

Dies ist eine edle Sache und die tatsächliche Problem. Der Titel der Frage - was die meisten Antworten zu beantworten scheinen - enthält einen unglücklichen roten Hering.

Hinweis: Es ist das Wort "Klick" !

Sie möchten eigentlich keine Klick-Handler binden.

Wenn Sie Click-Handler binden, um das Dialogfeld zu schließen, sind Sie bereits gescheitert. Der Grund, warum Sie versagt haben, ist, dass nicht jeder clickEreignisse auslöst . Benutzer, die keine Maus verwenden, können Ihren Dialog durch Drücken von verlassen (und Ihr Popup-Menü ist wohl eine Art Dialog) Tabund können dann den Inhalt hinter dem Dialog nicht lesen, ohne anschließend ein clickEreignis auszulösen .

Lassen Sie uns die Frage umformulieren.

Wie schließt man einen Dialog, wenn ein Benutzer damit fertig ist?

Das ist das Ziel. Leider müssen wir jetzt das userisfinishedwiththedialogEreignis binden , und diese Bindung ist nicht so einfach.

Wie können wir also feststellen, dass ein Benutzer die Verwendung eines Dialogfelds beendet hat?

focusout Veranstaltung

Ein guter Anfang ist festzustellen, ob der Fokus den Dialog verlassen hat.

Hinweis: Seien Sie vorsichtig mit dem blurEreignis, verbreiten Sie blursich nicht, wenn das Ereignis an die Blasenphase gebunden war!

jQuery's focusoutwird gut funktionieren. Wenn Sie jQuery nicht verwenden können, können Sie blurwährend der Erfassungsphase Folgendes verwenden:

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

Außerdem müssen Sie für viele Dialoge zulassen, dass der Container den Fokus erhält. Hinzufügen tabindex="-1", damit der Dialog dynamisch den Fokus erhält, ohne den Tabulatorfluss zu unterbrechen.

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Wenn Sie länger als eine Minute mit dieser Demo spielen, sollten Sie schnell Probleme sehen.

Der erste ist, dass der Link im Dialogfeld nicht anklickbar ist. Wenn Sie versuchen, darauf oder auf die Registerkarte zu klicken, wird der Dialog geschlossen, bevor die Interaktion stattfindet. Dies liegt daran, dass das Fokussieren des inneren Elements ein focusoutEreignis auslöst , bevor a ausgelöst wirdfocusin Ereignis erneut wird.

Das Update besteht darin, die Statusänderung in der Ereignisschleife in die Warteschlange zu stellen. Dies kann mithilfe von setImmediate(...)oder setTimeout(..., 0)für Browser erfolgen, die dies nicht unterstützen setImmediate. Sobald es in der Warteschlange steht, kann es durch eine nachfolgende gelöscht werden focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

Das zweite Problem ist, dass der Dialog nicht geschlossen wird, wenn der Link erneut gedrückt wird. Dies liegt daran, dass der Dialog den Fokus verliert und das Schließverhalten auslöst. Danach löst der Linkklick den Dialog zum erneuten Öffnen aus.

Ähnlich wie in der vorherigen Ausgabe muss der Fokusstatus verwaltet werden. Da die Statusänderung bereits in die Warteschlange gestellt wurde, müssen nur Fokusereignisse in den Dialogauslösern behandelt werden:

Das sollte vertraut aussehen
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});


Esc Schlüssel

Wenn Sie dachten, Sie wären mit der Behandlung der Fokuszustände fertig, können Sie noch mehr tun, um die Benutzererfahrung zu vereinfachen.

Dies ist oft eine "nice to have" -Funktion, aber es ist üblich, dass der EscSchlüssel es schließt , wenn Sie ein Modal oder Popup irgendeiner Art haben.

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}


Wenn Sie wissen, dass Sie fokussierbare Elemente im Dialogfeld haben, müssen Sie das Dialogfeld nicht direkt fokussieren. Wenn Sie ein Menü erstellen, können Sie stattdessen den ersten Menüpunkt fokussieren.

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}


WAI-ARIA-Rollen und andere Unterstützung für Barrierefreiheit

Diese Antwort behandelt hoffentlich die Grundlagen der barrierefreien Tastatur- und Mausunterstützung für diese Funktion, aber da sie bereits ziemlich umfangreich ist, werde ich jede Diskussion über WAI-ARIA-Rollen und -Attribute vermeiden. Ich empfehle jedoch dringend , dass Implementierer sich für Einzelheiten auf die Spezifikation beziehen auf welche Rollen sie verwenden sollten und welche anderen geeigneten Attribute.

zzzzBov
quelle
29
Dies ist die vollständigste Antwort unter Berücksichtigung von Erklärungen und Zugänglichkeit. Ich denke, dies sollte die akzeptierte Antwort sein, da die meisten anderen Antworten nur Klicks verarbeiten und nur Code-Snippets sind, die ohne Erklärungen gelöscht wurden.
Cyrille
4
Erstaunlich, gut erklärt. Ich habe diesen Ansatz gerade für eine Reaktionskomponente verwendet und perfekt funktioniert, danke
David Lavieri,
2
@zzzzBov danke für die ausführliche Antwort, ich versuche es in Vanilla JS zu erreichen, und ich bin ein bisschen verloren mit all dem jquery Zeug. gibt es etwas ähnliches in vanilla js?
HendrikEng
2
@zzzzBov nein, ich suche natürlich nicht nach dir, um eine jQuery-freie Version zu schreiben. Ich werde es versuchen und ich denke, das Beste ist, hier eine neue Frage zu stellen, wenn ich wirklich stecken bleibe. Nochmals vielen Dank.
HendrikEng
7
Dies ist zwar eine großartige Methode zum Erkennen des Klickens aus benutzerdefinierten Dropdowns oder anderen Eingaben, sollte jedoch aus zwei Gründen niemals die bevorzugte Methode für Modals oder Popups sein. Das Modal wird geschlossen, wenn der Benutzer zu einer anderen Registerkarte oder einem anderen Fenster wechselt oder ein Kontextmenü öffnet, was wirklich ärgerlich ist. Darüber hinaus wird das Ereignis "Klick" mit der Maus nach oben ausgelöst, während das Ereignis "Fokus" in dem Moment ausgelöst wird, in dem Sie die Maus nach unten drücken. Normalerweise führt eine Schaltfläche nur dann eine Aktion aus, wenn Sie die Maustaste drücken und dann loslassen. Der richtige und leicht zugängliche Weg, dies für Modale zu tun, besteht darin, eine tabellarische Schaltfläche zum Schließen hinzuzufügen.
Kevin
144

Die anderen Lösungen hier haben bei mir nicht funktioniert, daher musste ich Folgendes verwenden:

if(!$(event.target).is('#foo'))
{
    // hide menu
}
Dennis
quelle
Ich habe ein weiteres praktisches Beispiel für die Verwendung von event.target veröffentlicht, um zu vermeiden, dass andere Jquery UI-Widgets außerhalb des Mausklicks ausgelöst werden, wenn sie in Ihr eigenes Popup-Feld eingebettet werden: Der beste Weg, um das ursprüngliche Ziel zu erhalten
Joey T
43
Dies funktionierte für mich, außer dass ich && !$(event.target).parents("#foo").is("#foo")innerhalb der IFAnweisung hinzugefügt habe , damit untergeordnete Elemente das Menü beim Klicken nicht schließen.
Honyovk
1
Kann nicht besser finden als: // Wir sind außerhalb von $ (event.target) .parents ('# foo'). Length == 0
AlexG
3
Die prägnante Verbesserung für die Handhabung tiefer Verschachtelungen ist zu verwenden .is('#foo, #foo *'), ich empfehle jedoch nicht, Klick-Handler zu binden, um dieses Problem zu lösen .
zzzzBov
1
!$(event.target).closest("#foo").lengthwäre besser und macht die Hinzufügung von @ honyovk überflüssig.
tvanc
127

Ich habe eine Anwendung, die ähnlich wie Erans Beispiel funktioniert, außer dass ich das Klickereignis beim Öffnen des Menüs an den Text anhänge ... Ein bisschen wie folgt:

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

Weitere Informationen zur Funktion von jQueryone()

Joe Lencioni
quelle
9
aber dann, wenn Sie auf das Menü selbst klicken, dann draußen, wird es nicht funktionieren :)
vsync
3
Es ist hilfreich, event.stopProgagantion () zu platzieren, bevor der Klick-Listener an body gebunden wird.
Jasper Kennis
4
Das Problem dabei ist, dass "eins" für die jQuery-Methode zum mehrfachen Hinzufügen von Ereignissen zu einem Array gilt. Wenn Sie also mehrmals auf das Menü klicken, um es zu öffnen, wird das Ereignis erneut an den Text gebunden und versucht, das Menü mehrmals auszublenden. Um dieses Problem zu beheben, sollte eine Ausfallsicherheit angewendet werden.
Marksyzm
Nach dem Binden mit .one- innerhalb des $('html')Handlers - schreiben Sie a $('html').off('click').
Cody
4
@Cody Ich glaube nicht, dass das hilft. Der oneHandler ruft automatisch auf off(wie in den jQuery-Dokumenten gezeigt).
Mariano Desanze
44

Nach Recherchen habe ich drei funktionierende Lösungen gefunden (ich habe die Seitenlinks als Referenz vergessen)

Erste Lösung

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if(clickFlag == 0) {
            console.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('showed the element');
        /* Show the element */
    });
</script>

Zweite Lösung

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Dritte Lösung

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>
Rameez Rami
quelle
8
Die dritte Lösung ist bei weitem die eleganteste Art der Überprüfung. Es ist auch kein Overhead von jQuery erforderlich. Sehr schön. Es hat sehr geholfen. Vielen Dank.
dbarth
Ich versuche, die dritte Lösung mit mehreren Elementen in Gang zu bringen. document.getElementsByClassNameWenn jemand eine Ahnung hat, teilen Sie diese bitte mit.
Lowtechsun
@lowtechsun Sie müssten eine Schleife durchlaufen, um jede zu überprüfen.
Donnie D'Amato
Die dritte Lösung hat mir sehr gut gefallen, aber der Klick wird ausgelöst, bevor mein Div zu zeigen beginnt, was sie wieder verbirgt. Irgendeine Idee warum?
Indiehjaerta
Das dritte erlaubt zwar console.log, lässt Sie jedoch nicht schließen, indem Sie die Anzeige auf none setzen - da dies geschieht, bevor das Menü angezeigt wird. Haben Sie eine Lösung dafür?
RolandiXor
40
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Funktioniert bei mir ganz gut.

user212621
quelle
3
Dies ist die, die ich verwenden würde. Es mag nicht perfekt sein, aber als Hobby-Programmierer ist es einfach genug, um es klar zu verstehen.
Kevtrout
Unschärfe ist eine Bewegung außerhalb der #menucontainerFrage war über einen Klick
Borrel
4
@borrel Unschärfe ist keine Bewegung außerhalb des Containers. Unschärfe ist das Gegenteil von Fokus, Sie denken an Mouseout. Diese Lösung funktionierte besonders gut für mich, als ich "Click to Edit" -Text erstellte, bei dem ich bei Klicks zwischen Klartext und Eingabefeldern hin und her wechselte.
parker.sikand
Ich musste hinzufügen , tabindex="-1"um das , #menuscontainerdamit es funktioniert. Es scheint, dass der Container ausgeblendet wird, wenn Sie ein Eingabe-Tag in den Container einfügen und darauf klicken.
Tyrion
Das Mouseleave-Event ist besser für Menüs und Container geeignet (Ref: w3schools.com/jquery/… )
Evgenia
38

Jetzt gibt es dafür ein Plugin: Externe Events ( Blogpost )

Folgendes passiert, wenn ein Clickoutside- Handler (WLOG) an ein Element gebunden ist:

  • Das Element wird einem Array hinzugefügt, das alle Elemente mit Clickoutside enthält Handler
  • ein ( Namespace ) Klick Handler ist an das Dokument gebunden (falls nicht bereits vorhanden).
  • Bei jedem Klick im Dokument wird das Clickoutside- Ereignis für die Elemente in diesem Array ausgelöst, die nicht gleich oder übergeordnet zum Klick sind Ziel -events sind
  • Zusätzlich das event.target für die Clickoutside Ereignis auf das Element festgelegt, auf das der Benutzer geklickt hat (sodass Sie sogar wissen, auf was der Benutzer geklickt hat, und nicht nur, dass er nach außen geklickt hat).

Daher werden keine Ereignisse an der Weitergabe gehindert, und zusätzliche Klick- Handler können "über" dem Element mit dem Außen-Handler verwendet werden.

Wolfram
quelle
Schönes Plugin. Der Link zu "externen Ereignissen" ist tot, während der Blogpost-Link stattdessen aktiv ist und ein sehr nützliches Plugin für "clickoutside" -Ereignisse bietet. Es ist auch MIT-lizenziert.
TechNyquist
Tolles Plugin. Hat perfekt funktioniert. Die Verwendung ist wie folgt:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Gavin
33

Das hat bei mir perfekt funktioniert !!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});
Srinath
quelle
Diese Lösung funktionierte gut für das, was ich tat, wo ich immer noch das Klickereignis brauchte, um zu sprudeln, danke.
Mike
26

Ich glaube nicht, dass Sie das Menü wirklich schließen müssen, wenn der Benutzer nach draußen klickt. Sie müssen das Menü schließen, wenn der Benutzer auf eine beliebige Stelle auf der Seite klickt. Wenn Sie auf das Menü klicken oder das Menü verlassen, sollte es geschlossen werden, oder?

Da ich oben keine zufriedenstellenden Antworten gefunden habe, habe ich neulich diesen Blog-Beitrag geschrieben . Für die pedantischeren gibt es eine Reihe von Fallstricken zu beachten:

  1. Wenn Sie zum Zeitpunkt des Klickens einen Klickereignishandler an das Body-Element anhängen, warten Sie auf den zweiten Klick, bevor Sie das Menü schließen und das Ereignis aufheben. Andernfalls sprudelt das Klickereignis, das das Menü geöffnet hat, zu dem Listener, der das Menü schließen muss.
  2. Wenn Sie event.stopPropogation () für ein Klickereignis verwenden, können keine anderen Elemente auf Ihrer Seite über eine Funktion zum Klicken an einer beliebigen Stelle zum Schließen verfügen.
  3. Das unbegrenzte Anhängen eines Klickereignishandlers an das Body-Element ist keine performante Lösung
  4. Beim Vergleich des Ziels des Ereignisses und seiner Eltern mit dem Ersteller des Handlers wird davon ausgegangen, dass Sie das Menü schließen möchten, wenn Sie darauf klicken. Wenn Sie es wirklich schließen möchten, wenn Sie auf eine beliebige Stelle auf der Seite klicken.
  5. Wenn Sie auf Ereignisse im body-Element warten, wird Ihr Code spröder. Ein so unschuldiges Styling würde es brechen:body { margin-left:auto; margin-right: auto; width:960px;}
34m0
quelle
2
"Wenn Sie auf das Menü klicken oder das Menü verlassen, sollte es geschlossen werden, oder?" nicht immer. Wenn Sie einen Klick durch Ziehen eines Elements abbrechen, wird weiterhin ein Klick auf Dokumentebene ausgelöst. Es ist jedoch nicht beabsichtigt, das Menü weiter zu schließen. Es gibt auch viele andere Arten von Dialogen, die das "Click-out" -Verhalten verwenden könnten, das ein internes Klicken ermöglicht.
zzzzBov
25

Wie ein anderes Poster sagte, gibt es viele Fallstricke, insbesondere wenn das Element, das Sie anzeigen (in diesem Fall ein Menü), interaktive Elemente enthält. Ich habe festgestellt, dass die folgende Methode ziemlich robust ist:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});
benb
quelle
25

Eine einfache Lösung für die Situation ist:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID

    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

Das obige Skript verbirgt das divif außerhalb desdiv Auslösen, Klickereignisses ausgelöst wird.

Weitere Informationen finden Sie im folgenden Blog: http://www.codecanal.com/detect-click-outside-div-using-javascript/

Jitendra Damor
quelle
22

Lösung1

Anstatt event.stopPropagation () zu verwenden, das einige Nebenwirkungen haben kann, definieren Sie einfach eine einfache Flag-Variable und fügen Sie eine ifBedingung hinzu. Ich habe dies getestet und ohne Nebenwirkungen von stopPropagation richtig gearbeitet:

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if(flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

Lösung2

Mit nur einer einfachen ifBedingung:

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});
Iman Sedighi
quelle
Ich habe diese Lösung mit einem booleschen Flag verwendet und es ist auch gut mit einem artikulierten DOm und auch wenn es in #menucontainer viele andere Elemente gibt
Migio B
Lösung 1 funktioniert besser, da Fälle behandelt werden, in denen das Klickziel aus dem DOM entfernt wird, wenn sich das Ereignis auf das Dokument ausbreitet.
Alice
21

Überprüfen Sie das Fensterklick-Ereignisziel (es sollte sich auf das Fenster ausbreiten, solange es nirgendwo anders erfasst wird) und stellen Sie sicher, dass es keines der Menüelemente ist. Wenn nicht, befinden Sie sich außerhalb Ihres Menüs.

Oder überprüfen Sie die Position des Klicks und prüfen Sie, ob er im Menübereich enthalten ist.

Chris MacDonald
quelle
18

Ich hatte Erfolg mit so etwas:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

Die Logik lautet: Wenn #menuscontainerangezeigt wird, binden Sie einen Klick-Handler an den Körper, der sich #menuscontainernur verbirgt , wenn das Ziel (des Klicks) kein Kind davon ist.

Chu Yeow
quelle
17

Als Variante:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

Es hat kein Problem mit dem Stoppen der Ereignisweitergabe und unterstützt besser mehrere Menüs auf derselben Seite, bei denen durch Klicken auf ein zweites Menü, während ein erstes geöffnet ist, das erste in der stopPropagation-Lösung geöffnet bleibt.

Bohdan Lyzanets
quelle
16

Das Ereignis hat eine Eigenschaft namens event.path des Elements, die eine "statisch geordnete Liste aller seiner Vorfahren in Baumreihenfolge" ist . Um zu überprüfen, ob ein Ereignis von einem bestimmten DOM-Element oder einem seiner untergeordneten Elemente stammt, überprüfen Sie einfach den Pfad für dieses bestimmte DOM-Element. Es kann auch verwendet werden, um mehrere Elemente zu überprüfen, indem ORdie Elementprüfung in der someFunktion logisch ausgeführt wird .

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.log("Inside")
  } else {
    console.log("Outside")
  }
});
#main {
  display: inline-block;
  background:yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id="main2">
  Outside Main
</div>

Also für Ihren Fall sollte es sein

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});
Dan Philip
quelle
1
Funktioniert nicht in Edge.
Legenden
event.pathist keine Sache.
Andrew
14

Ich habe diese Methode in einem jQuery-Kalender-Plugin gefunden.

function ClickOutsideCheck(e)
{
  var el = e.target;
  var popup = $('.popup:visible')[0];
  if (popup==undefined)
    return true;

  while (true){
    if (el == popup ) {
      return true;
    } else if (el == document) {
      $(".popup").hide();
      return false;
    } else {
      el = $(el).parent()[0];
    }
  }
};

$(document).bind('mousedown.popup', ClickOutsideCheck);
Nazar Kuliyev
quelle
13

Hier ist die Vanille-JavaScript-Lösung für zukünftige Betrachter.

Wenn Sie beim Klicken auf ein Element im Dokument die ID des angeklickten Elements umschalten oder das ausgeblendete Element nicht ausgeblendet ist und das ausgeblendete Element das angeklickte Element nicht enthält, schalten Sie das Element um.

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

Wenn Sie mehrere Schalter auf derselben Seite haben, können Sie Folgendes verwenden:

  1. Fügen Sie den Klassennamen hiddenzum reduzierbaren Element hinzu.
  2. Schließen Sie beim Klicken auf ein Dokument alle ausgeblendeten Elemente, die das angeklickte Element nicht enthalten und nicht ausgeblendet sind
  3. Wenn das angeklickte Element ein Umschalter ist, schalten Sie das angegebene Element um.

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href="javascript:void(0)" data-toggle="#hidden1">Toggle Hidden Div</a>
<div class="hidden" id="hidden1" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden2">Toggle Hidden Div</a>
<div class="hidden" id="hidden2" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden3">Toggle Hidden Div</a>
<div class="hidden" id="hidden3" style="display: none;" data-hidden="true">This content is normally hidden</div>

Benutzer4639281
quelle
13

Ich bin überrascht, dass niemand das focusoutEreignis tatsächlich anerkannt hat:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <button id="button">Click</button>
</body>
</html>

Jovanni G.
quelle
Du vermisst meine Antwort, denke ich. stackoverflow.com/a/47755925/6478359
Muhammet Can TONBUL
Dies sollte die akzeptierte Antwort sein, direkt auf den Punkt. Vielen Dank!!!
Lucky Lam
8

Wenn Sie Skripte für IE und FF 3 erstellen * und nur wissen möchten, ob der Klick in einem bestimmten Feldbereich aufgetreten ist, können Sie auch Folgendes verwenden:

this.outsideElementClick = function(objEvent, objElement){   
var objCurrentElement = objEvent.target || objEvent.srcElement;
var blnInsideX = false;
var blnInsideY = false;

if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

if (blnInsideX && blnInsideY)
    return false;
else
    return true;}
Erik
quelle
8

Verwenden Sie stattdessen eine Flussunterbrechung, ein Unschärfe- / Fokusereignis oder eine andere knifflige Technik, um den Ereignisfluss einfach mit der Verwandtschaft des Elements abzugleichen:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if(!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

Um den Klick außerhalb des Ereignis-Listeners zu entfernen, gehen Sie einfach wie folgt vor:

$(document).off("click.menu-outside");
mems
quelle
Für mich war das die beste Lösung. Ich habe es in einem Rückruf nach der Animation verwendet, also musste ich das Ereignis irgendwann trennen. +1
qwertzman
Hier erfolgt eine redundante Prüfung. Wenn das Element tatsächlich ist, gehen #menuscontainerSie immer noch durch die Eltern. Sie sollten dies zuerst überprüfen. Wenn es sich nicht um dieses Element handelt, gehen Sie in den DOM-Baum.
vsync
Recht ! Sie können die Bedingung in ändern if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Es ist eine winzige Optimierung, die jedoch nur wenige Male in Ihrer Programmlebensdauer auftritt: für jeden Klick, wenn das click.menu-outsideEreignis registriert ist. Es ist länger (+32 Zeichen) und verwendet keine Methodenverkettung
Mems
8

Verwenden:

var go = false;
$(document).click(function(){
    if(go){
        $('#divID').hide();
        go = false;
    }
})

$("#divID").mouseover(function(){
    go = false;
});

$("#divID").mouseout(function (){
    go = true;
});

$("btnID").click( function(){
    if($("#divID:visible").length==1)
        $("#divID").hide(); // Toggle
    $("#divID").show();
});
webenformasyon
quelle
7

Wenn jemand hier neugierig ist, ist Javascript-Lösung (es6):

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

und es5, nur für den Fall:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});

Walt
quelle
7

Hier ist eine einfache Lösung durch reines Javascript. Es ist mit ES6 auf dem neuesten Stand :

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})
Duannx
quelle
"Mit ES6 auf dem neuesten Stand" ist eine ziemlich kühne Behauptung, wenn das einzige, was mit ES6 auf dem neuesten Stand ist, () => {}statt zu tun function() {}. Was Sie dort haben, wird als einfaches JavaScript mit einem Twist von ES6 klassifiziert.
MortenMoulder
@MortenMoulder: Ja. Es ist nur zur Aufmerksamkeit, obwohl es tatsächlich ES6 ist. Aber schauen Sie sich einfach die Lösung an. Ich finde es gut.
Duannx
Es ist Vanilla JS und funktioniert für Ereignisziele, die aus dem DOM entfernt wurden (z. B. wenn der Wert aus dem inneren Popup ausgewählt ist und das Popup sofort geschlossen wird). +1 von mir!
Alice
6

Hängen Sie einen Klickereignis-Listener in das Dokument ein. Im Ereignis-Listener können Sie das Ereignisobjekt , insbesondere das event.target , anzeigen , um festzustellen, auf welches Element geklickt wurde:

$(document).click(function(e){
    if ($(e.target).closest("#menuscontainer").length == 0) {
        // .closest can help you determine if the element 
        // or one of its ancestors is #menuscontainer
        console.log("hide");
    }
});
Salman A.
quelle
6

Ich habe unten Skript verwendet und mit jQuery fertig.

jQuery(document).click(function(e) {
    var target = e.target; //target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); //if the click element is not the above id will hide
    }
})

Unten finden Sie den HTML-Code

<div class="main-container">
<div> Hello I am the title</div>
<div class="tobehide">I will hide when you click outside of me</div>
</div>

Sie können das Tutorial hier lesen

Rinto George
quelle
5
$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

Wenn Sie auf das Dokument klicken, blenden Sie ein bestimmtes Element aus, es sei denn, Sie klicken auf dasselbe Element.

Eberesche
quelle
5

Upvote für die beliebteste Antwort, aber hinzufügen

&& (e.target != $('html').get(0)) // ignore the scrollbar

Ein Klick auf eine Bildlaufleiste [versteckt oder was auch immer] Ihr Zielelement nicht.

bbe
quelle
5

Für eine einfachere Verwendung und ausdrucksstärkeren Code habe ich ein jQuery-Plugin dafür erstellt:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

Hinweis: Ziel ist das Element, auf das der Benutzer tatsächlich geklickt hat. Der Rückruf wird jedoch weiterhin im Kontext des ursprünglichen Elements ausgeführt, sodass Sie diesen wie erwartet in einem jQuery-Rückruf verwenden können.

Plugin:

$.fn.clickOut = function (parent, fn) {
    var context = this;
    fn = (typeof parent === 'function') ? parent : fn;
    parent = (parent instanceof jQuery) ? parent : $(document);

    context.each(function () {
        var that = this;
        parent.on('click', function (e) {
            var clicked = $(e.target);
            if (!clicked.is(that) && !clicked.parents().is(that)) {
                if (typeof fn === 'function') {
                    fn.call(that, clicked);
                }
            }
        });

    });
    return context;
};

Standardmäßig wird der Listener für Klickereignisse im Dokument platziert. Wenn Sie jedoch den Bereich des Ereignis-Listeners einschränken möchten, können Sie ein jQuery-Objekt übergeben, das ein übergeordnetes Element darstellt, das das oberste übergeordnete Element ist, bei dem Klicks abgehört werden. Dies verhindert unnötige Ereignis-Listener auf Dokumentebene. Offensichtlich funktioniert es nur, wenn das angegebene übergeordnete Element ein übergeordnetes Element Ihres ursprünglichen Elements ist.

Verwenden Sie wie folgt:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
Matt Goodwin
quelle