CSS-Übergänge bei Auto-Dimensionen

Avatar of Brandon Smith
Brandon Smith am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Wir kennen das alle. Sie haben ein Element, das Sie mit CSS-Übergängen glatt ein- und ausklappen möchten, aber seine erweiterte Größe muss vom Inhalt abhängig sein. Sie haben transition: height 0.2s ease-out eingestellt. Sie haben eine CSS-Klasse collapsed erstellt, die height: 0 anwendet. Sie probieren es aus und… die Höhe übergreift nicht. Sie springt zwischen den beiden Größen hin und her, als ob transition nie eingestellt worden wäre. Nach einigem Herumprobieren stellen Sie fest, dass dieses Problem nur auftritt, wenn die Höhe anfänglich oder am Ende auto ist. Prozente, Pixelwerte, alle absoluten Einheiten funktionieren wie erwartet. Aber all diese erfordern das Vorkodieren einer bestimmten Höhe, anstatt zuzulassen, dass sie natürlich aus der Größe des Elementinhalts resultiert.

Nikita Vasilyev hat das gut dokumentiert.

In diesem Artikel spreche ich der Einfachheit halber hauptsächlich über height, aber alles hier gilt auch für width.

Wenn Sie hofften, dass ich eine magische, vollständige Lösung für dieses Problem hätte, tut es mir leid, Sie enttäuschen zu müssen. Es gibt keine einzige Lösung, die den gewünschten Effekt ohne Nachteile erzielt. Es gibt jedoch mehrere Workarounds, die jeweils mit unterschiedlichen Vor- und Nachteilen verbunden sind, und in den meisten Anwendungsfällen wird mindestens einer davon die Aufgabe in einer akzeptablen Weise erledigen. Ich werde die wichtigsten skizzieren und ihre Vor- und Nachteile auflisten, damit Sie hoffentlich die beste für Ihre Situation auswählen können.

Warum wurde dieses Problem nicht auf Browser-Ebene behoben?

Laut den Dokumenten des Mozilla Developer Network wurden Auto-Werte absichtlich von der CSS-Transitions-Spezifikation ausgeschlossen. Es scheint, dass dies von ein paar Leuten angefordert wurde, aber wenn man darüber nachdenkt, ist es zumindest ein wenig sinnvoll, dass es nicht enthalten war. Der Browserprozess, der die Größen und Positionen aller Elemente basierend auf ihrem Inhalt und ihrer Interaktion miteinander neu berechnet (bekannt als „Reflow“), ist aufwendig. Wenn Sie ein Element in eine height von auto überleiten würden, müsste der Browser für jede Phase dieser Animation einen Reflow durchführen, um zu bestimmen, wie sich alle anderen Elemente bewegen sollen. Dies könnte nicht einfach zwischengespeichert oder berechnet werden, da die Start- und/oder Endwerte erst im Moment des Übergangs bekannt sind. Dies würde die zugrunde liegende Mathematik erheblich verkomplizieren und wahrscheinlich die Leistung beeinträchtigen, was für den Entwickler möglicherweise nicht offensichtlich ist.

Technik 1: max-height

Wenn Sie dieses Problem im Internet suchen, wird der Ansatz mit max-height wahrscheinlich in allen ersten fünf bis zehn Ergebnissen erwähnt. Er ist eigentlich ziemlich unideal, aber ich dachte, er wäre der Vollständigkeit halber erwähnenswert.

Es funktioniert so: CSS-Werte können nur zu und von festen Einheitenwerten übergeleitet werden. Aber stellen Sie sich vor, wir haben ein Element, dessen height auf auto gesetzt ist, dessen max-height aber auf einen festen Wert gesetzt ist; sagen wir, 1000px. Wir können height nicht überleiten, aber wir können max-height überleiten, da es einen expliziten Wert hat. Zu jedem gegebenen Zeitpunkt ist die tatsächliche Höhe des Elements das Minimum von height und max-height. Solange der Wert von max-height größer ist als der Wert, den auto ergibt, können wir einfach max-height überleiten und eine Version des gewünschten Effekts erzielen.

Es gibt zwei entscheidende Nachteile

Einer ist offensichtlich, der andere subtil. Der offensichtliche Nachteil ist, dass wir immer noch eine maximale Höhe für das Element vordefinieren müssen, auch wenn wir die Höhe selbst nicht vordefinieren müssen. Je nach Situation können Sie vielleicht garantieren, dass Sie nicht mehr Höhe als diese benötigen. Wenn nicht, ist das ein ziemlich großer Kompromiss. Der zweite, weniger offensichtliche Nachteil ist, dass die Übergangslänge nicht tatsächlich dem entspricht, was Sie angeben, es sei denn, die Inhaltsgröße entspricht exakt der max-height. Wenn Ihr Inhalt beispielsweise 600 Pixel hoch ist und Ihre max-height von 0px auf 1000px mit einer Dauer von 1 Sekunde überleitet, wie lange dauert es dann, bis das Element 600 Pixel erreicht? 0,6 Sekunden! Die max-height wird weiter überleiten, aber die tatsächliche Höhe wird sich nicht mehr ändern, sobald sie das Ende ihres Inhalts erreicht hat. Dies wird noch stärker ausgeprägt sein, wenn Ihr Übergang eine nichtlineare Timing-Funktion verwendet. Wenn der Übergang am Anfang schnell und am Ende langsam ist, dehnt sich Ihr Abschnitt schnell aus und zieht sich langsam zusammen. Nicht ideal. Dennoch sind Übergänge relativ subjektiv, sodass in Fällen, in denen diese Technik anderweitig geeignet ist, ein akzeptabler Kompromiss sein könnte.

Technik 2: transform: scaleY()

Wenn Sie mit der Eigenschaft transform nicht vertraut sind, ermöglicht sie Ihnen, GPU-gesteuerte Transformationen (Translate, Skalieren, Drehen usw.) auf ein Element anzuwenden. Es ist wichtig, ein paar Dinge über die Natur dieser Transformationen zu beachten:

  1. Sie operieren auf der visuellen Darstellung des Elements, als wäre es einfach ein Bild und nicht ein DOM-Element. Das bedeutet zum Beispiel, dass ein zu stark skaliertes Element verpixelt aussieht, da sein DOM ursprünglich auf weniger Pixel gerendert wurde, als es nun überspannt.
  2. Sie lösen keine Reflows aus. Wiederum kennt oder kümmert sich die Transformation nicht um die DOM-Struktur des Elements, sondern nur um das „Bild“, das der Browser davon gezeichnet hat. Dies ist sowohl der Grund, warum diese Technik funktioniert, als auch ihr größter Nachteil.

Die Implementierung funktioniert so: Wir setzen einen transition für die transform-Eigenschaft des Elements und wechseln dann zwischen transform: scaleY(1) und transform: scaleY(0). Diese bedeuten jeweils: „Dieses Element mit der gleichen Skalierung (auf der Y-Achse) rendern, mit der es beginnt“ und „Dieses Element mit einer Skalierung von 0 (auf der Y-Achse) rendern“. Der Übergang zwischen diesen beiden Zuständen wird das Element sauber zu und von seiner natürlichen, inhaltsbasierten Größe „quetschen“. Als Bonus werden sich sogar die Buchstaben und/oder Bilder im Inneren visuell „quetschen“, anstatt hinter die Grenzen des Elements zu gleiten. Der Nachteil? Da kein Reflow ausgelöst wird, werden die Elemente um dieses Element herum völlig unberührt bleiben. Sie werden weder bewegt noch skaliert, um den leeren Raum zu füllen.

Die Vor- und Nachteile dieses Ansatzes sind gravierend

Er wird entweder sehr gut für Ihren Anwendungsfall funktionieren oder überhaupt nicht geeignet sein.

Dies hängt hauptsächlich davon ab, ob dem fraglichen Element im Fluss des Dokuments weitere Elemente folgen oder nicht. Zum Beispiel funktioniert etwas, das wie ein Modal oder ein Tooltip über dem Hauptdokument schwebt, auf diese Weise perfekt. Es würde auch für ein Element am Ende des Dokuments funktionieren. Aber leider wird es in vielen Situationen nicht ausreichen.

Technik 3: JavaScript

Das Verwalten eines CSS-Übergangs in CSS wäre ideal, aber wie wir lernen, ist es manchmal einfach nicht vollständig möglich.

Wenn Sie unbedingt glatt zusammenklappende Abschnitte haben müssen, deren erweiterte Größe vollständig von ihrem Inhalt bestimmt wird und um die sich andere Elemente auf der Seite während des Übergangs bewegen, können Sie dies mit etwas JavaScript erreichen.

Die grundlegende Strategie besteht darin, manuell das zu tun, was der Browser verweigert: die volle Größe des Elementinhalts zu berechnen und dann das Element per CSS-Übergang auf diese explizite Pixelgröße zu setzen.

Lassen Sie uns das ein wenig aufschlüsseln. Das Erste, was man bemerken sollte, ist, dass wir mit dem Attribut data-collapsed verfolgen, ob der Abschnitt gerade eingeklappt ist. Dies ist notwendig, damit wir wissen, was wir jedes Mal mit dem Element „tun“ müssen, wenn seine Erweiterung umgeschaltet wird. Wenn dies eine React- oder Angular-App wäre, wäre dies eine Zustandsvariable.

Das Nächste, was auffallen könnte, ist die Verwendung von requestAnimationFrame(). Dies ermöglicht es Ihnen, eine Callback-Funktion beim nächsten Neurendern des Browsers auszuführen. In diesem Fall verwenden wir es, um darauf zu warten, etwas zu tun, bis die gerade gesetzte Stilart Wirkung gezeigt hat. Dies ist wichtig, wenn wir die Höhe des Elements von auto auf den entsprechenden expliziten Pixelwert ändern, da wir hier nicht auf einen Übergang warten wollen. Wir müssen also den Wert von transition löschen, dann height setzen und transition wiederherstellen. Wenn dies aufeinanderfolgende Zeilen im Code wären, wäre das Ergebnis, als wären sie alle gleichzeitig gesetzt worden, da der Browser nicht parallel zur JavaScript-Ausführung rendert (zumindest nicht für unsere Zwecke).

Die andere Besonderheit ist, wo wir height wieder auf auto setzen, sobald die Erweiterung abgeschlossen ist. Wir registrieren einen Event-Listener mit transitionend, der ausgelöst wird, wann immer ein CSS-Übergang endet. Innerhalb dieses Event-Listeners entfernen wir ihn (da er nur auf den unmittelbar folgenden Übergang reagieren soll) und entfernen dann height aus den Inline-Stilen. Auf diese Weise wird die Elementgröße wieder so definiert, wie es die normalen Stile der Seite vorsehen. Wir wollen nicht davon ausgehen, dass sie die gleiche Pixelgröße behalten sollte oder sogar, dass sie auto-dimensioniert bleiben sollte. Wir möchten, dass unser JavaScript den Übergang durchführt und sich dann zurückzieht und nicht mehr als nötig stört.

Der Rest ist ziemlich unkompliziert. Und wie Sie sehen können, erzielt dies genau das gewünschte Ergebnis. Dennoch gibt es trotz bester Bemühungen recht viele Möglichkeiten, wie dies unseren Code zerbrechlicher und potenziell fehleranfälliger macht.

  • Wir haben 27 Zeilen Code statt 3 hinzugefügt
  • Änderungen an Dingen wie padding oder border-box in unserem Abschnittselement könnten Änderungen an diesem Code erfordern.
  • CSS-Übergänge für das Element, die zufällig enden, während der Höhenübergang noch läuft, könnten dazu führen, dass die Höhe nicht auf ihren Standardwert zurückgesetzt wird.
  • Das Deaktivieren von transition für einen Frame könnte andere Übergänge an diesem Element stören, die zufällig gleichzeitig laufen.
  • Wenn ein Fehler dazu führen würde, dass der height-Stil des Elements nicht mehr mit seinem data-collapsed-Attribut übereinstimmt, könnte sein Verhalten Probleme bereiten.

Darüber hinaus ist der Code, den wir geschrieben haben, prozedural statt deklarativ, was ihn von Natur aus fehleranfälliger und komplexer macht. Trotzdem muss unser Code manchmal das tun, was er tun muss, und wenn es die Kompromisse wert ist, dann ist es die Kompromisse wert.

Bonus-Technik: Flexbox

Ich nenne diese Technik einen Bonus, weil sie das gewünschte Verhalten technisch nicht erreicht. Sie bietet eine alternative Möglichkeit, die Größen Ihrer Elemente zu bestimmen, die in vielen Fällen ein sinnvoller Ersatz sein kann und die Übergänge vollständig unterstützt.

Möglicherweise möchten Sie sich mit Flexbox und flex-grow vertraut machen, bevor Sie diesen Abschnitt lesen, wenn Sie damit noch nicht vertraut sind.

Flexbox ist ein extrem leistungsfähiges System zur Verwaltung der Größenanpassung Ihrer Benutzeroberfläche an verschiedene Situationen. Viele Artikel wurden darüber geschrieben, und ich werde nicht ins Detail gehen. Was ich tun werde, ist die weniger erwähnte Tatsache, dass die flex-Eigenschaft und verwandte Eigenschaften Übergänge vollständig unterstützen!

Das bedeutet, dass, wenn Ihr Anwendungsfall es Ihnen erlaubt, die Größenbestimmung mithilfe von Flexbox anstelle der Inhaltsgröße zu bestimmen, das reibungslose Zusammenklappen eines Abschnitts so einfach ist wie das Setzen von transition: flex 0.3s ease-out und das Umschalten von flex: 0. Immer noch nicht so gut wie inhaltsbasiert, aber mehr flexibel (ich weiß, ich weiß, es tut mir leid) als Pixelgrößen zu verwenden und zurückzukehren.