2941 Stimmen

Wie kann ich einen Klick außerhalb eines Elements erkennen?

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

Ist so etwas mit jQuery möglich?

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

2017voto

Eran Galperin Punkte 84916

Anmerkung: Mit stopPropagation ist etwas, das vermieden werden sollte, da es den normalen Ereignisfluss im DOM unterbricht. Siehe dieser CSS-Tricks-Artikel für weitere Informationen. Erwägen Sie die Verwendung diese Methode stattdessen.

Verknüpfen Sie ein Klick-Ereignis mit dem Dokumentenkörper, das das Fenster schließt. Weisen Sie dem Container ein separates Klickereignis zu, das die Weitergabe an den Dokumentenkörper stoppt.

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

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

1621voto

Art Punkte 22351

Sie können für eine anklicken. Veranstaltung am document und stellen Sie dann sicher, dass #menucontainer weder ein Vorfahre noch das Ziel des angeklickten Elements ist, indem Sie .closest() .

Ist dies nicht der Fall, so liegt das angeklickte Element außerhalb der #menucontainer und Sie können es sicher verstecken.

$(document).click(function(event) { 
  var $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 aufräumen, wenn Sie planen, das Menü zu beenden und nicht mehr auf Ereignisse zu warten. Diese Funktion bereinigt nur den neu erstellten Listener und behält alle anderen Click-Listener auf document . Mit ES2015-Syntax:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    const $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 plain 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 

NOTA: Dies basiert auf dem Kommentar von Alex, einfach die !element.contains(event.target) anstelle des jQuery-Teils.

Aber element.closest() ist jetzt auch in allen wichtigen Browsern verfügbar (die W3C-Version unterscheidet sich ein wenig von der jQuery-Version). Polyfills können hier gefunden werden: Element.closest()

Bearbeiten - 2020-05-21

Wenn Sie möchten, dass der Benutzer innerhalb des Elements klicken und ziehen kann und dann die Maus außerhalb des Elements loslässt, 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()
        }
    }

433voto

zzzzBov Punkte 166065

Wie erkennt man 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, wie wenig Sorgfalt auf die Zugänglichkeit verwendet wurde.

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

Dies ist eine edle Sache und ist die aktuell Problem. Der Titel der Frage - auf den die meisten Antworten anscheinend abzielen - enthält ein unglückliches Ablenkungsmanöver.

Hinweis: Es ist das Wort "Klick" !

Sie wollen eigentlich keine Click-Handler binden.

Wenn Sie Klick-Handler binden, um den Dialog zu schließen, sind Sie bereits gescheitert. Der Grund dafür, dass Sie gescheitert sind, ist, dass nicht jeder Trigger click Veranstaltungen. Benutzer, die keine Maus verwenden, können Ihren Dialog (und Ihr Popup-Menü ist wohl eine Art von Dialog) verlassen, indem sie Tab und können dann den Inhalt des Dialogs nicht mehr lesen, ohne eine click Veranstaltung.

Lassen Sie uns also die Frage neu formulieren.

Wie kann ein Dialog geschlossen werden, wenn der Benutzer damit fertig ist?

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

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

focusout Veranstaltung

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

Hinweis: Seien Sie vorsichtig mit dem blur Veranstaltung, blur wird nicht weitergegeben, wenn das Ereignis an die Blasenbildungsphase gebunden war!

jQuery's focusout reicht völlig aus. Wenn Sie jQuery nicht verwenden können, dann können Sie blur während der Erfassungsphase:

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

Bei vielen Dialogen müssen Sie außerdem zulassen, dass der Container den Fokus erhält. hinzufügen tabindex="-1" damit das Dialogfeld den Fokus dynamisch erhalten kann, ohne dass der Tabbing-Fluss unterbrochen wird.

$('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, werden Sie schnell Probleme feststellen.

Der erste ist, dass der Link im Dialog nicht anklickbar ist. Der Versuch, darauf zu klicken oder mit der Tabulatortaste darauf zu wechseln, führt dazu, dass der Dialog geschlossen wird, bevor die Interaktion stattfindet. Das liegt daran, dass das Fokussieren des inneren Elements eine focusout Ereignis vor dem Auslösen eines focusin Veranstaltung wieder.

Die Lösung besteht darin, die Zustandsänderung in der Ereignisschleife in eine Warteschlange zu stellen. Dies kann geschehen durch die Verwendung von setImmediate(...) , oder setTimeout(..., 0) bei Browsern, die keine Unterstützung für setImmediate . Einmal in die Warteschlange gestellt, kann sie durch eine nachfolgende 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 der Link erneut gedrückt wird. Das liegt daran, dass das Dialogfeld den Fokus verliert, wodurch das Schließverhalten ausgelöst wird, woraufhin der Link-Klick das Dialogfeld wieder öffnet.

Ähnlich wie bei der vorherigen Frage muss der Fokusstatus verwaltet werden. Da die Zustandsänderung bereits in die Warteschlange gestellt wurde, müssen lediglich die Fokus-Ereignisse der Dialog-Trigger verarbeitet 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 dachten, dass Sie mit der Handhabung der Fokuszustände fertig sind, können Sie noch mehr tun, um die Benutzererfahrung zu vereinfachen.

Dies ist oft eine "nice to have" Funktion, aber es ist üblich, dass wenn Sie ein Modal oder Popup jeglicher Art haben, dass die Esc Taste wird sie geschlossen.

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 das erste Menüelement 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 Unterstützung für Barrierefreiheit

Diese Antwort deckt hoffentlich die Grundlagen der Tastatur- und Mausunterstützung für diese Funktion ab, aber da sie bereits ziemlich umfangreich ist, werde ich jede Diskussion über WAI-ARIA-Rollen und -Attribute aber ich sehr empfehlen den Implementierern, in der Spezifikation nachzulesen, welche Rollen sie verwenden sollten und welche anderen Attribute geeignet sind.

171voto

Dennis Punkte 3388

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

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

Bearbeiten: Einfache Javascript-Variante (2021-03-31)

Ich habe diese Methode verwendet, um ein Dropdown-Menü zu schließen, wenn ich außerhalb des Menüs klicke.

Zunächst habe ich einen benutzerdefinierten Klassennamen für alle Elemente der Komponente erstellt. Dieser Klassenname wird zu allen Elementen hinzugefügt, aus denen das Menü-Widget besteht.

const className = `dropdown-${Date.now()}-${Math.random() * 100}`;

Ich erstelle eine Funktion, die nach Klicks und dem Klassennamen des angeklickten Elements sucht. Wenn das angeklickte Element nicht den benutzerdefinierten Klassennamen enthält, den ich oben generiert habe, sollte die Funktion den show Flagge zu false und das Menü wird geschlossen.

const onClickOutside = (e) => {
  if (!e.target.className.includes(className)) {
    show = false;
  }
};

Dann habe ich den Click-Handler an das Window-Objekt angehängt.

// add when widget loads
window.addEventListener("click", onClickOutside);

... und zum Schluss noch etwas Haushaltsführung

// remove listener when destroying the widget
window.removeEventListener("click", onClickOutside);

153voto

Cezar Augusto Punkte 7479

Wir schreiben das Jahr 2020 und Sie können event.composedPath()

Von: https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath

Die Methode composedPath() der Ereignisschnittstelle gibt den Pfad des Ereignisses zurück, bei dem es sich um ein Array der Objekte handelt, für die Listener aufgerufen werden sollen.

const target = document.querySelector('#myTarget')

document.addEventListener('click', (event) => {
  const withinBoundaries = event.composedPath().includes(target)

  if (withinBoundaries) {
    target.innerText = 'Click happened inside element'
  } else {
    target.innerText = 'Click happened **OUTSIDE** element'
  } 
})

/* just to make it good looking. you don't need this */
#myTarget {
  margin: 50px auto;
  width: 500px;
  height: 500px;
  background: gray;
  border: 10px solid black;
}

<div id="myTarget">
  click me (or not!)
</div>

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X