In diesem Artikel möchte ich Ihnen erklären, wie Sie ein zugängliches Modal implementieren, ohne das Attribut "aria-modal" zu verwenden .
Ein bisschen Theorie!
"Aria-modal" ist ein Attribut, mit dem Hilfstechnologien (z. B. Bildschirmleseprogramme) mitgeteilt werden, dass der Webinhalt im aktuellen Dialogfeld nicht interoperabel (inert) ist. Mit anderen Worten, kein Element unterhalb des Modals sollte den Fokus auf Klicken, TAB / UMSCHALT + TAB-Navigation oder Wischen auf Sensorgeräten erhalten.
Aber warum können wir nicht "aria-modal" für das modale Fenster verwenden?
Es gibt verschiedene Gründe:
- Nur nicht von Screenreadern unterstützt
- von Pseudoklassen ignoriert ": before /: after"
Fahren wir mit der Implementierung fort.
Implementierung
Um mit der Entwicklung zu beginnen, müssen wir die Eigenschaften auswählen, die das verfügbare modale Fenster haben soll :
- Alle interaktiven Elemente außerhalb des modalen Fensters müssen für die Benutzermanipulation blockiert werden: Klicken, Fokussieren usw.
- Die Navigation sollte nur über die Browsersystemkomponenten und den Inhalt des Modals selbst verfügbar sein (alle Inhalte außerhalb des Modals sollten ignoriert werden).
Leer
Wir werden eine Vorlage verwenden, um keine Zeit mit einer schrittweisen Beschreibung der Erstellung eines modalen Fensters zu verschwenden.
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button type="button" id="infoBtn" class="btn"> Standart button </button>
<button type="button" id="openBtn"> Open modal window</button>
<div role="button" tabindex="0" id="infoBtn" class="btn"> Custom button </button>
</div>
<div>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt maxime tenetur sint porro tempore aperiam! Eaque tempore repudiandae culpa omnis placeat, fugit nostrum quisquam in ipsa odit accusamus illum velit?
</div>
<div id="modalWindow" class="modal">
<div>
<button type="button" id="closeBtn" class="btn-close">Close</button>
<h2>Modal window</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita, doloribus.</p>
</div>
</div>
</body>
</html>
Stile:
.modal {
position: fixed;
font-family: Arial, Helvetica, sans-serif;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.8);
z-index: 99999;
transition: opacity 400ms ease-in;
display: none;
pointer-events: none;
}
.active{
display: block;
pointer-events: auto;
}
.modal > div {
width: 400px;
position: relative;
margin: 10% auto;
padding: 5px 20px 13px 20px;
border-radius: 10px;
background: #fff;
}
.btn-close {
padding: 5px;
position: absolute;
right: 10px;
border: none;
background: red;
color: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
.btn {
display: inline-block;
border: 1px solid #222;
padding: 3px 10px;
background: #ddd;
box-sizing: border-box;
}
JS:
let modaWindow = document.getElementById('modalWindow');
document.getElementById('openBtn').addEventListener('click', function() {
modaWindow.classList.add('active');
});
document.getElementById('closeBtn').addEventListener('click', function() {
modaWindow.classList.remove('active');
});
Wenn Sie die Seite öffnen und versuchen, mit den Tasten "TAB / UMSCHALT + TAB" zu den Elementen hinter dem Modal zu navigieren, erhalten diese Elemente den Fokus, wie im beigefügten Bild gezeigt.
Um dieses Problem zu lösen, müssen wir allen interaktiven Elementen ein 'tabindex'-Attribut mit einem Wert von minus eins zuweisen.
1. Erstellen Sie für weitere Arbeiten eine Klasse "modalWindow" mit den folgenden Eigenschaften und Methoden:
- doc - page document. in dem wir ein modales Fenster bauen
- modal - der Container für das modale Fenster
- InteractiveElementsList - Ein Array interaktiver Elemente
- blockElementsList - Ein Array von Seitenblockelementen
- Konstruktor - der Konstruktor der Klasse
- create - Die Methode zum Erstellen des modalen Fensters
- remove - Die Methode zum Entfernen des Modals
2. Implementieren wir den Konstruktor:
constructor(doc, modal) {
this.doc = doc;
this.modal = modal;
this.interactiveElementsList = [];
this.blockElementsList = [];
}
"InteractiveElementsList" und "blockElementsList" werden benötigt, um die Seitenelemente zu enthalten, die beim Erstellen des Modals geändert wurden.
3. Erstellen Sie eine Konstante, in der wir eine Liste aller Elemente speichern, die den Fokus haben können:
const INTERECTIVE_SELECTORS = ['a', 'button', 'input', 'textarea', '[tabindex]'];
4. Wählen Sie in der Methode 'create' alle Elemente aus, die unseren Selektoren entsprechen, und setzen Sie alle 'tabindex = -1' (ignorieren Sie Elemente, die bereits diesen Wert haben).
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
Ein ähnliches Problem tritt auf, wenn wir spezielle Tasten oder Gesten (in mobilen Programmen) für die Navigation verwenden. In diesem Fall können wir nicht nur durch interaktive Elemente, sondern auch durch Text navigieren. Um dies zu beheben, müssen wir
5 hinzufügen . Hier müssen wir kein Array erstellen, das die Selektoren enthält. Wir greifen einfach alle untergeordneten Elemente des Knotens 'body'
let children = this.doc.body.children;
6. Der vierte Schritt ähnelt Schritt 2 und verwendet nur 'aria-hidden'.
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
Abgeschlossene "create" -Methode:
create() {
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
let children = this.doc.body.children;
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
}
7. Im sechsten Schritt implementieren wir die umgekehrte Methode 'create':
remove() {
let element;
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('tabindex', '0');
}
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('aria-gidden', 'false');
}
}
8. Damit dies funktioniert, müssen wir eine Instanz der Klasse "modalWindow" erstellen und die Methoden "create" und "remove" aufrufen:
let modaWindow = document.getElementById('modalWindow');
const modal = new modalWindow(document, modaWindow);
document.getElementById('openBtn').addEventListener('click', function() {
modaWindow.classList.add('active');
// modal.create();
});
document.getElementById('closeBtn').addEventListener('click', function() {
modaWindow.classList.remove('active');
// modal.remove();
});
Vollständiger Klassencode:
class modalWindow{
constructor(doc, modal) {
this.doc = doc;
this.modal = modal;
this.interactiveElementsList = [];
this.blockElementsList = [];
}
create() {
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
let children = this.doc.body.children;
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
}
remove() {
let element;
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('tabindex', '0');
}
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('aria-gidden', 'false');
}
}
PS
Wenn Probleme mit der Navigation in Textelementen auf Mobilgeräten nicht gelöst werden, kann die folgende Auswahl verwendet werden:
const BLOCKS_SELECTORS = ['div', 'header', 'main', 'section', 'footer'];
let children = this.doc.querySelectorAll(BLOCKS_SELECTORS .toString());