Я знаю, що це тридцять щось відповідь на це питання, але я вважаю, що воно того варте, тому ось що. Це єдине CSS- рішення з такими властивостями:
- На початку немає затримки, і перехід не припиняється рано. В обох напрямках (розширення та згортання), якщо ви вказали тривалість переходу у вашій CSS, тривалість переходу становить 300 мс, період.
- Це перехід фактичної висоти (на відміну від
transform: scaleY(0)
), тому він робить правильно, якщо є вміст після збірного елемента.
- Хоча (як і в інших рішеннях) є магічні цифри (наприклад, "виберіть довжину, яка перевищує вашу коробку"), це не фатально, якщо ваше припущення виявиться помилковим. Перехід може не виглядати дивовижним у такому випадку, але до і після переходу це не проблема: у розширеному (
height: auto
) стані весь вміст завжди має правильну висоту (на відміну, наприклад, якщо ви вибрали такий, max-height
який виявляється таким занадто низька). А в згорнутому стані висота дорівнює нулю як слід.
Демо
Ось демонстрація з трьома збірними елементами, різної висоти, які використовують один і той же CSS. Ви, можливо, захочете натиснути "повну сторінку" після натискання "запустити фрагмент". Зауважте, що JavaScript перемикає лише collapsed
клас CSS, вимірювання не бере участь. (Ви можете зробити цю точну демонстрацію без JavaScript взагалі, поставивши прапорець або :target
). Також зауважте, що частина CSS, яка відповідає за перехід, досить коротка, а для HTML потрібен лише один додатковий елемент обгортки.
$(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>
Як це працює?
Насправді два переходи беруть участь у здійсненні цього. Один з них переходить margin-bottom
з 0px (у розгорнутому стані) -2000px
у згорнутий стан (подібно до цієї відповіді ). Ось 2000 рік - це перше магічне число, воно базується на припущенні, що ваше поле не буде вище цього (2000 пікселів здається розумним вибором).
Використання margin-bottom
переходу в поодинці сам по собі має два питання:
- Якщо у вас дійсно є вікно, що перевищує 2000 пікселів, то
margin-bottom: -2000px
приховати все не вийде - вони будуть помітні навіть у згорнутому корпусі. Це незначне виправлення, яке ми зробимо пізніше.
- Якщо власне поле, скажімо, у висоті 1000 пікселів, а ваш перехід триває 300 мс, то видимий перехід вже закінчується приблизно через 150 мс (або, у зворотному напрямку, починається пізніше 150 мс).
Виправлення цього другого питання полягає в тому, де відбувається другий перехід, і цей перехід концептуально орієнтований на мінімальну висоту обгортки ("концептуально", оскільки ми фактично не використовуємо min-height
властивість для цього; докладніше про це пізніше).
Ось анімація, яка показує, як поєднання переходу нижньої межі з мінімальним переходом на висоту, обидва однакової тривалості, дає нам комбінований перехід від повної висоти до нульової висоти, що має однакову тривалість.
Ліва смуга показує, як від'ємне нижнє поле штовхає низ вгору, зменшуючи видиму висоту. На середній смузі показано, як мінімальна висота забезпечує те, що у випадку, що згортається, перехід не закінчується рано, а в розширюваному випадку перехід не починається пізно. Правий рядок показує, як комбінація двох приводить перехід вікна з повної висоти до нульової висоти в потрібний час.
Для демонстрації я встановив 50px як верхнє значення мінімальної висоти. Це друге магічне число, і воно повинно бути нижчим, ніж висота поля. 50px також здається розумним; здається малоймовірним, що вам дуже часто хочеться зробити елемент розбірним, який у першу чергу не має навіть 50 пікселів.
Як ви бачите в анімації, отриманий перехід є безперервним, але він не диференційований - в той момент, коли мінімальна висота дорівнює повній висоті, відрегульованій нижньому краю, відбувається різка зміна швидкості. Це дуже помітно в анімації, оскільки вона використовує лінійну функцію синхронізації для обох переходів і тому, що весь перехід дуже повільний. У фактичному випадку (мій демо вгорі) перехід займає лише 300 мс, а перехід в нижній край не є лінійним. Я грав багато різних функцій синхронізації для обох переходів, і ті, з якими я закінчився, відчували, що вони найкраще працюють для найрізноманітніших випадків.
Для вирішення залишаються дві проблеми:
- точка зверху, де поля з висотою понад 2000 пікселів не повністю заховані в згорнутому стані,
- і зворотна проблема, коли в не прихованому випадку вікна розміром менше 50 пікселів занадто високі, навіть коли перехід не виконується, оскільки мінімальна висота підтримує їх у 50 пікселів.
Першу задачу ми вирішуємо, надаючи контейнерному елементу a max-height: 0
у згорнутому випадку з 0s 0.3s
переходом. Це означає, що це насправді не перехід, але max-height
застосовується із запізненням; він застосовується лише після закінчення переходу. Для правильної роботи нам також потрібно вибрати число max-height
для протилежного стану, що не згортається. Але на відміну від випадку 2000px, коли вибір занадто великої кількості впливає на якість переходу, в цьому випадку це насправді не має значення. Тож ми можемо просто вибрати таке високе число, що знаємо, що жодна висота ніколи не наблизиться до цього. Я вибрав мільйон пікселів. Якщо ви вважаєте, що вам може знадобитися підтримувати вміст висотою понад мільйон пікселів, то 1) Вибачте, і 2) просто додайте пару нулів.
Друга проблема - причина, по якій насправді ми не використовуємо min-height
для переходу мінімальної висоти. Натомість ::after
у контейнері є псевдоелемент, height
який переходить з 50px до нуля. Це має такий же ефект, як і min-height
: Це не дозволить контейнеру скорочуватися нижче, на яку висоту має псевдоелемент. Але оскільки ми використовуємо height
, ні min-height
, ми тепер можемо використовувати max-height
(вкотре застосований із запізненням) для встановлення фактичної висоти псевдоелемента до нуля, коли перехід закінчиться, гарантуючи, що принаймні поза переходом навіть маленькі елементи мають правильна висота. Тому що min-height
це сильніше , ніж max-height
це не буде працювати , якщо ми використовували контейнер min-height
замість псевдо-елементаheight
. Як і max-height
в попередньому абзаці, і цьому max-height
потрібно значення для протилежного кінця переходу. Але в цьому випадку ми можемо просто вибрати 50px.
Тестовано в Chrome (Win, Mac, Android, iOS), Firefox (Win, Mac, Android), Edge, IE11 (за винятком проблеми з макетом flexbox з моєю демонстрацією, що я не турбував налагодження), і Safari (Mac, iOS ). Говорячи про флексокс, слід зробити це можливим без використання будь-якого флексбоксу; насправді я думаю, ви могли б змусити майже все працювати в IE7 - за винятком того, що у вас не буде CSS переходів, що робить це досить безглуздою вправою.
height:auto/max-height
рішення працюватиме лише в тому випадку, якщо ви розширюєте площу більше, ніжheight
хочете обмежити. Якщо у вас єmax-height
з300px
, але випадають розкриваються, який може повернутися50px
, тоmax-height
не допоможуть,50px
може змінюватися в залежності від кількості елементів, ви можете прийти до безвихідної ситуації , коли я не можу це виправити , тому щоheight
НЕ виправлено,height:auto
було рішенням, але я не можу використовувати переходи з цим.