Frage Wie erkenne ich einen Klick außerhalb eines Elements?


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üs klickt.

Ist so etwas mit jQuery möglich?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});

2023


Ursprung


Antworten:


HINWEIS: Verwenden stopEventPropagation() ist etwas, das vermieden werden sollte, da es den normalen Ereignisfluss im DOM unterbricht. Sehen Dieser Artikel für mehr Informationen. Überlegen Sie zu verwenden diese Methode stattdessen.

Fügen Sie ein Click-Ereignis an den Dokumentkörper an, der das Fenster schließt. Hängen Sie ein separates Klickereignis an das Fenster an, das die Weitergabe an den Dokumentkörper stoppt.

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

$('#menucontainer').click(function(event){
    event.stopPropagation();
});

1618



Sie können nach einem hören klicken Ereignis am document und dann stellen Sie sicher #menucontainer ist nicht ein Vorfahre oder das Ziel des angeklickten Elements mit .closest().

Wenn dies nicht der Fall ist, befindet sich das angeklickte Element außerhalb der #menucontainer und du kannst es sicher verstecken.

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

Bearbeiten - 2017-06-23

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

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    if (!$(event.target).closest(selector).length) {
      if ($(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 plain vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target)) { // or use: event.target.closest(selector) === null
            if (isVisible(element)) {
                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 Alex Kommentar zu verwenden !element.contains(event.target) anstelle des jQuery-Teils.

Aber element.closest() ist jetzt auch in allen gängigen Browsern verfügbar (die W3C-Version unterscheidet sich ein wenig von der jQuery-Version). Polyfills finden Sie hier: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


1123



Wie erkennt man einen Klick außerhalb eines Elements?

Der Grund, warum diese Frage so populär 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 Rücksicht auf die Barrierefreiheit genommen wurde.

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

Dies ist eine edle Sache und ist die tatsächlich Problem. Der Titel der Frage, auf die die meisten Antworten zu antworten scheinen, enthält einen unglücklichen Ablenkungsmanöver.

Tipp: Es ist das Wort "klicken"!

Sie möchten Click-Handler nicht wirklich binden.

Wenn Sie klicken, klicken Sie auf Handler, um den Dialog zu schließen, Sie sind bereits fehlgeschlagen. Der Grund, warum du versagt hast, ist, dass nicht jeder auslöst click Veranstaltungen. Benutzer, die keine Maus verwenden, können den Dialog verlassen (und Ihr Popup-Menü ist wohl eine Art Dialog), indem Sie drücken Tab, und sie werden dann nicht in der Lage sein, den Inhalt hinter dem Dialog zu lesen, ohne anschließend einen click Veranstaltung.

Lassen Sie uns die Frage neu formulieren.

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

Das ist das Ziel. Leider müssen wir jetzt den userisfinishedwiththedialog Ereignis, und diese Bindung ist nicht so einfach.

Wie können wir erkennen, dass ein Benutzer den Dialog beendet hat?

focusout Veranstaltung

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

Tipp: Vorsicht mit dem blur Veranstaltung, blur propagiert nicht, wenn das Ereignis an die Bubbling-Phase gebunden war!

jQuery's focusout geht es gut. Wenn Sie jQuery nicht verwenden können, können Sie verwenden blur während der Eroberungsphase:

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

Außerdem müssen Sie für viele Dialogfelder dem Container den Fokus geben. Hinzufügen tabindex="-1" Damit der Dialog den Fokus dynamisch erhält, ohne den Tab-Flow sonst 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 Dialog nicht anklickbar ist. Wenn Sie versuchen, darauf zu klicken oder darauf zu klicken, wird der Dialog geschlossen, bevor die Interaktion stattfindet. Dies liegt daran, dass die Fokussierung des inneren Elements einen Auslöser auslöst focusout Ereignis vor dem Auslösen eines focusin Ereignis erneut.

Die Lösung besteht darin, die Statusänderung in der Ereignisschleife in eine Warteschlange zu stellen. Dies kann unter Verwendung von setImmediate(...), oder setTimeout(..., 0) für Browser, die dies nicht unterstützen setImmediate. Einmal in die Warteschlange gestellt, kann es durch einen nachfolgenden abgebrochen 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'));
  }
});

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

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});
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>

Das zweite Problem ist, dass der Dialog nicht geschlossen wird, wenn die Verbindung erneut gedrückt wird. Dies liegt daran, dass der Dialog den Fokus verliert und das Schließen-Verhalten auslöst, woraufhin der Link-Klick den Dialog zum erneuten Öffnen auslöst.

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

Dies sollte Ihnen bekannt vorkommen
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

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

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
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>


Esc Schlüssel

Wenn Sie der Meinung sind, dass Sie die Fokuszustände bereits erledigt haben, können Sie noch mehr tun, um die Benutzererfahrung zu vereinfachen.

Dies ist oft ein "nice to have" -Feature, aber es ist üblich, dass wenn Sie ein Modal oder Popup irgendeiner Art haben Esc Schlüssel wird es schließen.

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

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

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('active');
      e.preventDefault();
    }
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
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 wissen, dass Sie fokussierbare Elemente innerhalb des Dialogs haben, müssen Sie den Dialog nicht direkt fokussieren. Wenn Sie ein Menü erstellen, können Sie stattdessen den ersten Menüeintrag fokussieren.

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

$('.menu__link').on({
  click: function (e) {
    $(this.hash)
      .toggleClass('submenu--active')
      .find('a:first')
      .focus();
    e.preventDefault();
  },
  focusout: function () {
    $(this.hash).data('submenuTimer', setTimeout(function () {
      $(this.hash).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('submenuTimer'));  
  }
});

$('.submenu').on({
  focusout: function () {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('submenuTimer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('submenu--active');
      e.preventDefault();
    }
  }
});
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
.menu:after {
  clear: both;
  content: '';
  display: table;
}
.menu__item {
  float: left;
  position: relative;
}

.menu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
  background-color: black;
  color: lightblue;
}

.submenu {
  border: 1px solid black;
  display: none;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 100%;
}
.submenu--active {
  display: block;
}

.submenu__item {
  width: 150px;
}

.submenu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}

.submenu__link:hover,
.submenu__link:focus {
  background-color: black;
  color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="menu">
  <li class="menu__item">
    <a class="menu__link" href="#menu-1">Menu 1</a>
    <ul class="submenu" id="menu-1" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
  <li class="menu__item">
    <a  class="menu__link" href="#menu-2">Menu 2</a>
    <ul class="submenu" id="menu-2" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
</ul>
lorem ipsum <a href="http://example.com/">dolor</a> sit amet.


WAI-ARIA-Rollen und andere Zugänglichkeitsunterstützung

Diese Antwort deckt hoffentlich die Grundlagen der zugänglichen Tastatur- und Mausunterstützung für diese Funktion ab, aber da sie bereits ziemlich groß ist, werde ich jede Diskussion vermeiden WAI-ARIA-Rollen und -Attribute, Wie auch immer, ich höchst empfehlen, dass Implementierer auf die Spezifikation verweisen, um Details zu den zu verwendenden Rollen und anderen geeigneten Attributen zu erhalten.


184



Die anderen Lösungen hier funktionierten nicht für mich, also musste ich verwenden:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

127



Ich habe eine Anwendung, die ähnlich wie Erans Beispiel funktioniert, außer dass ich das Click-Ereignis an den Körper anschließe, wenn ich das Menü öffne ... So ähnlich:

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

  event.stopPropagation();
});

Weitere Informationen zu jQuery's one() Funktion


118



$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Funktioniert für mich gut.


32



Jetzt gibt es ein Plugin dafür: äußere Ereignisse (Blogeintrag)

Folgendes passiert, wenn a Klick außerhalb Handler (WLOG) ist an ein Element gebunden:

  • Das Element wird zu einem Array hinzugefügt, in dem alle Elemente enthalten sind Klick außerhalb Handler
  • ein (Namensraum) klicken Handler ist an das Dokument gebunden (wenn nicht schon da)
  • auf irgendwelchen klicken in dem Dokument, das Klick außerhalb Das Ereignis wird für die Elemente in diesem Array ausgelöst, die nicht gleich oder übergeordnet sind klicken-Ereignisse Ziel
  • zusätzlich das event.target für die Klick außerhalb Das Ereignis wird auf das Element gesetzt, auf das der Nutzer geklickt hat (damit Sie wissen, worauf der Nutzer geklickt hat und nicht nur, dass er außerhalb geklickt hat)

Es werden also keine Ereignisse von der Propagierung und zusätzlich gestoppt klicken Handler können "über" dem Element mit dem Außenhandler verwendet werden.


31



Das hat bei mir perfekt funktioniert !!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});

26