Ich weiß, dass dies die dreißigste Antwort auf diese Frage ist, aber ich denke, es lohnt sich, also geht es weiter. Dies ist eine reine CSS- Lösung mit den folgenden Eigenschaften:
- Am Anfang gibt es keine Verzögerung, und der Übergang wird nicht vorzeitig beendet. Wenn Sie in beiden Richtungen (Erweitern und Reduzieren) in Ihrem CSS eine Übergangsdauer von 300 ms angeben, dauert der Übergang 300 ms.
- Es
transform: scaleY(0)
ändert die tatsächliche Höhe (im Gegensatz zu ), also macht es das Richtige, wenn nach dem zusammenklappbaren Element Inhalt vorhanden ist.
- Während (wie in anderen Lösungen) gibt es magische Zahlen (wie „eine Länge auswählen , die höher ist als Ihre Box ist immer los zu sein“), es ist nicht tödlich , wenn Ihre Annahme endet falsch zu sein. Der Übergang sieht in diesem Fall vielleicht nicht besonders gut aus, aber vor und nach dem Übergang ist dies kein Problem: Im erweiterten (
height: auto
) Status hat der gesamte Inhalt immer die richtige Höhe (im Gegensatz zum Beispiel, wenn Sie einen auswählen max-height
, der sich als solche herausstellt zu niedrig). Und im zusammengeklappten Zustand ist die Höhe Null, wie sie sollte.
Demo
Hier ist eine Demo mit drei zusammenklappbaren Elementen unterschiedlicher Höhe, die alle dasselbe CSS verwenden. Möglicherweise möchten Sie auf "ganze Seite" klicken, nachdem Sie auf "Snippet ausführen" geklickt haben. Beachten Sie, dass das JavaScript nur die collapsed
CSS-Klasse umschaltet , es ist keine Messung erforderlich. (Sie können genau diese Demo ohne JavaScript ausführen, indem Sie ein Kontrollkästchen verwenden oder :target
). Beachten Sie auch, dass der Teil des CSS, der für den Übergang verantwortlich ist, ziemlich kurz ist und der HTML-Code nur ein einziges zusätzliches Wrapper-Element erfordert.
$(function () {
$(".toggler").click(function () {
$(this).next().toggleClass("collapsed");
$(this).toggleClass("toggled"); // this just rotates the expander arrow
});
});
.collapsible-wrapper {
display: flex;
overflow: hidden;
}
.collapsible-wrapper:after {
content: '';
height: 50px;
transition: height 0.3s linear, max-height 0s 0.3s linear;
max-height: 0px;
}
.collapsible {
transition: margin-bottom 0.3s cubic-bezier(0, 0, 0, 1);
margin-bottom: 0;
max-height: 1000000px;
}
.collapsible-wrapper.collapsed > .collapsible {
margin-bottom: -2000px;
transition: margin-bottom 0.3s cubic-bezier(1, 0, 1, 1),
visibility 0s 0.3s, max-height 0s 0.3s;
visibility: hidden;
max-height: 0;
}
.collapsible-wrapper.collapsed:after
{
height: 0;
transition: height 0.3s linear;
max-height: 50px;
}
/* END of the collapsible implementation; the stuff below
is just styling for this demo */
#container {
display: flex;
align-items: flex-start;
max-width: 1000px;
margin: 0 auto;
}
.menu {
border: 1px solid #ccc;
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
margin: 20px;
}
.menu-item {
display: block;
background: linear-gradient(to bottom, #fff 0%,#eee 100%);
margin: 0;
padding: 1em;
line-height: 1.3;
}
.collapsible .menu-item {
border-left: 2px solid #888;
border-right: 2px solid #888;
background: linear-gradient(to bottom, #eee 0%,#ddd 100%);
}
.menu-item.toggler {
background: linear-gradient(to bottom, #aaa 0%,#888 100%);
color: white;
cursor: pointer;
}
.menu-item.toggler:before {
content: '';
display: block;
border-left: 8px solid white;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
width: 0;
height: 0;
float: right;
transition: transform 0.3s ease-out;
}
.menu-item.toggler.toggled:before {
transform: rotate(90deg);
}
body { font-family: sans-serif; font-size: 14px; }
*, *:after {
box-sizing: border-box;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div class="menu">
<div class="menu-item">Something involving a holodeck</div>
<div class="menu-item">Send an away team</div>
<div class="menu-item toggler">Advanced solutions</div>
<div class="collapsible-wrapper collapsed">
<div class="collapsible">
<div class="menu-item">Separate saucer</div>
<div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
<div class="menu-item">Ask Worf</div>
<div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
<div class="menu-item">Ask Q for help</div>
</div>
</div>
<div class="menu-item">Sweet-talk the alien aggressor</div>
<div class="menu-item">Re-route power from auxiliary systems</div>
</div>
<div class="menu">
<div class="menu-item">Something involving a holodeck</div>
<div class="menu-item">Send an away team</div>
<div class="menu-item toggler">Advanced solutions</div>
<div class="collapsible-wrapper collapsed">
<div class="collapsible">
<div class="menu-item">Separate saucer</div>
<div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
</div>
</div>
<div class="menu-item">Sweet-talk the alien aggressor</div>
<div class="menu-item">Re-route power from auxiliary systems</div>
</div>
<div class="menu">
<div class="menu-item">Something involving a holodeck</div>
<div class="menu-item">Send an away team</div>
<div class="menu-item toggler">Advanced solutions</div>
<div class="collapsible-wrapper collapsed">
<div class="collapsible">
<div class="menu-item">Separate saucer</div>
<div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
<div class="menu-item">Ask Worf</div>
<div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
<div class="menu-item">Ask Q for help</div>
<div class="menu-item">Separate saucer</div>
<div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
<div class="menu-item">Ask Worf</div>
<div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
<div class="menu-item">Ask Q for help</div>
</div>
</div>
<div class="menu-item">Sweet-talk the alien aggressor</div>
<div class="menu-item">Re-route power from auxiliary systems</div>
</div>
</div>
Wie funktioniert es?
Es gibt tatsächlich zwei Übergänge, die dazu beitragen, dass dies geschieht. Einer von ihnen wechselt margin-bottom
von 0px (im erweiterten Zustand) -2000px
in den reduzierten Zustand (ähnlich dieser Antwort ). Die 2000 hier ist die erste magische Zahl. Sie basiert auf der Annahme, dass Ihre Box nicht höher als diese ist (2000 Pixel scheinen eine vernünftige Wahl zu sein).
Die margin-bottom
alleinige Verwendung des Übergangs hat zwei Probleme:
- Wenn Sie tatsächlich eine Box haben, die höher als 2000 Pixel ist,
margin-bottom: -2000px
wird a nicht alles verbergen - es wird auch im zusammengeklappten Fall sichtbares Material geben. Dies ist eine kleine Korrektur, die wir später vornehmen werden.
- Wenn die tatsächliche Box beispielsweise 1000 Pixel hoch ist und Ihr Übergang 300 ms lang ist, ist der sichtbare Übergang bereits nach etwa 150 ms beendet (oder beginnt in der entgegengesetzten Richtung 150 ms zu spät).
Bei der Behebung dieses zweiten Problems kommt der zweite Übergang ins Spiel, und dieser Übergang zielt konzeptionell auf die Mindesthöhe des Wrappers ab ("konzeptionell", da wir die min-height
Eigenschaft dafür nicht verwenden; dazu später mehr).
Hier ist eine Animation, die zeigt, wie die Kombination des Übergangs am unteren Rand mit dem Übergang der minimalen Höhe, beide von gleicher Dauer, einen kombinierten Übergang von voller Höhe zu Höhe von Null mit derselben Dauer ergibt.
Der linke Balken zeigt, wie der negative untere Rand den Boden nach oben drückt und die sichtbare Höhe verringert. Der mittlere Balken zeigt, wie die Mindesthöhe sicherstellt, dass im kollabierenden Fall der Übergang nicht vorzeitig endet und im expandierenden Fall der Übergang nicht zu spät beginnt. Der rechte Balken zeigt, wie die Kombination der beiden bewirkt, dass die Box in der richtigen Zeit von voller Höhe auf null Höhe übergeht.
Für meine Demo habe ich mich für 50px als oberen Mindesthöhenwert entschieden. Dies ist die zweite magische Zahl, und sie sollte niedriger sein als die Höhe der Box jemals sein würde. 50px scheint ebenfalls vernünftig zu sein; Es ist unwahrscheinlich, dass Sie ein Element sehr oft zusammenklappbar machen möchten, das überhaupt nicht einmal 50 Pixel hoch ist.
Wie Sie in der Animation sehen können, ist der resultierende Übergang kontinuierlich, aber nicht differenzierbar. In dem Moment, in dem die minimale Höhe gleich der vollen Höhe ist, die durch den unteren Rand eingestellt wird, ändert sich die Geschwindigkeit plötzlich. Dies macht sich in der Animation sehr bemerkbar, da für beide Übergänge eine lineare Timing-Funktion verwendet wird und der gesamte Übergang sehr langsam ist. Im tatsächlichen Fall (meine Demo oben) dauert der Übergang nur 300 ms, und der Übergang am unteren Rand ist nicht linear. Ich habe mit vielen verschiedenen Timing-Funktionen für beide Übergänge herumgespielt, und diejenigen, mit denen ich endete, hatten das Gefühl, dass sie für die unterschiedlichsten Fälle am besten funktionieren.
Zwei Probleme müssen noch behoben werden:
- der Punkt von oben, an dem Felder mit einer Höhe von mehr als 2000 Pixel im reduzierten Zustand nicht vollständig ausgeblendet sind,
- und das umgekehrte Problem, bei dem im nicht ausgeblendeten Fall Felder mit einer Höhe von weniger als 50 Pixel zu hoch sind, selbst wenn der Übergang nicht ausgeführt wird, weil die minimale Höhe sie bei 50 Pixel hält.
Wir lösen das erste Problem, indem wir dem Containerelement max-height: 0
im reduzierten Fall ein a mit einem 0s 0.3s
Übergang geben. Dies bedeutet, dass es sich nicht wirklich um einen Übergang handelt, der jedoch max-height
verzögert angewendet wird. Dies gilt nur, wenn der Übergang beendet ist. Damit dies korrekt funktioniert, müssen wir auch eine Zahl max-height
für den entgegengesetzten, nicht reduzierten Zustand auswählen . Im Gegensatz zum Fall 2000px, bei dem die Auswahl einer zu großen Zahl die Qualität des Übergangs beeinflusst, spielt dies in diesem Fall keine Rolle. Wir können also einfach eine Zahl auswählen, die so hoch ist, dass wir wissen, dass keine Höhe jemals in die Nähe kommen wird. Ich habe eine Million Pixel ausgewählt. Wenn Sie der Meinung sind, dass Sie möglicherweise Inhalte mit einer Höhe von mehr als einer Million Pixel unterstützen müssen, dann 1) tut mir leid und 2) fügen Sie einfach ein paar Nullen hinzu.
Das zweite Problem ist der Grund, warum wir nicht min-height
für den minimalen Höhenübergang verwenden. Stattdessen befindet sich ::after
im Container ein height
Pseudoelement mit einem Übergang von 50px zu Null. Dies hat den gleichen Effekt wie min-height
: Es lässt den Container nicht unter die Höhe schrumpfen, die das Pseudoelement derzeit hat. Aber weil wir height
nicht verwenden min-height
, können wir jetzt max-height
(erneut mit Verzögerung angewendet) die tatsächliche Höhe des Pseudoelements auf Null setzen, sobald der Übergang beendet ist, um sicherzustellen, dass zumindest außerhalb des Übergangs auch kleine Elemente die haben richtige Höhe. Weil min-height
es stärker ist als max-height
, würde dies nicht funktionieren, wenn wir die Container min-height
anstelle der Pseudoelemente verwenden würdenheight
. Genau wie max-height
im vorherigen Absatz max-height
benötigt dies auch einen Wert für das andere Ende des Übergangs. Aber in diesem Fall können wir einfach die 50px auswählen.
Getestet in Chrome (Win, Mac, Android, iOS), Firefox (Win, Mac, Android), Edge, IE11 (mit Ausnahme eines Flexbox-Layout-Problems mit meiner Demo, das ich nicht debuggt habe) und Safari (Mac, iOS) ). Apropos Flexbox: Es sollte möglich sein, diese Funktion ohne Verwendung einer Flexbox auszuführen. Tatsächlich denke ich, dass Sie fast alles in IE7 zum Laufen bringen können - mit Ausnahme der Tatsache, dass Sie keine CSS-Übergänge haben, was es zu einer ziemlich sinnlosen Übung macht.
height:auto/max-height
Lösung wird nur funktionieren, wenn Ihr erweiterter Bereich größer ist als der, denheight
Sie einschränken möchten. Wenn Sie eine habenmax-height
von300px
, aber ein Kombinationsfeld Dropdown - Liste , die zurückkehren50px
, dannmax-height
wird dir nicht helfen,50px
ist variabel , abhängig von der Anzahl der Elemente, können Sie in eine unmögliche Situation kommen , wo ich es nicht reparieren kann , weil dasheight
nicht der Fall ist behoben,height:auto
war die Lösung, aber ich kann keine Übergänge damit verwenden.