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 click
Ereignisse 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 click
Ereignis 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 userisfinishedwiththedialog
Ereignis 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 blur
Ereignis, verbreiten Sie blur
sich nicht, wenn das Ereignis an die Blasenphase gebunden war!
jQuery's focusout
wird gut funktionieren. Wenn Sie jQuery nicht verwenden können, können Sie blur
wä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 focusout
Ereignis 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'));
}
});
$('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. 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'));
}
});
$('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, 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();
}
}
$('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 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();
}
$('.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 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.