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.

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:
- 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.
- 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
paddingoderborder-boxin 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
transitionfü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 seinemdata-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.
Schöne Lösungen hier, ich war in solchen Situationen früher gefangen.
Eigentlich verwende ich eine Lösung, die Opazität und Schriftgröße mischt, wenn der Inhalt nur Text ist, lässt er sich leicht steuern.
Alles, was es braucht, ist, die Schriftgröße und die Opazität zu unterschiedlichen Zeiten zu ändern: Wenn der Inhalt angezeigt wird, erhöht er zuerst die Schriftgröße von 0 auf die gewünschte und ändert dann die Opazität auf 0; wenn Sie ausblenden müssen, wird er zuerst transparent und dann nimmt die Schriftgröße auf 0 ab, was visuell glatter funktioniert :)
Hier ist ein Skizzen-Code, den ich in einem früheren Projekt verwendet habe: http://codepen.io/pedrorivera/pen/ZpAVwk
font-sizeÜbergang verursacht jedoch Reflow.Vielen Dank für die umfassende Zusammenfassung dieses CSS-Transition-Problems. Ich habe den Max-Height-Workaround ein paar Mal verwendet und gedacht, es müsse einen besseren Weg geben. Zumindest weiß ich jetzt, dass es, obwohl es möglicherweise keinen besseren Weg gibt, andere Optionen gibt, die man ausprobieren kann.
Sie könnten die Prüfung
transitioningrobuster machen, indem Sie den Übergangstyp übertransitionEvent.propertyNameüberprüfen und Nicht-Höhen-Übergänge ignorieren.Eine weitere Technik ist die Animation von
clip-path. Sie hat noch keine universelle Unterstützung, aber Jake Archibald sagte, Chrome arbeite daran, sie hardwarebeschleunigt zu machen, sodass sie bald eine attraktive Option sein sollte.Toller Artikel! Ich habe mich gerade mehr mit CSS beschäftigt und das ist wirklich hilfreich.
Um „Technik 3: JavaScript“ oben (vielleicht) zu ergänzen oder eine Alternative anzubieten, verwende ich seit jeher Folgendes für Menü-/vertikale Expander-Übergänge. (Insbesondere mit der Idee, nur jQuery zum Hinzufügen/Entfernen einer Klasse zu verwenden – obwohl in diesem Fall etwas mehr – anstelle von jQuery-Animationen, slideToggle usw.) Ich begrüße alle Hinweise auf Probleme/Nachteile, die jemand sieht.
CSS
(Der Überlaufzustand kann bei Bedarf für mehrstufige Menüs usw. manipuliert werden, und natürlich kann die Anfangshöhe auf auto oder eine andere Alternative gesetzt werden, wenn kein JS vorhanden ist.)
jQuery
(Fällt auf sofortiges Öffnen/Schließen ohne JS zurück.)
Ich habe viele dieser Lösungen mit „Mehr lesen“-Texten ausprobiert, und die Angabe einer Höhe oder maximalen Höhe funktioniert nicht, da der zusätzliche Text von ein paar Sätzen bis zu einer ganzen Seite reichen kann. Zumindest habe ich vorerst aufgehört, Übergänge mit zusammenklappbaren Abschnitten zu verwenden.
Die einfachste Lösung ist wahrscheinlich die, die z. B. von Bootstrap für das mobile Menü verwendet wird (wenn ich mich richtig erinnere). Zumindest habe ich die folgende Lösung schon ein paar Mal mit gutem Ergebnis verwendet. Berechnen Sie einfach die tatsächliche Höhe des umzuschaltenden Elements vor jedem Umschalten und legen Sie sie in einem Inline-Stil als max-height fest. Der Rest ist derselbe – CSS-Übergang zwischen max-height 0 und der inline festgelegten.
Das habe ich schon oft verwendet. Es erfordert jedoch, dass die Elemente sichtbar sind, wenn Sie die Höhen zuerst berechnen.
Was ich mache, ist eine JS-Lösung: Bevor ich das Element einklappe, setze ich seine Höhe über element.scrollHeight und wende dann eine CSS-Klasse mit {height: 0px !important; overflow: hidden} an;
Haben Sie einen Codepen davon? Ich würde es gerne ausprobieren. Klingt interessant.
Ich selbst habe Technik 1 verwendet, aber anstatt die max-height fest zu kodieren, gebe ich ihr eine sehr hohe max-height und verwende kubische Bezier-Kurven auf dem Übergang, um ihm beim Hovern eine langsame zu schnelle und beim Wegbewegen vom Hovern eine schnelle zu langsame Animation zu geben.
http://codepen.io/foxareld/pen/XMgMvP
Ich denke, es ist besser, JavaScript (oder jQuery) zu verwenden, wenn man ein Akkordeon animiert, da die anderen reinen CSS- oder gemischten CSS/JS-Optionen das Ziel nicht vollständig erreichen.
jQuery 3.1.1 verwendet requestAnimationFrame, daher ist die Animation der Höhe ausreichend flüssig.
Das ist der Stand der Technik…
Nur zur Info, es gibt ein offenes Issue im Github der CSS WG: https://github.com/w3c/csswg-drafts/issues/626. Geben Sie gerne ein Daumen hoch, wenn Sie die Unterstützung für eine native Lösung in CSS zeigen möchten.
Wow, dieser Flex-Transitions-Trick ist großartig. Ich habe nie wirklich darüber nachgedacht. So einfach :)
Großartiger Artikel und Dokumentation Brandon. Wir haben gerade ein Paket (react-css-collapse) erstellt, das sehr ähnlich den Techniken ist, die Sie in Technik 3 erklären. Vielleicht findet es jemand hier nützlich :)
Ich habe auch einige Tests für meine zugänglichen Skripte durchgeführt, der Übergang bei max-height ist nicht perfekt, aber er erfüllt seinen Zweck (ich habe eine Kombination aus scaleX und max-height getestet, aber noch keine guten Ergebnisse erzielt). Wenn es auf kleinen Bildschirmen zu kompliziert wird (zu viel Inhalt), können Sie den Übergang entfernen und einfach display: none verwenden.
Ein weiterer wichtiger Punkt für die Zugänglichkeit (wenn Sie fokussierbaren versteckten Inhalt vermeiden möchten): Sie können display nicht animieren. Hier ist der Trick: Sie können visibility mit einer Verzögerung animieren (damit sie am Ende des Übergangs „animiert“ wird).
Aaron Davidson hat sich mit einer Möglichkeit gemeldet, diese Höhenmessungen in JavaScript zu erhalten, und dann die Daten in Form von CSS-Custom-Properties an CSS zurückzugeben, damit sie übergeleitet werden.
Maurice Carey stieß ebenfalls auf den Ansatz mit CSS-Custom-Properties.
Jeroen Sormani hat sich mit diesem Beispiel gemeldet.