Erstellen Sie Ihre eigenen erweiternden und zusammenziehenden Inhaltsfelder

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ In UI/UX ist ein allgemeines Muster, das immer wieder benötigt wird, das einer einfachen animierten Öffnungs- und Schließleiste oder „Schublade“. Sie brauchen keine Bibliothek, um diese zu erstellen. Mit etwas grundlegendem HTML/CSS und JavaScript werden wir lernen, wie man es selbst macht.

Wir haben sie bisher als „Öffnungs- und Schließplatte“ bezeichnet, aber sie werden auch als Erweiterungsplatten oder einfacher als Erweiterungsplatten bezeichnet.

Um genau zu verdeutlichen, wovon wir sprechen, gehen Sie zu diesem Beispiel auf CodePen:

Einfache Schublade ein-/ausblenden (Multiples) von Ben Frain auf CodePen.

Einfache Schublade ein-/ausblenden (Multiples) von Ben Frain auf CodePen.

Das werden wir in diesem kurzen Tutorial bauen.

Aus funktionaler Sicht gibt es einige Möglichkeiten, das animierte Öffnen und Schließen zu erreichen, nach dem wir suchen. Jeder Ansatz mit seinen eigenen Vorteilen und Kompromissen. Ich werde die Details meiner „go-to“-Methode in diesem Artikel im Detail teilen. Betrachten wir zunächst mögliche Ansätze.

Ansätze

Es gibt Variationen dieser Techniken, aber im Großen und Ganzen fallen die Ansätze in eine von drei Kategorien:

  1. Animieren/Übergehen Sie die height oder max-height des Inhalts.
  2. Verwenden Sie transform: translateY , um Elemente an eine neue Position zu verschieben, wodurch die Illusion entsteht, dass sich ein Panel schließt, und rendern Sie dann das DOM erneut, sobald die Transformation mit den Elementen an ihrer endgültigen Position abgeschlossen ist.
  3. Verwenden Sie eine Bibliothek, die eine Kombination/Variation von 1 oder 2 enthält!
Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Überlegungen zu jedem Ansatz

Aus Performance-Sicht ist die Verwendung einer Transformation effektiver als das Animieren oder Übergehen der Höhe/Max-Höhe. Bei einer Transformation werden die sich bewegenden Elemente gerastert und von der GPU verschoben. Dies ist eine billige und einfache Operation für eine GPU, sodass die Leistung tendenziell viel besser ist.

Die grundlegenden Schritte bei der Verwendung eines Transformationsansatzes sind:

  1. Holen Sie sich die Höhe des zu reduzierenden Inhalts.
  2. Verschieben Sie den Inhalt und alles danach um die Höhe des zu reduzierenden Inhalts mit transform: translateY(Xpx) . Führen Sie die Transformation mit dem Übergang Ihrer Wahl durch, um einen ansprechenden visuellen Effekt zu erzielen.
  3. Verwenden Sie JavaScript, um auf das transitionend Ereignis zu lauschen. Wenn es ausgelöst wird, display: none den Inhalt an und entfernen Sie die Transformation, und alles sollte an der richtigen Stelle sein.

Klingt nicht schlecht, oder?

Bei dieser Technik gibt es jedoch eine Reihe von Überlegungen, daher vermeide ich sie für gelegentliche Implementierungen, es sei denn, die Leistung ist absolut entscheidend.

Beispielsweise müssen Sie beim transform: translateY -Ansatz den z-index der Elemente berücksichtigen. Standardmäßig befinden sich die hochtransformierenden Elemente nach dem Triggerelement im DOM und erscheinen daher bei der Hochübersetzung über den Dingen vor ihnen.

Sie müssen auch überlegen, wie viele Dinge nach dem Inhalt erscheinen, den Sie im DOM reduzieren möchten. Wenn Sie kein großes Loch in Ihrem Layout haben möchten, finden Sie es möglicherweise einfacher, JavaScript zu verwenden, um alles, was Sie verschieben möchten, in ein Containerelement zu packen und dieses einfach zu verschieben. Überschaubar, aber wir haben gerade mehr Komplexität eingeführt! Dies ist jedoch die Art von Ansatz, den ich gewählt habe, als ich Spieler in In/Out nach oben und unten bewegt habe. Wie das gemacht wurde, können Sie hier sehen.

Für zwanglosere Bedürfnisse neige ich dazu, die max-height des Inhalts zu ändern. Dieser Ansatz funktioniert nicht so gut wie eine Transformation. Der Grund dafür ist, dass der Browser die Höhe des kollabierenden Elements während des gesamten Übergangs tweent; das verursacht viele Layout-Berechnungen, die für den Host-Computer nicht so billig sind.

Dieser Ansatz gewinnt jedoch unter dem Gesichtspunkt der Einfachheit. Die Auszahlung des oben erwähnten Rechenschlags besteht darin, dass sich der DOM-Reflow um die Position und Geometrie von allem kümmert. Wir haben sehr wenig Berechnungen zu schreiben und das JavaScript, das benötigt wird, um es gut zu machen, ist vergleichsweise einfach.

Der Elefant im Raum: Details und zusammenfassende Elemente

Diejenigen, die sich mit den HTML-Elementen bestens auskennen, werden wissen, dass es eine native HTML-Lösung für dieses Problem in Form von details und summary gibt. Hier ist ein Beispiel-Markup:

 <details> <summary>Click to open/close</summary> Here is the content that is revealed when clicking the summary... </details>

Standardmäßig stellen Browser ein kleines Dreieck neben dem Zusammenfassungselement bereit; Klicken Sie auf die Zusammenfassung und der Inhalt unter der Zusammenfassung wird angezeigt.

Großartig, he? Details unterstützen sogar das toggle Ereignis in JavaScript, sodass Sie so etwas tun können, um verschiedene Dinge auszuführen, je nachdem, ob es geöffnet oder geschlossen ist (machen Sie sich keine Sorgen, wenn diese Art von JavaScript-Ausdruck seltsam erscheint; dazu kommen wir in mehr Detail in Kürze):

 details.addEventListener("toggle", () => { details.open ? thisCoolThing() : thisOtherThing(); })

OK, ich werde Ihre Aufregung genau hier stoppen. Die Details und Zusammenfassungselemente werden nicht animiert. Nicht standardmäßig und es ist derzeit nicht möglich, sie mit zusätzlichem CSS und JavaScript animieren/übergehen zu lassen und zu schließen.

Wenn Sie etwas anderes wissen, würde ich gerne widerlegt werden.

Da wir eine Öffnungs- und Schließästhetik brauchen, müssen wir leider unsere Ärmel hochkrempeln und mit den anderen uns zur Verfügung stehenden Werkzeugen die bestmögliche und zugänglichste Arbeit leisten.

Richtig, mit den deprimierenden Nachrichten aus dem Weg, lasst uns damit fortfahren, diese Sache zu verwirklichen.

Markup-Muster

Das grundlegende Markup sieht folgendermaßen aus:

 <div class="container"> <button type="button" class="trigger">Show/Hide content</button> <div class="content"> All the content here </div> </div>

Wir haben einen äußeren Behälter, um den Expander einzuwickeln, und das erste Element ist der Knopf, der als Auslöser für die Aktion dient. Beachten Sie das type-Attribut in der Schaltfläche? Ich füge das immer ein, da standardmäßig eine Schaltfläche in einem Formular eine Übermittlung durchführt. Wenn Sie feststellen, dass Sie ein paar Stunden damit verschwenden, sich zu fragen, warum Ihr Formular nicht funktioniert und Schaltflächen in Ihrem Formular enthalten sind; Stellen Sie sicher, dass Sie das Typattribut überprüfen!

Das nächste Element nach der Schaltfläche ist die Inhaltsleiste selbst; alles, was Sie verstecken und zeigen möchten.

Um die Dinge zum Leben zu erwecken, verwenden wir benutzerdefinierte CSS-Eigenschaften, CSS-Übergänge und ein wenig JavaScript.

Grundlegende Logik

Die Grundlogik ist folgende:

  1. Lassen Sie die Seite laden, messen Sie die Höhe des Inhalts.
  2. Legen Sie die Höhe des Inhalts auf dem Container als Wert einer benutzerdefinierten CSS-Eigenschaft fest.
  3. Verstecken Sie den Inhalt sofort, indem Sie ihm ein aria-hidden: "true" -Attribut hinzufügen. Die Verwendung von aria-hidden stellt sicher, dass Hilfstechnologien wissen, dass Inhalte ebenfalls verborgen sind.
  4. Verbinden Sie das CSS so, dass die max-height der Inhaltsklasse der Wert der benutzerdefinierten Eigenschaft ist.
  5. Durch Drücken unserer Trigger-Schaltfläche wird die aria-hidden-Eigenschaft von wahr auf falsch umgeschaltet, was wiederum die max-height des Inhalts zwischen 0 und der in der benutzerdefinierten Eigenschaft festgelegten Höhe umschaltet. Ein Übergang auf diesem Grundstück sorgt für das optische Flair – nach Geschmack anpassen!

Hinweis: Dies wäre nun ein einfacher Fall zum Umschalten einer Klasse oder eines Attributs, wenn max-height: auto gleich der Höhe des Inhalts wäre. Leider nicht. Gehen Sie und rufen Sie das W3C hier an.

Schauen wir uns an, wie sich dieser Ansatz im Code manifestiert. Nummerierte Kommentare zeigen die entsprechenden logischen Schritte von oben im Code.

Hier ist das JavaScript:

 // Get the containing element const container = document.querySelector(".container"); // Get content const content = document.querySelector(".content"); // 1. Get height of content you want to show/hide const heightOfContent = content.getBoundingClientRect().height; // Get the trigger element const btn = document.querySelector(".trigger"); // 2. Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { document.documentElement.classList.add("height-is-set"); 3. content.setAttribute("aria-hidden", "true"); }, 0); btn.addEventListener("click", function(e) { container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true"); // 5. Toggle aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true"); })

Das CSS:

 .content { transition: max-height 0.2s; overflow: hidden; } .content[aria-hidden="true"] { max-height: 0; } // 4. Set height to value of custom property .content[aria-hidden="false"] { max-height: var(--containerHeight, 1000px); }

Wichtige Punkte

Was ist mit mehreren Schubladen?

Wenn Sie eine Reihe von Schubladen zum Öffnen und Verbergen auf einer Seite haben, müssen Sie sie alle durchlaufen, da sie wahrscheinlich unterschiedliche Größen haben.

Um dies zu handhaben, müssen wir einen querySelectorAll , um alle Container abzurufen, und dann Ihre Einstellung der benutzerdefinierten Variablen für jeden Inhalt in einem forEach erneut ausführen.

Das setTimeout

Ich habe ein setTimeout mit einer Dauer von 0 , bevor ich den Container so einstelle, dass er ausgeblendet wird. Dies ist wohl unnötig, aber ich verwende es als „Gürtel und Hosenträger“-Ansatz, um sicherzustellen, dass die Seite zuerst gerendert wurde, sodass die Höhen für den Inhalt zum Lesen verfügbar sind.

Feuern Sie dies nur ab, wenn die Seite fertig ist

Wenn Sie andere Dinge am Laufen haben, können Sie Ihren Schubladencode in eine Funktion verpacken, die beim Laden der Seite initialisiert wird. Angenommen, die Schubladenfunktion wäre in eine Funktion namens initDrawers verpackt, könnten wir Folgendes tun:

 window.addEventListener("load", initDrawers);

Tatsächlich werden wir das in Kürze hinzufügen.

Zusätzliche data-*-Attribute für den Container

Es gibt ein Datenattribut im äußeren Container, das ebenfalls umgeschaltet wird. Dies wird hinzugefügt, falls sich beim Öffnen/Schließen der Schublade etwas mit dem Auslöser oder Behälter ändern muss. Zum Beispiel möchten wir vielleicht die Farbe von etwas ändern oder ein Symbol anzeigen oder umschalten.

Standardwert für die benutzerdefinierte Eigenschaft

Für die benutzerdefinierte Eigenschaft in CSS ist ein Standardwert von 1000px . Das ist das Bit nach dem Komma im Wert: var(--containerHeight, 1000px) . Das bedeutet, wenn die --containerHeight auf irgendeine Weise vermasselt wird, sollten Sie immer noch einen anständigen Übergang haben. Sie können das natürlich so einstellen, wie es für Ihren Anwendungsfall geeignet ist.

Warum nicht einfach einen Standardwert von 100000px verwenden?

Angesichts der Tatsache, dass max-height: auto nicht übergeht, fragen Sie sich vielleicht, warum Sie sich nicht einfach für eine festgelegte Höhe mit einem Wert entscheiden, der größer ist, als Sie jemals benötigen würden. Zum Beispiel 10000000px?

Das Problem bei diesem Ansatz ist, dass er immer von dieser Höhe aus wechselt. Wenn Ihre Übergangsdauer auf 1 Sekunde eingestellt ist, bewegt sich der Übergang 10000000 Pixel in einer Sekunde. Wenn Ihr Inhalt nur 50 Pixel hoch ist, erhalten Sie einen ziemlich schnellen Öffnungs-/Schließungseffekt!

Ternärer Operator für Schalter

Wir haben einige Male einen ternären Operator verwendet, um Attribute umzuschalten. Einige Leute hassen sie, aber ich und andere lieben sie. Sie mögen zunächst etwas seltsam und ein wenig „Code-Golf“ erscheinen, aber sobald Sie sich an die Syntax gewöhnt haben, denke ich, dass sie einfacher zu lesen sind als ein Standard-if/else.

Für den Uneingeweihten ist ein ternärer Operator eine verkürzte Form von if/else. Sie sind so geschrieben, dass zuerst überprüft werden muss, dann das ? trennt, was ausgeführt werden soll, wenn die Prüfung wahr ist, und dann das : , um zu unterscheiden, was ausgeführt werden soll, wenn die Prüfung falsch ist.

 isThisTrue ? doYesCode() : doNoCode();

Unsere Attributumschalter funktionieren, indem sie prüfen, ob ein Attribut auf "true" gesetzt ist, und wenn ja, setzen Sie es auf "false" , andernfalls setzen Sie es auf "true" .

Was passiert beim Ändern der Seitengröße?

Wenn ein Benutzer die Größe des Browserfensters ändert, ändert sich mit hoher Wahrscheinlichkeit die Höhe unseres Inhalts. Daher sollten Sie in diesem Szenario möglicherweise die Höheneinstellung für Container erneut ausführen. Jetzt ziehen wir solche Eventualitäten in Betracht, es scheint ein guter Zeitpunkt zu sein, die Dinge ein wenig umzugestalten.

Wir können eine Funktion zum Einstellen der Höhen und eine andere Funktion zum Behandeln der Interaktionen erstellen. Fügen Sie dann zwei Listener im Fenster hinzu; eine für das Laden des Dokuments, wie oben erwähnt, und eine andere, um auf das Größenänderungsereignis zu warten.

Ein bisschen mehr A11Y

Es ist möglich, der Zugänglichkeit ein wenig mehr Beachtung zu schenken, indem Sie die Attribute aria-expanded , aria-controls und aria-labelledby . Dies gibt der unterstützten Technologie einen besseren Hinweis, wenn die Schubladen geöffnet/erweitert wurden. Wir fügen aria-expanded="false" neben aria-controls="IDofcontent" zu unserem Schaltflächen-Markup hinzu, wobei IDofcontent der Wert einer ID ist, die wir dem Inhaltscontainer hinzufügen.

Dann verwenden wir einen anderen ternären Operator, um das aria-expanded Attribut beim Klicken im JavaScript umzuschalten.

Alle zusammen

Mit dem Laden der Seite, mehreren Schubladen, zusätzlicher A11Y-Arbeit und der Behandlung von Größenänderungsereignissen sieht unser JavaScript-Code so aus:

 var containers; function initDrawers() { // Get the containing elements containers = document.querySelectorAll(".container"); setHeights(); wireUpTriggers(); window.addEventListener("resize", setHeights); } window.addEventListener("load", initDrawers); function setHeights() { containers.forEach(container => { // Get content let content = container.querySelector(".content"); content.removeAttribute("aria-hidden"); // Height of content to show/hide let heightOfContent = content.getBoundingClientRect().height; // Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { container.classList.add("height-is-set"); content.setAttribute("aria-hidden", "true"); }, 0); }); } function wireUpTriggers() { containers.forEach(container => { // Get each trigger element let btn = container.querySelector(".trigger"); // Get content let content = container.querySelector(".content"); btn.addEventListener("click", () => { btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false" ? "true" : "false"); container.setAttribute( "data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true" ); content.setAttribute( "aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true" ); }); }); }

Sie können auch hier auf CodePen damit spielen:

Easy Show/Hide Drawer (Multiples) von Ben Frain auf CodePen.

Easy Show/Hide Drawer (Multiples) von Ben Frain auf CodePen.

Zusammenfassung

Es ist möglich, noch einige Zeit weiter zu verfeinern und für immer mehr Situationen zu sorgen, aber die grundlegenden Mechanismen zum Erstellen einer zuverlässigen Öffnungs- und Schließschublade für Ihre Inhalte sollten jetzt in Ihrer Reichweite sein. Hoffentlich sind Sie sich auch einiger Gefahren bewusst. Das details kann nicht animiert werden, max-height: auto tut nicht das, was Sie erhofft haben, Sie können nicht zuverlässig einen massiven max-height-Wert hinzufügen und erwarten, dass alle Inhaltsbereiche wie erwartet geöffnet werden.

Um unseren Ansatz hier zu wiederholen: Messen Sie den Container, speichern Sie seine Höhe als benutzerdefinierte CSS-Eigenschaft, blenden Sie den Inhalt aus und verwenden Sie dann einen einfachen Schalter, um zwischen der max-height von 0 und der Höhe, die Sie in der benutzerdefinierten Eigenschaft gespeichert haben, umzuschalten.

Es ist vielleicht nicht die absolut leistungsstärkste Methode, aber ich habe festgestellt, dass es für die meisten Situationen vollkommen ausreichend ist und davon profitiert, dass es vergleichsweise einfach zu implementieren ist.