Как перейти от 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
Получить координаты (X,Y) HTML-элемента
Стилизация кнопки input type="file"
Изменение цвета элемента hr
Использование подстановочного знака * в CSS для классов