Как перейти от height: 0; к height: auto; с помощью CSS?
У меня возникла проблема с анимацией элемента <ul>, который должен плавно появляться с помощью CSS-переходов.
В данный момент я устанавливаю height: 0; для <ul>. При наведении высота меняется на height: auto;, но в таком случае элемент просто появляется, а не анимируется.
Если я установлю высоту сначала на height: 40px;, затем на height: auto;, то элемент будет сжиматься до height: 0;, а затем внезапно прыгнет до нужной высоты.
Как еще можно это реализовать без использования JavaScript?
Вот пример кода:
#child0 {
height: 0;
overflow: hidden;
background-color: #dedede;
transition: height 1s ease;
}
#parent0:hover #child0 {
height: auto;
}
#child40 {
height: 40px;
overflow: hidden;
background-color: #dedede;
transition: height 1s ease;
}
#parent40:hover #child40 {
height: auto;
}
h1 {
font-weight: bold;
}
<hr>
<div id="parent0">
<h1>Наведи на меня (высота: 0)</h1>
<div id="child0">Неконтент
<br>Неконтент
<br>Неконтент
<br>Неконтент
<br>Неконтент
<br>Неконтент
<br>
</div>
</div>
<hr>
<div id="parent40">
<h1>Наведи на меня (высота: 40)</h1>
<div id="child40">Неконтент
<br>Неконтент
<br>Неконтент
<br>Неконтент
<br>Неконтент
<br>Неконтент
<br>
</div>
</div>
Единственное различие между этими двумя фрагментами CSS заключается в том, что в одном высота равна 0, а в другом — 40. Как мне добиться плавного перехода при наведении?
5 ответ(ов)
На данный момент вы не можете анимировать высоту, если одна из высот равна auto. Вам необходимо задать две фиксированные высоты.
Обновление: Согласно комментарию ниже, теперь в Chrome поддерживается анимация элементов с естественными размерами: https://developer.chrome.com/blog/new-in-chrome-129#animate
Если вам нужно решение для других или более старых браузеров, посмотрите на высоко оцененные ответы выше.
Решение, которое я всегда использовал, заключалось в том, чтобы сначала сделать элемент прозрачным (fade out), а затем уменьшить значения font-size, padding и margin. Хотя это и не выглядит как смахивание (wipe), но оно работает без необходимости задавать статическую height или max-height.
Пример рабочего кода:
/* финальное отображение */
#menu #list {
margin: .5em 1em;
padding: 1em;
}
/* скрытие */
#menu:not(:hover) #list {
font-size: 0;
margin: 0;
opacity: 0;
padding: 0;
/* сначала затемнение, затем уменьшение */
transition: opacity .25s,
font-size .5s .25s,
margin .5s .25s,
padding .5s .25s;
}
/* раскрытие */
#menu:hover #list {
/* сначала увеличение, затем затемнение */
transition: font-size .25s,
margin .25s,
padding .25s,
opacity .5s .25s;
}
<div id="menu">
<b>наведите на меня</b>
<ul id="list">
<li>элемент</li>
<li>элемент</li>
<li>элемент</li>
<li>элемент</li>
<li>элемент</li>
</ul>
</div>
<p>Другой параграф...</p>
Таким образом, можно добиться желаемого эффекта без необходимости задавать фиксированную высоту.
Вы можете добиться этого с небольшими хитростями. Мой обычный подход заключается в анимации высоты внешнего блока DIV, который имеет единственного ребенка — стиль-вариант DIV, который используется только для измерения высоты содержимого.
function growDiv() {
var growDiv = document.getElementById('grow');
if (growDiv.clientHeight) {
growDiv.style.height = 0;
} else {
var wrapper = document.querySelector('.measuringWrapper');
growDiv.style.height = wrapper.clientHeight + "px";
}
}
#grow {
-moz-transition: height .5s;
-ms-transition: height .5s;
-o-transition: height .5s;
-webkit-transition: height .5s;
transition: height .5s;
height: 0;
overflow: hidden;
outline: 1px solid red;
}
<input type="button" onclick="growDiv()" value="grow">
<div id='grow'>
<div class='measuringWrapper'>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
</div>
</div>
Хотелось бы просто избавиться от .measuringWrapper и установить высоту DIV в auto, чтобы это анимировалось, но, похоже, это не работает (высота задается, но анимации не происходит).
function growDiv() {
var growDiv = document.getElementById('grow');
if (growDiv.clientHeight) {
growDiv.style.height = 0;
} else {
growDiv.style.height = 'auto';
}
}
#grow {
-moz-transition: height .5s;
-ms-transition: height .5s;
-o-transition: height .5s;
-webkit-transition: height .5s;
transition: height .5s;
height: 0;
overflow: hidden;
outline: 1px solid red;
}
<input type="button" onclick="growDiv()" value="grow">
<div id='grow'>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
<div>
Содержимое моего блока.
</div>
</div>
Моя интерпретация заключается в том, что для запуска анимации требуется явная высота. Анимации по высоте не может быть, когда либо начальная, либо конечная высота равна auto.
Ответ, который ты нашел, подходит для большинства случаев, но не всегда работает хорошо, когда высота твоего div может сильно варьироваться — скорость анимации не зависит от фактической высоты содержимого, из-за чего анимация может выглядеть дерганной.
Ты все еще можешь выполнить анимацию с помощью CSS, но для этого нужно использовать JavaScript, чтобы вычислить высоту элементов, вместо того чтобы пытаться установить её на auto. В этом нет необходимости в jQuery, хотя, возможно, придётся немного модифицировать код для лучшей совместимости (он работает в последней версии Chrome 😃).
Вот пример кода:
window.toggleExpand = function(element) {
if (!element.style.height || element.style.height == '0px') {
element.style.height = Array.prototype.reduce.call(element.childNodes, function(p, c) {return p + (c.offsetHeight || 0);}, 0) + 'px';
} else {
element.style.height = '0px';
}
}
CSS для установки начальной высоты и анимации:
#menu #list {
height: 0px;
transition: height 0.3s ease;
background: #d5d5d5;
overflow: hidden;
}
А вот HTML-структура:
<div id="menu">
<input value="Toggle list" type="button" onclick="toggleExpand(document.getElementById('list'));">
<ul id="list">
<!-- Работает хорошо с динамически изменяемым содержимым. -->
<li>item</li>
<li><div style="height: 100px; width: 100px; background: red;"></div></li>
<li>item</li>
<li>item</li>
<li>item</li>
</ul>
</div>
С этим подходом анимация высоты будет более плавной и согласованной с фактическим содержимым, благодаря динамическому вычислению высоты с помощью JavaScript.
Мой обходной способ заключается в том, чтобы анимировать max-height до точно измеренной высоты контента для плавной анимации, а затем использовать коллбек transitionEnd, чтобы установить max-height на 9999px, чтобы контент мог свободно изменять размер.
var content = $('#content');
content.inner = $('#content .inner'); // внутренний div нужен для получения размера контента, когда он закрыт
// коллбек для css-перехода
content.on('transitionEnd webkitTransitionEnd transitionend oTransitionEnd msTransitionEnd', function(e){
if(content.hasClass('open')){
content.css('max-height', 9999); // попробуйте установить это значение на 'none'... я вас на это берусь!
}
});
$('#toggle').on('click', function(e){
content.toggleClass('open closed');
content.contentHeight = content.outerHeight();
if(content.hasClass('closed')){
// отключить переходы и установить max-height на высоту контента
content.removeClass('transitions').css('max-height', content.contentHeight);
setTimeout(function(){
// включить и начать переход
content.addClass('transitions').css({
'max-height': 0,
'opacity': 0
});
}, 10); // тайм-аут 10мс - секретный ингредиент для отключения/включения переходов
// Chrome нуждается лишь в 1мс, но FF нуждается в ~10мс, иначе он зависает на первой анимации по какой-то причине
} else if(content.hasClass('open')){
content.contentHeight += content.inner.outerHeight(); // если закрыт, добавляем высоту внутреннего контента к высоте контента
content.css({
'max-height': content.contentHeight,
'opacity': 1
});
}
});
.transitions {
transition: all 0.5s ease-in-out;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
}
body {
font-family: Arial;
line-height: 3ex;
}
code {
display: inline-block;
background: #fafafa;
padding: 0 1ex;
}
#toggle {
display: block;
padding: 10px;
margin: 10px auto;
text-align: center;
width: 30ex;
}
#content {
overflow: hidden;
margin: 10px;
border: 1px solid #666;
background: #efefef;
opacity: 1;
}
#content .inner {
padding: 10px;
overflow: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div id="content" class="open">
<div class="inner">
<h3>Плавные CSS-переходы между <code>height: 0</code> и <code>height: auto</code></h3>
<p>Хитроумный обходной способ - использовать <code>max-height</code> вместо <code>height</code> и установить его на что-то большее, чем ваш контент. Проблема в том, что браузер использует это значение для вычисления продолжительности перехода. Поэтому, если вы установите <code>max-height: 1000px</code>, а контент имеет высоту всего 100px, анимация будет в 10 раз быстрее, чем нужно.</p>
<p>Другой вариант - измерить высоту контента с помощью JS и переходить к этому фиксированному значению, но тогда вам нужно будет отслеживать контент и вручную изменять его размер, если он изменится.</p>
<p>Это решение является гибридом двух предыдущих подходов - переходите к измеренной высоте контента, а затем установите <code>max-height: 9999px</code> после перехода для свободного изменения размера контента.</p>
</div>
</div>
<br />
<button id="toggle">Вызов принят!</button>
Таким образом, вы сможете сконструировать плавную анимацию для открытия и закрытия элементов на странице, избегая проблем, связанных с использованием фиксированной высоты.
Переходы на свойстве display в CSS
Сокращённая запись CSS для переходов с несколькими свойствами?
Как расположить div внизу своего контейнера?
Медиа-запросы: Как нацелиться на десктоп, планшет и мобильные устройства?
Как адаптировать ширину flex-элемента к содержимому?