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.