JavaScript/Tutorials/Akkordeon mit details

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Text-Info

Lesedauer
20min
Schwierigkeitsgrad
einfach
Vorausgesetztes Wissen
JavaScript


Unter einem Akkordeon (engl. accordion) versteht man einen Aufklappmechanismus, der nur einen Teilbereich der Seite darstellt und erst durch die Interaktion des Benutzers (click, touch) oder eine Tastaturbedienung weitere Teilbereiche aufschiebt und somit sichtbar macht. HTML5 bietet mit dem details-Element eine bequeme, in allen modernen Browsern unterstützte Lösung.

Allerdings gibt es derzeit keine Möglichkeit, den Übergang zwischen open und closed zu animieren. In diesem Tutorial lernen Sie, wie Sie details-Akkordeons mit JavaScript so anreichern, dass sie als Fallback für alte Browser dienen können und mehr Komfort als eine reine HTML-Lösung bieten.

Akkordeon[Bearbeiten]

Information: details und Zugänglichkeit

Es gibt immer wieder Stimmen, die behaupten, dass das details-Element kein Akkordeon wäre. So würden eventuell darin vorhandene Überschriften (h1-h6) vom summary-„button“ geschluckt und wären so nicht mehr zugänglich.[1]

Nach der HTML5-Spezifikation hat das details-Element eine implizierte role="group"[2]. Diese Rolle wird als "Name from author" definiert und benötigt entweder eine summary oder ein aria-label-Attribut oder ein aria-labelledby-Attribut.[3][4]

Ein Akkordeon ist ein sehr nützliches Entwurfsmuster für progressive disclosure (englisch für schrittweise Offenlegung).[5] – wichtige Abschnitte werden hervorgehoben und mit einem Tap oder Klick können, wenn nötig, weitere Details geöffnet werden.

So bleibt das Design übersichtlich und zeigt wesentliche Informationen zuerst, während weiterführende Details leicht erreichbar sind. Oft können so unübersichtliche und gedrängt aussehende Seiten ansprechender präsentiert werden.

Wie so etwas früher aussah, kann man hier sehen:

obsoletes HTML-Markup eines Akkordeons mit aria-labels
<div id="accordion">
  <button aria-expanded="false" aria-controls="collapsible-0">Section 1</button>
  <div id="collapsible-0" aria-hidden="true">
    <p>Inhalt 1</p>
  </div>
  <button aria-expanded="false" aria-controls="collapsible-1">Section 2</button>
  <div id="collapsible-1" aria-hidden="true">
    <p>Inhalt 2</p>
  </div>
</div>
Das Akkordeon besteht aus Buttons und einem dazugehörenden div. Um diesen Zusammenhang auch für Screenreader klarzustellen, erhalten die Buttons ein aria-controls-Attribut, dass auf die id des dazugehörenden divs verweist. Jeder Button erhält ein aria-expanded-Attribut, dass den jeweils geschalteten Zustand anzeigt, was auch mit CSS sichtbar gemacht wird.

Fazit: Die aria-labels gaben den bedeutungslosen buttons und divs einen Sinn, durch das Vorlesen aller aria-Attribute wurde dieser Segen aber oft zum Fluch, da die vielen Attribute doch vom eigentlichen Inhalt ablenkten!

HTML[Bearbeiten]

Als Markup verwenden wir das letzte Beispiel aus dem details-Artikel:

HTML-Markup eines Akkordeons ansehen …
<details> 
   <summary>
        immer sichtbare Überschrift
   </summary>
   <div>
        aufzuklappender Inhalt
   </div>
</details>

Das details-Element erfüllt genau die Anforderungen an ein Akkordeon und ist im Gegenteil zu vielen mit jQuery vorgefertigten, aber auch rein CSS-basierten Beispielen in allen unterstützten Browsern mit seinem Standardverhalten zugänglich, d. h. auch mit der Tastatur zu öffnen und zu schließen.

JavaScript[Bearbeiten]

Die Funktionalität des Beispiels ist bereits vorhanden; mit einem Klick springt das details-Element abrupt von "zu" auf "auf" und umgekehrt. Mit JavaScript können Sie nun zusätzlichen Komfort einbauen:

weicher Übergang[Bearbeiten]

Mit CSS ist eine transition der Höhe nur möglich, wenn beide Zustände feste Werte erhalten, der Wert auto für die „normale“, durch den Inhalt bestimmte Höhe bewirkt wieder ein Ausklappen.

JavaScript soll nun nur den Aufklapp-Mechanismus mit einem weichen Übergang versehen. Dabei soll die Höhe des im details umschlossenen Containers automatisch ermittelt werden.

Ermittlung der Höhe ansehen …
const detailElements = document.querySelectorAll("details"),
style = document.createElement("style");
let stylecontent = "";
detailElements.forEach(function(detailElement,index){
  stylecontent += 
  `details[open]:nth-of-type(${index+1})height:${detailElement.getBoundingClientRect().height}px;}`;
  detailElement.removeAttribute("open");
  stylecontent += 
  `details:nth-of-type(${index+1}){height:${detailElement.getBoundingClientRect().height}px;}`;
})
style.innerText = stylecontent;
document.head.appendChild(style);

Über querySelectorAll werden alle details-Elemente selektiert und einer node list mit Namen detailElements geschrieben.

Zusätzlich wird ein style-Element erzeugt. Mit forEach wird für jedes details-Element der Liste mit getBoundingClientRect() die Höhe der Box einmal im geöffneten und einmal im geschlossenen Zustand ermittelt und jeweils in einem template-literal als CSS-Regelsatz der Variable stylecontent zugewiesen:

`details[open]:nth-of-type(${index+1}){height:${detailElement.getBoundingClientRect().height}px;}`;

CSS-Selektor ist der Elementselektor details mit dem Attribut-Selektor [open] und dann dem nth-of-type-Selektor, der mit index+1 die entsprechende Stelle im DOM erhält.

Diese Anweisungen werden mit innerText in das style-Element geschrieben und dies mit appendChild in den Elementbaum eingehängt.

Beim Öffnen und Schließen können nun die festen Pixel-Werte für die Animation verwendet werden.

beim Öffnen andere Elemente schließen[Bearbeiten]

In diesem Beispiel werden drei details-Elemente in einem div gruppiert. Bei einem Klick auf ein summary-Element wird das betreffende details-Element geöffnet, alle anderen details-Elemente geschlossen:

beim Öffnen andere Elemente schließen ansehen …
document.querySelectorAll('.detailsGroup details')
        .forEach(details => details.addEventListener('toggle', detailsToggler));

function detailsToggler(event) {
   if (!event.target.open) return;
   let container = event.target.closest('.detailsGroup');
   for (let details of container.querySelectorAll('details')) {
      if (details != event.target)
         details.open = false;
   }
}

Mit document.querySelectorAll('…') werden alle Elemente, die zum angegebenen CSS Selektor passen, ausgewählt. In diesem Fall details-Elemente, die ein Element mit class="detailsGroup" als Elternelement haben.

Das Ergebnis von querySelectorAll ist eine NodeList. forEach ruft die Funktion, die da in den Klammern steht, für jeden Eintrag in der NodeList – also für jedes gefundene details-Element, einmal auf.

details => … ist eine Pfeilfunktion und eine Kurzschreibweise für function(details) { return … }. Für jedes details-Element wird addEventListener aufgerufen, um für das toggle-Event die Funktion detailsToggler als Eventhandler zu registrieren.

Die Funktion detailsToggler sucht mittels der closest-Methode das nächstliegende Elternelement, auf das der angegebene CSS Selektor zutrifft. Das ist das div class="detailsGroup". Und darauf wird wiederum mit querySelectorAll die NodeList der enthaltenen details-Elemente ermittelt.

Diese wird nun mit der For..of-Schleife durchlaufen. Nun wird geprüft, ob das gefundene details-Element mit dem Event-Target übereinstimmt. Nur, wenn das nicht der Fall ist, wird es geschlossen. Andernfalls würde das frisch geöffnete details ebenfalls gleich wieder zugemacht.


Quellen[Bearbeiten]

  1. Dave Rupert: Why <details> is Not an Accordion
    tl;dr - <summary> is a button and buttons eat semantics
  2. W3C: details
  3. W3C: ARIA specs
  4. hasellinclusion: Accessible accordions part 2 – using <details> and <summary>
    Sehr ausführlicher Artikel, der die Zugänglichkeit in allen Browsern und Readern testet.
    tl;dr - In NVDA werden state changes nicht vorgelesen. (https://github.com/nvaccess/nvda/issues/8631)
  5. smashing magazine: Designing The Perfect Accordion
    The Barebones Of An Accordion
    sehr ausführlicher Grundlagenartikel, der Anwendungsfälle und besonders die Gestaltung des markers mit vielen Beispielen zeigt.