Я знаю, что это тридцатилетний ответ на этот вопрос, но я думаю, что оно того стоит. Это решение только для CSS со следующими свойствами:
- В начале отсрочки нет, и переход не прекращается рано. В обоих направлениях (разворачивание и свертывание), если в CSS указать длительность перехода 300 мс, переход занимает 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 к нулю. Это имеет тот же эффект, что и a 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 ). Говоря о flexbox, должна быть возможность сделать это без использования flexbox; на самом деле, я думаю, вы могли бы заставить почти все работать в IE7 - за исключением того факта, что у вас не будет переходов CSS, что делает это довольно бессмысленным упражнением.
height:auto/max-height
решение будет работать, только если вы расширяете область, которая больше, чемheight
вы хотите ограничить. Если у вас естьmax-height
оф300px
, но выпадающий список со списком, который может вернуться50px
, а затем вамmax-height
не поможет,50px
зависит от количества элементов, вы можете оказаться в невозможной ситуации, когда я не могу это исправить, потому чтоheight
нет Исправлено,height:auto
было решение, но я не могу использовать переходы с этим.