Fokus in bestimmtem Website-Bereich fangen

Manchmal kann es sinnvoll sein, den Fokus in einem bestimmten Website-Bereich zu fangen, sodass kein fokussierbares Element außerhalb dieses Bereichs angesteuert werden kann (zum Beispiel über die Tab-Taste der Tastatur). Hier zeige ich, wie sich das umsetzen lässt.

Die Ausgangslage

Ich habe nach einer Lösung für das angesprochene Problem gesucht, als ich mein Fanoe-Theme überarbeitet habe. Das Theme kommt mit einer Off-Canvas-Sidebar, die seit der Überarbeitung nicht mehr den Inhalt mit nach links verschiebt, sondern darüber eingeblendet wird, wie in folgendem GIF zu sehen:

Wenn die Sidebar eingeblendet ist, soll der Fokus in der Sidebar gefangen sein, sodass ein Tastatur-User andere Fokus-Elemente außerhalb des Sidebar-Elements nicht ansteuern kann, bevor die Sidebar über den Button oder die Esc-Taste wieder ausgeblendet wird.

Umsetzung der Lösung mit ally.js

Die JavaScript-Bibliothek ally.js bietet eine Lösung für das Problem an und veranschaulicht die Funktionsweise in einem Tutorial. In meinem Theme sieht das entscheidende Markup so aus:

<button class="sidebar-button -open">
	<span class="screen-reader-text">Show Sidebar</span>
	<span aria-hidden="true">≡</span>
</button>
<aside class="sidebar" role="sidebar">
	<button class="sidebar-button -close">
		<span class="screen-reader-text">Close Sidebar</span>
		<span aria-hidden="true">≡</span>
	</button>
	<div class="sidebar-content">
		<!-- The widgets -->
	</div>
</aside>

Und das ist der wichtige JS-Teil (ich nutze Webpack, um den import aufzulösen, und Babel, um ES6-Syntax in ES5 umzuwandeln – ich habe einen kleinen Beitrag zur Einrichtung davon geschrieben):

import ally from 'ally.js/ally';

/**
 * Custom JavaScript functions.
 *
 * @version 2.0.3
 *
 * @package Fanoe
 */
{
	/**
	 * Get the html element.
	 *
	 * @type {Element}
	 */
	const root = document.documentElement;

	/**
	 * Get the elements for sidebar handling.
	 */
	const openButton = document.querySelector('.sidebar-button.-open');
	const closeButton = document.querySelector('.sidebar-button.-close');
	const sidebarElem = document.querySelector('.sidebar');
	let disabledHandle;
	let tabHandle;
	let keyHandle;

	/**
	 * Call sidebar function sidebar button click.
	 */
	openButton.addEventListener('click', sidebar, false);

	/**
	 * Call sidebar function sidebar button click.
	 */
	closeButton.addEventListener('click', sidebar, false);

	/**
	 * Catch clicks on the document to close sidebar on mouse click
	 * outside the open sidebar.
	 */
	document.body.addEventListener('click', function (e) {
		/**
		 * Check if the sidebar is visible.
		 */
		if (root.classList.contains('active-sidebar')) {
			/**
			 * Check if the click was made on the .sidebar element, not the .sidebar-content.
			 */
			if (e.explicitOriginalTarget.classList.contains('sidebar')) {
				disabledHandle.disengage();
				tabHandle.disengage();
				root.classList.remove('active-sidebar');
			}
		}
	}, false);

	/**
	 * Function to display and hide sidebar.
	 *
	 * @link https://allyjs.io/tutorials/accessible-dialog.html
	 */
	function sidebar() {
		/**
		 * Toggle .active-sidebar class.
		 */
		root.classList.toggle('active-sidebar');

		/**
		 * Check for class name to know if the
		 * sidebar is currently visible or not.
		 */
		if (root.classList.contains('active-sidebar')) {
			disabledHandle = ally.maintain.disabled({
				filter: sidebarElem,
			});

			tabHandle = ally.maintain.tabFocus({
				context: sidebarElem,
			});

			keyHandle = ally.when.key({
				escape: closeSidebarByKey,
			});
		} else {
			disabledHandle.disengage();
			tabHandle.disengage();
		}
	}
}

Der Code orientiert sich stark an dem oben verlinkten Tutorial. Zunächst importieren wir die ally-Bibliothek, holen das Root-Element und speichern die Buttons und das Sidebar-Element. Danach fügen wir die benötigten EventListener hinzu:

  • Klick auf die beiden Buttons. Davon wird die sidebar()-Funktion aufgerufen, um die Sidebar anzuzeigen oder auszublenden.
  • Klick auf das body-Element. Nach einem Klick wird geprüft, ob die Sidebar gerade angezeigt wird. In dem Fall wird danach geguckt, ob der Klick auf das .sidebar-Element ausgeführt wurde. Wenn das so ist, wurde der Klick auf den ausgegrauten Bereich außerhalb des Sidebar-Inhalts ausgeführt (das liegt daran, weil der graue Bereich über dem Rest der Website durch eine .sidebar::before-Regel erstellt wurde).

In sidebar() wird zunächst für das Root-Element die Klasse active-sidebar hinzugefügt oder entfernt – je nachdem, ob sie vorhanden ist oder nicht. Anschließend wird geprüft, ob die Klasse gerade vorhanden ist (die Sidebar also durch den Klick auf den Anzeigen-Button eingeblendet wurde) und in diesem Fall über ally.maintain.disabled allen Elementen außer denen innerhalb des Sidebar-Elements die Fokus-Möglichkeit entzogen. Über ally.maintain.tabFocus wird danach noch der Tab-Fokus auf die Sidebar gesetzt, damit nach dem letzten fokussierbaren Element der Sidebar nicht zum Browser-UI gesprungen wird, sondern zum ersten Fokus-Element der Sidebar.

Anschließend legen wir fest, dass bei Aufruf der Escape-Taste sidebar() aufgerufen werden soll, um die Sidebar zu schließen. Wenn active-sidebar nicht gesetzt ist, werden die ganzen Beschränkungen wieder rückgängig gemacht.

Das könnte auch interessant sein

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.