Accordion Animations
Don’t use display none
Section titled “Don’t use display none”Im nächsten Schritt wollen wir unser Accordeon animieren. Dazu müssen wir wissen: das geht nicht, wenn wir display: none zum Verstecken des Inhalts verwenden. display: none lässt sich nicht animieren (bzw. jetzt schon über einen Umweg, aber ob das hier Sinn macht, müsste noch ausprobiert werden).
Um Elemente zu verstecken, könnten wir sie entweder aus dem sichtbaren Bereich hinausschieben (sie sind dann aber immer noch fokussierbar bzw. über AT erreichbar!) oder mit visibility: hidden verstecken (das Element nimmt dann immer noch seinen Platz im DOM ein!).
Beim Accordeon verstecken wir die entsprechenden Elemente über die nicht sichtbare Höhe. Da wir die Höhe animieren wollen, können wir den Accordeon-Inhalt im geschlossenen Zustand mit height: 0 und overflow: hidden verstecken (siehe weiter unten – Calculating Height). Der Inhalt ist dann nicht sichtbar, ist aber im DOM weiterhin vorhanden (und kann auch durchsucht werden).
Animating the height - Jank-Issue
Section titled “Animating the height - Jank-Issue”Wenn wir eine Eigenschaft wie height animieren, erzeugt das jank (TODO: setze einen Link zu den Animations, wenn sie da sind), aber in dem Fall haben wir keine andere Wahl.
Als erstes setzten wir die Höhe des Accordeon-Inhalts auf Null:
.accordion-content { height: 0;}Jetzt schaut’s ein bissl zerrüttelt aus … der Inhalt macht einen Overflow. Warum ist das so?
Exkurs: Calculating Height (and Width)
Section titled “Exkurs: Calculating Height (and Width)”Höhen werden in CSS normalerweise auf Basis der box-sizing Eigenschaft kalkuliert:
box-sizing: content-boxbedeutet, dass die Höhe nur so hoch wie der Inhalt selbst ist (das ist gleichzeitig der default Wert)box-sizing: boder-boxzählt zur Höhe des Inhalts nochpaddingundborderdazu.
Calculations for height change if you set height explicitly
Section titled “Calculations for height change if you set height explicitly”- Wenn
heightgrößer ist alspadding+border, entspricht die Höhe dem gesetzten Wert. - Wenn
heightgleich oder kleiner ist alspadding+border, setzt der Browser die Höhe auf den Wert vonpadding+border.
Wenn height zu klein für den Inhalt (oder überhaupt Null ist), wird trotzdem ein Teil vom Inhalt sichtbar sein, weil der overflow der Box by default auf visible steht. Oder anders ausgedrückt: wenn der Inhalt größer ist als der reservierte Platz dafür, dann darf er überlaufen.
Fixing the height issue
Section titled “Fixing the height issue”Um alle Inhalte zu verstecken, müssten wir also nicht nur height, sondern auch padding und, falls vorhanden, border auf Null setzen.
Daraus ergibt sich, dass wir nicht nur height, sondern auch padding und eventuell border animieren müssten. Das würde aber zu noch mehr jank führen.
Die Lösung? Wir passen das HTML an und wrappen den Accordeon-Content in ein weiteres <div>. Dessen Höhe wird animiert, die Elemente innerhalb dieses <div> können padding und border haben.
<div class="accordion-content"> <div class="accordion-inner"> <!-- content --> </div></div>Das CSS wird angepasst:
.accordion-content { height: 0; overflow: hidden; transition: height 0.3s ease-out; .is-open & { height: auto; }}Animating the height
Section titled “Animating the height”height soll jetzt also animiert werden, allerdings funktioniert das nicht, wenn heightauf auto gesetzt ist. Wir müssen die Höhe auf einen bestimmten Wert setzen. Den exakten Wert kennen wir nicht, können ihn aber mit JavaScript ermitteln und ihn dann für das jeweilige Accordeon einsetzen.
Getting the correct height
Section titled “Getting the correct height”Die Höhe eines Elements ermitteln wir mit getBoundingClientRect(). Dazu müssen wir aber zuerst das Element bestimmen, das uns die Höhe verraten soll. .accordion-content haben wir auf eine Höhe von Null gesetzt, aber die Höhe des Inhalts, .accordion-inner, bleibt davon unberührt.
.accordion-inner ist ein Kindelement von .accordion-content, und .accordion-content ist ein Geschwisterelement des geklickten .accordion-header. Wir ergänzen unseren EventListener:
accordionContainer.addEventListener('click', e => { const accordionHeader = e.target.closest('.accordion-header'); if (accordionHeader) { // ... const accordionContent = accordionHeader.nextElementSibling; const accordionContentInner = accordionContent.children[0]; const height = accordionContentInner.getBoundingClientRect().height; }});Setting the correct height
Section titled “Setting the correct height”Die ermittelte Höhe muss nun noch ins HTML. Wir setzen die height Property ebenfalls mit JavaScript, also eine style property, zu der wir noch ein px hinzufügen müssen, weil getBoundingClientRect() Pixelwerte zurückgibt:
accordionContent.style.height = `${height}px`;Closing the accordion
Section titled “Closing the accordion”Um das Accordeon wieder zu schließen, müssen wir den inline-Style auf Null zurücksetzen. (inline-Styles haben eine höhere Spezifität als die CSS-Klasse!)
Wir prüfen also, ob das Accordeon offen oder geschlossen ist und setzen den Wert der Höhe entsprechend ein:
accordionContainer.addEventListener('click', e => { const accordionHeader = e.target.closest('.accordion-header'); if (accordionHeader) { // ... if (accordion.classList.contains(is-open)) { accordioncontent.style.height = 0; } else { accordionContent.style.height = `${height}px`; }
accordion.classList.toggle('is-open'); }});Wir können den Code etwas klarer machen, indem wir height vorab initialisieren und der Variablen erst später einen Wert zuweisen:
let height;
if (accordion.classList.contains('is-open')) { height = 0;} else { height = accordionContent.getBoundingClientRect().height;}
accordion.classList.toggle('is-open');accordionContent.style.height = `${height}px`;