Как я могу перейти высоту: 0;по высоте: авто;используя CSS? - PullRequest
1874 голосов
/ 18 августа 2010

Я пытаюсь заставить <ul> скользить вниз, используя CSS-переходы.

<ul> начинается с height: 0;.При наведении высота установлена ​​на height:auto;.Тем не менее, это приводит к тому, что он просто появляется, не переход,

Если я сделаю это с height: 40px; до height: auto;, то он снизится до height: 0;, а затемвнезапно прыгнуть на правильную высоту.

Как еще я могу сделать это без использования JavaScript?

#child0 {
  height: 0;
  overflow: hidden;
  background-color: #dedede;
  -moz-transition: height 1s ease;
  -webkit-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
#parent0:hover #child0 {
  height: auto;
}
#child40 {
  height: 40px;
  overflow: hidden;
  background-color: #dedede;
  -moz-transition: height 1s ease;
  -webkit-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
#parent40:hover #child40 {
  height: auto;
}
h1 {
  font-weight: bold;
}
The only difference between the two snippets of CSS is one has height: 0, the other height: 40.
<hr>
<div id="parent0">
  <h1>Hover me (height: 0)</h1>
  <div id="child0">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
<hr>
<div id="parent40">
  <h1>Hover me (height: 40)</h1>
  <div id="child40">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>

Ответы [ 45 ]

2485 голосов
/ 30 ноября 2011

Используйте max-height в преобразовании, а не height.И установите для max-height значение, большее, чем когда-либо получит ваш ящик.

См. JSFiddle demo , предоставленную Крисом Джорданом в другом ответе здесь.

#menu #list {
    max-height: 0;
    transition: max-height 0.15s ease-out;
    overflow: hidden;
    background: #d5d5d5;
}

#menu:hover #list {
    max-height: 500px;
    transition: max-height 0.25s ease-in;
}
<div id="menu">
    <a>hover me</a>
    <ul id="list">
        <!-- Create a bunch, or not a bunch, of li's to see the timing. -->
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>
270 голосов
/ 23 июня 2013

Вместо этого вы должны использовать scaleY.

HTML:

<p>Here (scaleY(1))</p>
<ul>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ul>

CSS:

ul {
    background-color: #eee;
    transform: scaleY(0);    
    transform-origin: top;
    transition: transform 0.26s ease;
}

p:hover ~ ul {
    transform: scaleY(1);
}

Я сделал версию вышеуказанного кода с префиксом поставщика в jsfiddle, http://jsfiddle.net/dotnetCarpenter/PhyQc/9/ и изменил ваш jsfiddle, чтобы использовать scaleY вместо height, http://jsfiddle.net/dotnetCarpenter/7cnfc/206/.

183 голосов
/ 18 августа 2010

В настоящее время вы не можете анимировать по высоте, если одна из задействованных высот равна auto, вам нужно установить две явные высоты.

107 голосов
/ 29 мая 2015

Решением, которое я всегда использовал, было сначала затухать, а затем уменьшать значения font-size, padding и margin. Это не похоже на вайп, но работает без статического height или max-height.

Рабочий пример:

/* final display */
#menu #list {
    margin: .5em 1em;
    padding: 1em;
}

/* hide */
#menu:not(:hover) #list {
    font-size: 0;
    margin: 0;
    opacity: 0;
    padding: 0;
    /* fade out, then shrink */
    transition: opacity .25s,
                font-size .5s .25s,
                margin .5s .25s,
                padding .5s .25s;
}

/* reveal */
#menu:hover #list {
    /* unshrink, then fade in */
    transition: font-size .25s,
                margin .25s,
                padding .25s,
                opacity .5s .25s;
}
<div id="menu">
    <b>hover me</b>
    <ul id="list">
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>

<p>Another paragraph...</p>
73 голосов
/ 30 июня 2010

Можно, с небольшим количеством несемантических jiggery-pokery.Мой обычный подход состоит в том, чтобы анимировать высоту внешнего 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>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my 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>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
</div>

Моя интерпретация заключается в том, что для запуска анимации необходима явная высота.Вы не можете получить анимацию по высоте, если высота (начальная или конечная высота) равна auto.

72 голосов
/ 14 мая 2017

Я знаю, что это тридцать третий ответ на этот вопрос, но я думаю, что оно того стоит, так что здесь. Это решение только для CSS со следующими свойствами:

  • В начале отсрочки нет, и переход не прекращается рано. В обоих направлениях (развертывание и свертывание), если в CSS указать продолжительность перехода 300 мс, переход займет 300 мс (точка).
  • Он изменяет фактическую высоту (в отличие от transform: scaleY(0)), поэтому он правильно работает, если после свертываемого элемента есть контент.
  • Хотя (как и в других решениях) являются магическими числами (например, «выберите длину, превышающую вашу коробку»), это не смертельно, если ваше предположение окажется неверным , В этом случае переход может не выглядеть удивительно, но до и после перехода это не проблема: в расширенном состоянии (height: auto) все содержимое всегда имеет правильную высоту (в отличие, например, если вы выбрали max-height это оказывается слишком низким). А в свернутом состоянии высота равна нулю, как и должно быть.

Демо

Вот демонстрация с тремя складными элементами разной высоты, которые используют один и тот же CSS. Возможно, вы захотите нажать «Полная страница» после нажатия «Запустить фрагмент». Обратите внимание, что JavaScript переключает только CSS-класс collapsed, измерение не требуется. (Вы можете сделать эту точную демонстрацию вообще без 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 для этого; больше об этом позже).

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

animation as described above

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

Для моей демонстрации я установил 50px в качестве верхнего минимального значения высоты.Это второе магическое число, и оно должно быть ниже, чем высота коробки.50px кажется разумным;кажется маловероятным, что вам очень часто захочется сделать разборный элемент, высота которого даже не достигает 50 пикселей.

Как видно из анимации, результирующий переход будет непрерывным, ноне дифференцируемо - в момент, когда минимальная высота равна полной высоте, отрегулированной нижним полем, происходит внезапное изменение скорости.Это очень заметно в анимации, потому что она использует линейную функцию синхронизации для обоих переходов, и потому что весь переход очень медленный.В реальном случае (моя демонстрация вверху) переход занимает всего 300 мс, а переход нижнего поля не является линейным.Я поиграл с множеством различных функций синхронизации для обоих переходов, и те, с которыми я закончил, чувствовали, что они лучше всего работают в самых разных случаях.

Осталось решить две проблемы:

  1. точка сверху, где прямоугольники высотой более 2000 пикселей не полностью скрыты в свернутом состоянии,
  2. и обратная проблема, где в не скрытом случае прямоугольникивысота менее 50 пикселей слишком высока, даже когда переход не выполняется, потому что минимальная высота сохраняет их на уровне 50 пикселей.

Первую проблему мы решаем, задав элементу контейнера 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).Говоря о flexbox, должна быть возможность сделать это без использования flexbox;на самом деле, я думаю, что вы можете заставить почти все работать в IE7 - за исключением того факта, что у вас не будет переходов CSS, что делает это довольно бессмысленным упражнением.

47 голосов
/ 26 июня 2011

Визуальное обходное решение для анимации высоты с использованием CSS3-переходов состоит в том, чтобы вместо этого анимировать отступы.

Вы не совсем получаете эффект полного стирания, но игра со значениями длительности перехода и заполнения должна приблизить вас достаточно близко.Если вы не хотите явно устанавливать высоту / максимальную высоту, это должно быть то, что вы ищете.

div {
    height: 0;
    overflow: hidden;
    padding: 0 18px;
    -webkit-transition: all .5s ease;
       -moz-transition: all .5s ease;
            transition: all .5s ease;
}
div.animated {
    height: auto;
    padding: 24px 18px;
}

http://jsfiddle.net/catharsis/n5XfG/17/ (сброшено со стефбэнда выше jsFiddle)

45 голосов
/ 19 марта 2012

Мой обходной путь - перевести max-height в точную высоту содержимого для плавной анимации, а затем использовать обратный вызов transitionEnd, чтобы установить max-height равным 9999px, чтобы содержимое могло свободно изменять размер.

var content = $('#content');
content.inner = $('#content .inner'); // inner div needed to get size of content when closed

// css transition callback
content.on('transitionEnd webkitTransitionEnd transitionend oTransitionEnd msTransitionEnd', function(e){
    if(content.hasClass('open')){
        content.css('max-height', 9999); // try setting this to 'none'... I dare you!
    }
});

$('#toggle').on('click', function(e){
    content.toggleClass('open closed');
    content.contentHeight = content.outerHeight();
    
    if(content.hasClass('closed')){
        
        // disable transitions & set max-height to content height
        content.removeClass('transitions').css('max-height', content.contentHeight);
        setTimeout(function(){
            
            // enable & start transition
            content.addClass('transitions').css({
                'max-height': 0,
                'opacity': 0
            });
            
        }, 10); // 10ms timeout is the secret ingredient for disabling/enabling transitions
        // chrome only needs 1ms but FF needs ~10ms or it chokes on the first animation for some reason
        
    }else if(content.hasClass('open')){  
        
        content.contentHeight += content.inner.outerHeight(); // if closed, add inner height to content height
        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>Smooth CSS Transitions Between <code>height: 0</code> and <code>height: auto</code></h3>
        <p>A clever workaround is to use <code>max-height</code> instead of <code>height</code>, and set it to something bigger than your content. Problem is the browser uses this value to calculate transition duration. So if you set it to <code>max-height: 1000px</code> but the content is only 100px high, the animation will be 10x too fast.</p>
        <p>Another option is to measure the content height with JS and transition to that fixed value, but then you have to keep track of the content and manually resize it if it changes.</p>
        <p>This solution is a hybrid of the two - transition to the measured content height, then set it to <code>max-height: 9999px</code> after the transition for fluid content sizing.</p>
    </div>
</div>

<br />

<button id="toggle">Challenge Accepted!</button>
34 голосов
/ 21 октября 2014

Принятый ответ работает в большинстве случаев, но он не работает, когда ваш 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';
    }
}
#menu #list {
    height: 0px;
    transition: height 0.3s ease;
    background: #d5d5d5;
    overflow: hidden;
}
<div id="menu">
    <input value="Toggle list" type="button" onclick="toggleExpand(document.getElementById('list'));">
    <ul id="list">
        <!-- Works well with dynamically-sized content. -->
        <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>
26 голосов
/ 17 декабря 2014

Используйте max-height с различными переходами, замедления и задержки для каждого состояния.

HTML:

<a href="#" id="trigger">Hover</a>
<ul id="toggled">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
<ul>

CSS:

#toggled{
    max-height: 0px;
    transition: max-height .8s cubic-bezier(0, 1, 0, 1) -.1s;
}

#trigger:hover + #toggled{
    max-height: 9999px;
    transition-timing-function: cubic-bezier(0.5, 0, 1, 0); 
    transition-delay: 0s;
}

См. Пример: http://jsfiddle.net/0hnjehjc/1/

...