如何转换高度:0; 达到高度:自动;使用 CSS?

我正在尝试使用 CSS 过渡使<ul>滑落。

<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>

答案

在过渡中使用max-height ,而不要使用height 。并将max-height的值设置为比您的盒子所能达到的更大的值。

请参阅 Chris Jordan 提供的JSFiddle 演示 ,在此处还有另一个答案

#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>

您应该改用 scaleY。

ul {
  background-color: #eee;
  transform: scaleY(0);    
  transform-origin: top;
  transition: transform 0.26s ease;
}
p:hover ~ ul {
  transform: scaleY(1);
}
<p>Hover This</p>
<ul>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ul>

我做了上述代码的供应商前缀版本的 jsfiddle ,并改变了你的 jsfiddle到使用的 scaleY,而不是高度。

当前涉及的高度之一是auto ,您当前无法在高度上设置动画,您必须设置两个显式的高度。

我一直使用的解决方案是先淡出,然后缩小font-sizepaddingmargin值。它看起来与抹布并不相同,但是在没有静态heightmax-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>

它是如何工作的?

实际上,要实现这一目标涉及两个过渡。其中之一将margin-bottom从 0px(处于展开状态)转换为-2000px (处于折叠状态)(类似于此答案 )。这里的 2000 是第一个魔幻数字,它基于您的框不会高于此数字的假设(2000 像素似乎是一个合理的选择)。

仅单独使用margin-bottom过渡有两个问题:

  • 如果您实际上有一个高于 2000 像素的框,则margin-bottom: -2000px不会隐藏所有内容 - 即使在折叠的情况下也会有可见的东西。这是一个较小的修复程序,我们将在以后进行。
  • 例如,如果实际的框高为 1000 像素,并且过渡的长度为 300ms,则可见过渡在大约 150ms 后已经结束(或者,在相反的方向,延迟时间为 150ms)。

解决第二个问题是第二个转换的到来,这个转换从概念上讲针对包装器的最小高度(“概念上”,因为我们实际上并未为此使用min-height属性;稍后将对此进行更多介绍)。

这是一个动画,展示了如何将底部边距过渡与持续时间相等的最小高度过渡组合在一起,从而为我们提供了从全高到持续时间相同的零高度的组合过渡。

如上所述的动画

左栏显示负底边距如何向上推动底部,从而减小可见高度。中间的条显示了最小高度如何确保在折叠情况下过渡不会提前结束,而在扩展情况下过渡不会迟到。右栏显示了两者的组合如何导致框在正确的时间内从全高过渡到零高。

对于我的演示,我将 50px 设置为最小最小高度的上限。这是第二个魔术数字,它应该低于盒子的高度。 50px 也很合理;您似乎不太可能经常想使一个可折叠的元素起初甚至不超过 50 像素。

正如您在动画中所看到的那样,最终的过渡是连续的,但不可区分 - 在最小高度等于由底部边距调整的全高度的那一刻,速度会突然变化。这在动画中非常明显,因为它对两个过渡都使用线性计时功能,并且因为整个过渡非常慢。在实际情况下(我的演示在顶部),过渡仅花费 300ms,底部边距过渡不是线性的。对于这两种转换,我都使用了许多不同的计时功能,而最终我觉得它们在最广泛的情况下效果最好。

还有两个问题需要解决:

  1. 从上方的角度来看,高度超过 2000 像素的框在折叠状态下并未完全隐藏,
  2. 相反的问题是,在非隐藏情况下,即使不运行过渡,小于 50 像素高度的框也会过高,因为最小高度将其保持在 50 像素。

我们通过为容器元素赋予max-height: 0在折叠的情况下为0s 0.3s过渡为0s 0.3s解决第一个问题。这意味着它并不是真正的过渡,但是max-height会延迟应用;它仅在过渡结束后才适用。为了使它正常工作,我们还需要为相反的,未折叠的状态选择一个数值max-height 。但是与 2000px 情况不同的是,选择太大的数字会影响过渡的质量,在这种情况下,这实际上并不重要。因此,我们只能选择一个很高的数字,以至于我们知道没有一个高度会接近这个数字。我选了一百万像素。如果您觉得可能需要支持超过一百万像素的高度的内容,则 1)对不起,2)仅添加几个零。

第二个问题是为什么我们实际上没有使用min-height来进行最小高度转换。相反,容器中有一个::after伪元素,其height从 50px 过渡到零。这与min-height具有相同的效果:不会让容器缩小到伪元素当前具有的任何高度以下。但是,因为我们使用的是height ,而不是min-height ,所以现在可以使用max-height (再次应用延迟)将过渡结束后将伪元素的实际高度设置为零,从而确保至少在过渡,即使很小的元素也具有正确的高度。由于min-heightmax-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 转换这一事实之外,这是相当没有意义的练习。

您可以用一点点非语义的拼图扑克。我通常的方法是为外部 DIV 的高度设置动画,该外部 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>

我的解释是动画运行需要明确的高度。当 height(开始高度或结束高度)均为auto时,无法在 height 上获得动画。

使用 CSS3 过渡为高度设置动画的一种可视方法是改为对填充进行动画处理。

您还不能完全获得擦除效果,但是使用过渡时间和填充值应该可以使您足够接近。如果您不想显式设置 height / max-height,这应该是您想要的。

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/ (从 stephband 的 jsFiddle 上方摘下来)

我的解决方法是将 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>

可接受的答案在大多数情况下都有效,但是当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>

对每个状态使用具有不同过渡缓和和延迟的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/