29

Как перейти от height: 0; к height: auto; с помощью CSS?

14

У меня возникла проблема с анимацией элемента <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 ответ(ов)

2

На данный момент вы не можете анимировать высоту, если одна из высот равна auto. Вам необходимо задать две фиксированные высоты.

Обновление: Согласно комментарию ниже, теперь в Chrome поддерживается анимация элементов с естественными размерами: https://developer.chrome.com/blog/new-in-chrome-129#animate

Если вам нужно решение для других или более старых браузеров, посмотрите на высоко оцененные ответы выше.

1

Решение, которое я всегда использовал, заключалось в том, чтобы сначала сделать элемент прозрачным (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>

Таким образом, можно добиться желаемого эффекта без необходимости задавать фиксированную высоту.

1

Вы можете добиться этого с небольшими хитростями. Мой обычный подход заключается в анимации высоты внешнего блока 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.

0

Ответ, который ты нашел, подходит для большинства случаев, но не всегда работает хорошо, когда высота твоего 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.

0

Мой обходной способ заключается в том, чтобы анимировать 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>

Таким образом, вы сможете сконструировать плавную анимацию для открытия и закрытия элементов на странице, избегая проблем, связанных с использованием фиксированной высоты.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь