如何使用CSS将height: 0; 过渡到 height: auto;?

2856

我正尝试使用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>


80
OP 正在尝试使用 CSS 解决方案,而不是 JavaScript,否则他们可以直接使用 overflow 和 animate。 - Toni Leigh
5
但是内部div的负100%的margin-top会接收到外部包裹div的宽度,而不是高度。因此,这个解决方案和最大高度的解决方案有相同类型的问题。此外,当宽度小于内容的高度时,不是所有内容都会被负100%的margin-top隐藏。因此,这是一个错误的解决方案。 - GregTom
3
请参阅 https://github.com/w3c/csswg-drafts/issues/626,了解有关规范和实现适当解决方案的讨论。 - Ben Creasy
4
Flexbox/Grid方式:https://keithjgrant.com/posts/2023/04/transitioning-to-height-auto/ - dotnetCarpenter
@dotnetCarpenter 非常感谢!!!!!我真的真的真的非常感谢你!!!! - undefined
显示剩余6条评论
46个回答

3603
使用过渡效果时请使用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>


470
这个很好用!除了启动时有一些延迟,因为它的初始最大高度非常高……嗯,我觉得这有点烦人。 - vsync
216
+1 太棒了!过渡的速度是根据您指定的过渡到最大高度值的时间来计算的... 但由于高度将小于最大高度,因此实际高度的过渡会比指定的时间更快(通常显著更快)。 - kingjeffrey
84
请注意,当您必须使用比实际计算值大得多的值时,可能会导致不美观的过渡结束。我在尝试使一个div从0高度增长到由于不同屏幕尺寸而变化很大的内容高度(在我的2560x1440显示器上是2行,在智能手机上是>10行)时注意到了这一点。因此,我最终选择了使用JS来解决。 - jpeltoniemi
92
非常丑陋的解决方案,因为它会在一个方向上造成延迟,而在另一个方向上不会。 - Tim
137
这是一个相当懒惰的解决方案。我假设 OP 想使用 height: auto,因为容器的扩展高度有些不可预测。这个解决方案将导致动画在变得可见之前出现延迟。此外,动画的可见持续时间也是不可预测的。通过计算每个容器子节点的组合高度,并缓慢地到达确切的高度值,您将获得更可预测(并且可能更流畅)的结果。 - Shawn Whinnery
显示剩余9条评论

475

您应该使用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而不是height。 编辑有些人不喜欢scaleY如何转换内容。如果这是个问题,那我建议使用clip

ul {
  clip: rect(auto, auto, 0, auto);
  position: absolute;
  margin: -1rem 0;
  padding: .5rem;

  color: white;

  background-color: rgba(0, 0, 0, 0.8);

  transition-property: clip;
  transition-duration: 0.5s;
  transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
h3:hover ~ ul,
h3:active ~ ul,
ul:hover {
  clip: rect(auto, auto, 10rem, auto);
}
<h3>Hover here</h3>
<ul>
  <li>This list</li>
  <li>is clipped.</li>
  <li>A clip transition</li>
  <li>will show it</li>
</ul>
<p>
  Some text...
</p>


306
这种方法只能部分地达到所需的效果,但实际上并未移除空格。转换后的框像相对定位元素一样 - 无论如何缩放,都会占用空间。查看此jsFiddle,它采用了您的第一个示例,并在底部添加了一些虚假文本。请注意,当将框高度缩放为零时,下面的文本不会上移。 - animuson
11
现在它可以:http://jsfiddle.net/gNDX3/1/ 基本上你需要根据你的需求来设置元素的样式。CSS/HTML中没有像小部件一样的银弹或行为。 - dotnetCarpenter
115
虽然我赞赏有人尝试不同的方法,但是这种解决方案带来的现实影响和复杂性比已经很糟糕的max-height hack更糟糕。请勿使用。 - zrooda
9
“现在它可以做到”,除了消失的元素下方的内容在元素滑动消失之前会跳起来。这有时可能是有用的,但过渡效果的目的是平稳地改变视觉效果,而不是将一个生硬的跳跃换成另一个。 - Patrick Szalapski
6
实际上,使用“剪辑”转换和简单地设置“max-height”值并没有什么不同。在内容大小高度动态变化时,它仍然会受到明显动画延迟量不固定的影响。 - Monday Fatigue
显示剩余4条评论

280

如果涉及到的高度有一个是auto,则目前无法对其进行高度动画,必须设置两个明确的高度。


179

我一直使用的解决方法是先淡出,然后缩小字体大小paddingmargin值。它看起来不像擦除,但可以在没有静态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>


6
如果你有按钮或其他非文本元素,那么当不悬停时会出现不需要的空白。 - Dennis W
5
您可以通过选择所有子元素并应用其他更改来进一步完善它,使用 * 符号即可。按钮应该受到我的上述代码的影响,但是如果它们已经被正确地使用 em 进行样式设置,则不会被影响。 - Steven Vachon
2
不要这样做。这不是GPU加速的,会导致非常卡顿的动画(正如此答案中的代码片段所示)。 - enyo

162

这是一个CSS-only的解决方案,具有以下特点:

  • 没有开始时的延迟,过渡也不会提前停止。在两个方向(扩展和折叠)中,如果在您的CSS中指定了300ms的过渡持续时间,则过渡需要花费300ms的时间。
  • 它正在过渡实际高度(与transform: scaleY(0)不同),因此,如果在可折叠元素之后有内容,则会执行正确的操作。
  • 虽然(与其他解决方案一样)存在魔数(例如“选择比您的框永远不会高的长度”),但如果您的假设最终是错误的,这并不致命。在此情况下,过渡可能看起来不太令人惊艳,但在过渡之前和之后,这不是问题:在扩展(height: auto)状态下,整个内容始终具有正确的高度(例如,如果您选择的max-height太低)。在折叠状态下,高度为零,如预期。

演示

这里有一个演示,其中包含三个可折叠元素,所有元素高度都不同,但都使用相同的CSS。单击“运行片段”后,您可能需要单击“全屏”。请注意,JavaScript仅切换collapsed CSS类,没有涉及任何测量。(您可以使用复选框或:target在完全不使用JavaScript的情况下执行此示例)。此外,请注意负责过渡的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从0像素转换为在折叠状态下的-2000px(类似于这个答案)。这里的2000是第一个神奇数字,它基于您的框不会高于此(2000像素似乎是一个合理的选择)。

仅使用单独的margin-bottom转换存在两个问题:

  • 如果您有一个高度超过2000像素的框,则margin-bottom:-2000px不能隐藏所有内容,即使在折叠状态下也会有可见的内容。这只是一个小修复,我们稍后会处理。
  • 如果实际框高度为1000像素,并且您的转换时间为300毫秒,则在约150毫秒后(或相反方向上晚150毫秒)可见转换已经结束。

解决这第二个问题的方法就是使用第二个过渡,该过渡概念上针对包装器的最小高度(“概念上”因为我们实际上并未使用min-height属性进行此操作;稍后详细说明)。

这是一个动画,展示了如何结合底部边距过渡和最小高度过渡,两者持续时间相等,给我们提供了从全高到零高的组合过渡,持续时间相同。

animation as described above

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

对于我的演示,我已经将50像素作为上限最小高度值。这是第二个神奇数字,它应该比框的高度更低。50像素也是合理的;您很少需要使元素可折叠,而它的高度甚至不到50像素。

从动画中可以看到,过渡效果是连续的,但不可导--当最小高度等于底部边距调整后的完整高度时,速度会突然变化。这在动画中非常明显,因为它对于两个转换都使用线性时间函数,并且整个过渡非常缓慢。在实际情况下(我在顶部的演示),过渡仅需要300毫秒,并且底部边距转换是不线性的。我尝试了许多不同的时间函数来处理这两个过渡,而我最终选择的那些感觉在最广泛的情况下效果最好。

还有两个问题需要解决:

  1. 上述的问题,即超过2000像素高度的框在折叠状态下不能完全隐藏,
  2. 以及相反的问题,在非隐藏状态下,少于50像素高度的框即使过渡不运行,最小高度也保持在50像素。

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

第二个问题是我们没有实际使用 min-height 进行最小高度过渡的原因。相反,在容器中有一个带有 height::after 伪元素,它从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 过渡,这使得它成为一个相当无意义的练习。


127
希望您能简化(精简)解决方案,或者至少简化代码示例 - 看起来代码示例中的90%与您的答案无关。 - Gil Epshtain
18
对于一个学习任务的人来说,这是一个很好的回答,原因有几个。首先,它包含了完整的“实际生活”代码示例。有人可以拿这段代码进行实验,从中学习更多知识。其次,解释中的细节非常详细。感谢 @balpha 花时间提供这个回答 :) - James Walker
太棒了!这真的完美地运作了,而且通过一些修改,它也适用于我的使用情况,即折叠元素不在0高度(在我的情况下,当折叠时只显示一行网格,展开时显示所有行)。不仅如此,它似乎很容易在各种情境中实现,可以“插入”现有页面,而不必改变太多父级和子级元素,这是许多其他解决方案所不能做到的。 - Quentin Le Caignec
2
感谢您的认真对待,但是作为一个有点苛刻的人,在最后一天动画看起来和感觉都不好。这是这种方法固有的吗? - Steven Lu
@StevenLu 在某种程度上是的。我在我的回答中回答了这个问题,请看以“这在动画中非常明显…”开头的句子。 - balpha

117

你可以通过一些非语义的小技巧实现。我的常规做法是为外部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时,无法在高度上获得动画效果。


11
由于这是基于 JavaScript 的,您也可以使用 JavaScript 轻松地添加 measuringWrapper! - Quickredfox
28
你可以不使用包装器(wrapper)来实现它,只需这样做: function growDiv() { var growDiv = document.getElementById('grow'); if (growDiv.clientHeight) { // 如果已经有高度,则将其设置为0 growDiv.style.height = 0; } else { // 否则基于内容的高度将其设置为滚动高度 growDiv.style.height = growDiv.scrollHeight+'px'; } } - user1742529
1
我喜欢这种方法,因为它很精确。我还喜欢根据计算出的高度调整折叠元素的过渡速度。这样,高元素展开所需的时间比短元素长。例如:folder.style.transition = \height ${maxHeight}ms`;` - Symbolic
如果您不介意使用JavaScript,我认为这是最好的解决方案。虽然有一些纯CSS的方法,但它们都有一些相当大的缺点。 - Dolan

67

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


@Coderer 你可以使用 clientHeight - Tom
这个结果非常好。但是大部分 JavaScript 看起来像机器码。你如何重写代码,使其看起来像在说英语(易读的代码)? - klewis
3
当高度为0时,您也可以使用scrollHeight - MMD
4
这是我最喜欢的答案。但对于高度计算,我使用了@MMD建议的scrollHeight,像这样:element.style.height = element.scrollHeight + 'px' - Brett Donald

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


对我来说,这是一个丑陋的解决方案,但它是动画高度变化的最佳解决方案,因为其他所有解决方案都有妥协。然而,添加后续动画(例如返回到原始高度)变得困难。我发现如果我将高度设置回scrollHeight,然后立即设置为0,Chrome无法一致地对其进行动画处理,因为属性变化太快了。在两个更改之间添加超时可以解决问题,但神奇的延迟数字是未知的,并且它会给过渡添加不舒适的延迟。有什么解决办法吗? - Peter Cole

54

使用CSS3过渡动画实现高度动画的一种视觉解决方案是通过动画内边距(padding)来代替。

虽然不会完全得到擦除效果,但是调整transition-duration和padding值可以让你接近要达到的效果。如果你不想明确设置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)


27
这里的区别在于你没有对高度进行动画处理,而是对填充进行了动画处理......它可以很好地消失,因为它可以从当前状态动画到0,但是如果你仔细观察它展开的时候,它会随着文本弹出,然后只有填充在进行动画处理......因为它不知道如何从0到自动(auto)进行动画处理......它需要一个数值范围......这就是缓动动画的工作方式。 - Rob R
1
我不确定如何以不失礼貌的方式表达,但是...它看起来不专业。至少https://dev59.com/THA75IYBdhLWcg3wB0VP#30531678使运动“连续”...但它并不平滑。 - Steven Lu

40

使用不同的过渡缓动和延迟时间,为每个状态设置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/


20
这里的问题在于,在动态环境下确保有足够的空间,你需要使用荒谬的999999px的最大高度。然而,这会导致你的动画出现偏移,因为帧是根据这个值计算的。所以你可能会在一个方向上遇到动画“延迟”,在另一个方向上则非常快地结束(corr:时间函数)。 - Julian F. Weinert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接