【问题标题】:How can I transition height: 0; to height: auto; using CSS?我怎样才能过渡 height: 0;高度:自动;使用 CSS?
【发布时间】:2022-06-29 02:57:43
【问题描述】:

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

【问题讨论】:

  • 我相信 height:auto/max-height 解决方案只有在扩展区域大于要限制的 height 时才有效。如果你有一个300pxmax-height,但是一个组合框下拉,可以返回50px,那么max-height帮不了你,50px是可变的,取决于元素的数量,你可以到达由于 height 未修复,我无法修复它,这是不可能的情况,height:auto 是解决方案,但我不能对此使用转换。
  • OP 正在尝试 css 解决方案,而不是 js,否则他们可以只使用 overflow 和 animate
  • @VIDesignz:但是内部 div 的 -100% margin-top 收到了宽度包装 div,不是身高。所以这个解决方案有同样的问题,即最大高度解决方案。此外,当宽度小于内容的高度时,并非所有内容都被 -100% margin-top 隐藏。所以这是一个错误的解决方案。
  • 请参阅github.com/w3c/csswg-drafts/issues/626,了解有关规范和正确解决方案实施的讨论
  • @Paulie_D 问题是关于高度,而不是宽度。所有的答案都是关于身高的。请不要将问题标题更改为与它无关的内容。

标签: css css-transitions


【解决方案1】:

在转换中使用 max-height 而不是 height。并将 max-height 的值设置为比您的盒子所能达到的更大的值。

请参阅 Chris Jordan 提供的 JSFiddle demo 和另一个 answer 此处。

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

【讨论】:

  • 这很好用!除了它开始时有延迟,因为它开始时的最大高度最初非常高..嗯,我觉得这有点烦人
  • +1 很好的解决方案!过渡速度的计算是根据您指定过渡到最大高度值的时间来计算的...但是由于高度将小于最大高度,因此到实际高度的过渡将比(通常显着)快于规定的时间。
  • 请注意,当您必须使用比实际计算值大得多的值时,这可能会导致丑陋的过渡结束。我在尝试使 div 从 0 高度增长到内容高度时注意到这一点,内容高度因屏幕尺寸不同而有很大差异(我的 2560x1440 显示器上有 2 行,而智能手机上有 >10 行)。为此,我最终选择了 js。
  • 非常难看的解决方案,因为它会在一个方向上造成延迟,但不会在另一个方向上造成延迟。
  • 这是一个非常懒惰的解决方案。我假设 OP 想要使用 height : auto 因为容器的扩展高度有些不可预测。此解决方案将在动画变得可见之前造成延迟。此外,动画的可见持续时间将不可预测。通过计算每个容器子节点的组合高度,然后缓和到一个精确的高度值,您将获得更可预测(并且可能更平滑)的结果。
【解决方案2】:

您应该改用 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 而不是高度。

编辑有些人不喜欢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>

【讨论】:

  • 这种方法只是部分达到了预期的效果,实际上并没有消除空间。转换后的盒子就像一个相对定位的元素——无论它如何缩放,空间都会被占用。查看this jsFiddle,它使用了你的第一个,只是在底部添加了一些伪造的文本。请注意,当框高度缩放为零时,其下方的文本不会向上移动。
  • 现在可以了:jsfiddle.net/gNDX3/1 基本上您需要根据需要设置元素的样式。 CSS/HTML 中没有银弹或小部件之类的行为。
  • 虽然我为尝试不同方法的人鼓掌,但这个解决方案带来的现实效果和复杂性远比已经很糟糕的最大高度 hack 更糟糕。请不要使用。
  • “现在可以了”,只是消失元素下方的内容会在元素滑开之前跳起来。有时可能会有用,但是过渡的重点是平滑地改变视觉效果,而不是用一个明显的跳跃来换另一个。
  • 实际上,使用 clip 转换与简单地设置 max-height 值没有什么不同。它仍然遭受非固定数量的外观动画延迟和高度动态的内容大小。
【解决方案3】:

当所涉及的高度之一是auto 时,您目前无法对高度进行动画处理,您必须设置两个明确的高度。

【讨论】:

    【解决方案4】:

    我一直使用的解决方案是先淡出,然后缩小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>

    【讨论】:

    • 如果您有按钮或任何其他非文本元素,您最终会在不悬停时出现不需要的空白。
    • 您可以通过使用 * 选择所有子元素并应用其他更改来进一步优化它。但是,如果按钮使用 em 设置了正确的样式,它们应该会受到我上面代码的影响。
    • 不要这样做。这不是 GPU 加速的,并且会导致非常卡顿的动画(如本答案中的 sn-p 所示)。
    【解决方案5】:

    这是一个纯 CSS具有以下属性的解决方案:

    • 开始时没有延迟,过渡不会提前停止。在两个方向(展开和折叠),如果您在 CSS 中指定 300 毫秒的过渡持续时间,则过渡需要 300 毫秒,周期。
    • 它正在转换实际高度(与transform: scaleY(0) 不同),所以如果可折叠元素之后有内容,它会做正确的事情。
    • 虽然(与其他解决方案一样)幻数(比如“选择一个比你的盒子永远长的长度”),如果你的假设最终是错误的,这并不是致命的。在那种情况下,过渡可能看起来并不惊人,但在过渡之前和之后,这不是问题:在展开的 (height: auto) 状态下,整个内容始终具有正确的高度(不同于例如,如果您选择 @987654327 @事实证明太低了)。在折叠状态下,高度应该为零。

    演示

    这是一个包含三个可折叠元素的演示,所有元素的高度都不同,它们都使用相同的 CSS。您可能希望在单击“运行 sn-p”后单击“整页”。请注意,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 从 0px(展开状态)转换为折叠状态的 -2000px(类似于 this answer)。这里的 2000 是第一个幻数,它是基于你的盒子不会高于这个的假设(2000 像素似乎是一个合理的选择)。

    单独使用 margin-bottom 转换本身有两个问题:

    • 如果您实际上有一个高于 2000 像素的框,那么 margin-bottom: -2000px 不会隐藏所有内容——即使在折叠的情况下也会有可见的东西。这是我们稍后会做的一个小修复。
    • 如果实际的框是,比方说,1000 像素高,而你的过渡是 300 毫秒长,那么可见的过渡在大约 150 毫秒后已经结束(或者,在相反的方向上,晚 150 毫秒开始)。

    解决第二个问题是第二个转换的地方,这个转换在概念上针对包装器的最低限度高度(“概念上”,因为我们实际上并没有为此使用 min-height 属性;稍后会详细介绍)。

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

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

    对于我的演示,我已将 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-heightstronger而不是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 转换的事实,这使它成为一个相当毫无意义的练习。

    【讨论】:

    • 我希望您可以缩短(简化)您的解决方案,或者至少缩短代码示例 - 看起来 90% 的代码示例与您的答案无关。
    • 对于执行学习任务的人来说,这是一个很好的答案,原因有几个。首先,它有完整的“现实生活”代码示例。有人可以获取该代码,对其进行试验并了解更多信息。解释中的细节......非常详细。谢谢@balpha 花时间回答这个问题:)
    • 极好的 !这实际上工作得很好,经过一些修改,它也适合我的折叠元素不在 0 高度的用例(在我的例子中,网格在折叠时只显示一行,打开时显示所有行)。不仅如此,它似乎很容易在各种上下文中实现,可以“插入”现有页面,而不必更改太多的父元素和子元素,这是许多其他解决方案无法做到的。
    • 感谢所有的彻底性,但是,在这里有点坚持,动画看起来和感觉都很糟糕。在一天结束时不是一个平滑的计时功能。这是这种方法固有的吗?
    • @StevenLu 在某种程度上,是的。我在回答中解决了这个问题,请参阅以“This is very noticeable in the animation ...”开头的句子
    【解决方案6】:

    你可以,用一点非语义的jiggery-pokery。我通常的方法是为外部 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 的高度设置为自动并设置动画,但这似乎不起作用(设置了高度,但没有动画发生)。

    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 时,您无法获得有关高度的动画。

    【讨论】:

    • 由于这依赖于 javascript,您也可以使用 javascript 轻松添加 measuringWrapper!
    • 你可以在没有包装的情况下做到这一点。只是: function growDiv() { var growDiv = document.getElementById('grow');如果(growDiv.clientHeight){ growDiv.style.height = 0; } else { growDiv.style.height = growDiv.scrollHeight+'px'; } }
    • 我更喜欢这种方法,因为它很精确。我还喜欢根据计算出的高度调整折叠元素的过渡速度。这样一来,高大的元素比矮小的元素按比例需要更长的时间展开。即folder.style.transition = `height ${maxHeight}ms`;
    • 如果您不介意使用 javascript,我认为这是最好的解决方案。纯 css 有几种方法,但它们都有一些相当大的缺点。
    【解决方案7】:

    已接受的答案适用于大多数情况,但当您的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
    • 这有一个非常好的结果。但大部分 JavaScript 看起来像机器码。你怎么能像说英语一样重写代码(可读代码)?
    • 当高度为 0 时,你也可以使用 scrollHeight
    • 这是我最喜欢的答案。但是对于高度计算,我按照@MMD 的建议使用 scrollHeight,如下所示:element.style.height = element.scrollHeight + 'px'
    【解决方案8】:

    使用 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/(模仿 stephband 在 jsFiddle 上面的内容)

    【讨论】:

    • 除了在这里您根本没有设置高度动画。您正在为填充设置动画……它可以很好地消失,因为它可以从当前状态向下设置动画到 0,但是如果您在展开时仔细观察,它会随文本一起弹出,然后填充仅设置动画……因为它不会不知道如何制作从 0 到自动的动画……它需要一个数值范围……这就是补间的工作原理。
    • 我不确定如何以一种不粗鲁的方式来表达它,但是……它看起来不专业。至少stackoverflow.com/a/30531678/340947使动作“连续”......但它并不流畅
    【解决方案9】:

    我的解决方法是将 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 不会始终如一地为其设置动画,因为属性变化太快了。在两个更改之间添加超时是有效的,但神奇的延迟数是未知的,它给转换增加了一个不舒服的延迟。关于解决方案的任何想法?
    【解决方案10】:

    使用 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/

    【讨论】:

    • 这里的问题是,为了确保您在动态环境中有足够的空间,您需要使用像 999999px 这样可笑的最大高度。另一方面,这将移动您的动画,因为帧是根据该值计算的。所以你可能会在一个方向上以动画“延迟”结束,而在另一个方向上非常快速地结束(更正:计时功能)
    【解决方案11】:

    没有硬编码值。

    没有JavaScript。

    没有近似值。

    诀窍是使用隐藏和重复的 div 让浏览器理解 100% 的含义。

    只要您能够复制要设置动画的元素的 DOM,此方法就适用。

    .outer {
      border: dashed red 1px;
      position: relative;
    }
    
    .dummy {
      visibility: hidden;
    }
    
    .real {
      position: absolute;
      background: yellow;
      height: 0;
      transition: height 0.5s;
      overflow: hidden;
    }
    
    .outer:hover>.real {
      height: 100%;
    }
    Hover over the box below:
    <div class="outer">
      <!-- The actual element that you'd like to animate -->
      <div class="real">
    unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable
    content unpredictable content unpredictable content unpredictable content
      </div>
      <!-- An exact copy of the element you'd like to animate. -->
      <div class="dummy" aria-hidden="true">
    unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable
    content unpredictable content unpredictable content unpredictable content
      </div>
    </div>

    【讨论】:

    • 这仍然占用空间。 “剧透”的好解决方案,但不适合折叠的内容。
    【解决方案12】:

    根据 MDN Web Docsauto 值已被有意排除在 CSS 转换规范之外,因此在网格和弹性布局中使用 height: 100%topflex 属性而不是 height: auto

    展开/折叠叠加层

    .grid-container {
      display: grid;
      position: absolute;
    }
    
    .content {
      background: aqua;
      height: 0;
      overflow: hidden;
      transition: 1s;
    }
    
    span:hover + .grid-container .content {
      height: 100%;
    }
    <span>Hover over me!</span>
    
    <div class="grid-container">
    
      <div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
    
    </div>
    
    <p>Rest of the page content...</p>

    展开/折叠滑动叠加层

    .grid-container {
      display: grid;
      position: absolute;
      overflow: hidden;
      pointer-events: none; /* to enable interaction with elements below the container */
    }
    
    .content {
      background: aqua;
      pointer-events: auto;
      position: relative;
      top: -100%;
      transition: 1s;
    }
    
    span:hover + .grid-container .content {
      top: 0;
    }
    <span>Hover over me!</span>
    
    <div class="grid-container">
    
      <div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
    
    </div>
    
    <p>Rest of the page content...</p>

    在文档流中展开/折叠

    html {
      display: grid;
    }
    
    body {
      display: flex;
      flex-direction: column;
    }
    
    .content {
      background: aqua;
      flex-basis: 0;
      overflow: hidden;
      transition: 1s;
    }
    
    span:hover + .content {
      flex: 1;
    }
    <span>Hover over me!</span>
    
    <div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
    
    <p>Rest of the page content...</p>

    【讨论】:

    • 我对这个例子感到很兴奋:Expanding/collapsing in the document flow 但它不起作用,因为容器具有定义的高度,因此其余内容将受到影响,容器外的内容,
    • 是的!感谢您的分享,如果我这么认为,在某些情况下,如果您在内容末尾加空格并不重要,那么在内容是整页的情况下,您总是会在末尾有数不清的空格
    • @MarcoMesen:刚刚编辑了我的例子。
    • @Mori 在这种情况下有效,但通常不容易更改整个页面的标记,使用 body 作为 flex 容器并将展开/折叠元素放在它的正下方,只是为了达到这种效果。另外,您将如何解决两个彼此相邻的扩展元素并且每个元素将其下方的相同内容下推的问题?
    • @Sandro 可以使用divs 代替htmlbody,但我认为两个或多个元素彼此相邻的问题无法解决。
    【解决方案13】:

    当我发布这篇文章时,已经有超过 30 个答案,但我觉得我的答案比 jake 已经 accepted answer 有所改进。

    我不满足于简单地使用max-height 和 CSS3 过渡引起的问题,因为正如许多评论者指出的那样,您必须将 max-height 值设置为非常接近实际高度,否则会出现延迟。有关该问题的示例,请参阅此 JSFiddle

    为了解决这个问题(仍然不使用 JavaScript),我添加了另一个转换 transform: translateY CSS 值的 HTML 元素。

    这意味着 max-heighttranslateY 都被使用了:max-height 允许元素向下推它下面的元素,而 translateY 给出了我们想要的“即时”效果。 max-height 的问题仍然存在,但影响有所减弱。 这意味着您可以为您的 max-height 值设置更大的高度,而不必担心它。

    总体好处是,在返回(折叠)时,用户会立即看到 translateY 动画,因此 max-height 花费多长时间并不重要。

    Solution as Fiddle

    body {
      font-family: sans-serif;
    }
    
    .toggle {
      position: relative;
      border: 2px solid #333;
      border-radius: 3px;
      margin: 5px;
      width: 200px;
    }
    
    .toggle-header {
      margin: 0;
      padding: 10px;
      background-color: #333;
      color: white;
      text-align: center;
      cursor: pointer;
    }
    
    .toggle-height {
      background-color: tomato;
      overflow: hidden;
      transition: max-height .6s ease;
      max-height: 0;
    }
    
    .toggle:hover .toggle-height {
      max-height: 1000px;
    }
    
    .toggle-transform {
      padding: 5px;
      color: white;
      transition: transform .4s ease;
      transform: translateY(-100%);
    }
    
    .toggle:hover .toggle-transform {
      transform: translateY(0);
    }
    <div class="toggle">
      <div class="toggle-header">
        Toggle!
      </div>
      <div class="toggle-height">
        <div class="toggle-transform">
          <p>Content!</p>
          <p>Content!</p>
          <p>Content!</p>
          <p>Content!</p>
        </div>
      </div>
    </div>
    
    <div class="toggle">
      <div class="toggle-header">
        Toggle!
      </div>
      <div class="toggle-height">
        <div class="toggle-transform">
          <p>Content!</p>
          <p>Content!</p>
          <p>Content!</p>
          <p>Content!</p>
        </div>
      </div>
    </div>

    【讨论】:

    • “并不重要”仍然是非常主观的。转换转换的即时反馈当然很好,但这肯定还有一些不足之处
    【解决方案14】:

    好的,所以我想我想出了一个超级简单的答案...... 没有 max-height,使用 relative 定位,适用于 li 元素,& 是纯 CSS。 除了 Firefox,我没有测试过任何东西,尽管从 CSS 来看,它应该适用于所有浏览器。

    小提琴:http://jsfiddle.net/n5XfG/2596/

    CSS

    .wrap { overflow:hidden; }
    
    .inner {
                margin-top:-100%;
        -webkit-transition:margin-top 500ms;
                transition:margin-top 500ms;
    }
    
    .inner.open { margin-top:0px; }
    

    HTML

    <div class="wrap">
        <div class="inner">Some Cool Content</div>
    </div>
    

    【讨论】:

    • 这将一直有效,直到元素的高度超过其宽度。它是如何使用百分比计算边距的基础:它们是根据元素的宽度计算的。因此,如果您有一个 1000 像素宽的元素,那么 1100 像素的元素对于此解决方案来说就太大了,这意味着您必须增加负上边距。基本上,这是与使用高度或最大高度完全相同的问题。
    【解决方案15】:

    编辑:向下滚动以获取更新的答案
    我正在制作一个下拉列表并看到这篇文章......许多不同的答案,但我决定也分享我的下拉列表,......它并不完美但至少它只会使用 css 进行下拉!我一直在使用 transform:translateY(y) 将列表转换为视图......
    你可以在测试中看到更多
    http://jsfiddle.net/BVEpc/4/
    我将 div 放在每个 li 后面,因为我的下拉列表来自上方,为了正确显示它们,这是必需的,我的 div 代码是:

    #menu div {
        transition: 0.5s 1s;
        z-index:-1;
        -webkit-transform:translateY(-100%);
        -webkit-transform-origin: top;
    }
    

    悬停是:

    #menu > li:hover div {
        transition: 0.5s;
        -webkit-transform:translateY(0);
    }
    

    因为 ul 高度设置为它可以覆盖你的身体内容的内容,所以我为 ul 做了这个:

     #menu ul {
        transition: 0s 1.5s;
        visibility:hidden;
        overflow:hidden;
    }
    

    并悬停:

    #menu > li:hover ul {
         transition:none;
         visibility:visible;
    }
    

    过渡后的第二次是延迟,它会在我的下拉列表被动态关闭后隐藏起来......
    希望以后有人能从中受益。

    编辑:我简直不敢相信人们真的在使用这个原型!此下拉菜单仅适用于一个子菜单,仅此而已!! 我已经更新了一个更好的,它可以有两个子菜单,用于 ltr 和 rtl 方向,支持 IE 8。
    Fiddle for LTR
    Fiddle for RTL
    希望将来有人会觉得这很有用。

    【讨论】:

      【解决方案16】:

      您可以从 height:0 过渡到 height:auto,前提是您还提供了 min-height 和 max-height。

      div.stretchy{
          transition: 1s linear;
      }
      
      div.stretchy.hidden{
          height: 0;
      }
      
      div.stretchy.visible{
          height: auto;
          min-height:40px;
          max-height:400px;
      }
      

      【讨论】:

      • 这不会转换height,只是max-heightheight 将立即从0 跳转到auto,而max-height 将过渡到似乎工作,但会产生与其他答案试图解决的问题相同的问题。
      • 这实际上是个好主意,并且在我可以估计最大高度并且不关心动画的确切长度时有效。
      【解决方案17】:

      弹性盒解决方案

      优点:

      • 简单
      • 没有JS
      • 平稳过渡

      缺点:

      • 元素需要放在固定高度的弹性容器中

      它的工作方式是始终在具有内容的元素上设置 flex-basis: auto,然后过渡 flex-grow 和 flex-shrink。

      编辑:受 Xbox One 界面启发改进的 JS Fiddle。

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        transition: 0.25s;
        font-family: monospace;
      }
      
      body {
        margin: 10px 0 0 10px;
      }
      
      .box {
        width: 150px;
        height: 150px;
        margin: 0 2px 10px 0;
        background: #2d333b;
        border: solid 10px #20262e;
        overflow: hidden;
        display: inline-flex;
        flex-direction: column;
      }
      
      .space {
        flex-basis: 100%;
        flex-grow: 1;
        flex-shrink: 0;    
      }
      
      p {
        flex-basis: auto;
        flex-grow: 0;
        flex-shrink: 1;
        background: #20262e;
        padding: 10px;
        width: 100%;
        text-align: left;
        color: white;
      }
      
      .box:hover .space {
        flex-grow: 0;
        flex-shrink: 1;
      }
        
      .box:hover p {
        flex-grow: 1;
        flex-shrink: 0;    
      }
      <div class="box">
        <div class="space"></div>
        <p>
          Super Metroid Prime Fusion
        </p>
      </div>
      <div class="box">
        <div class="space"></div>
        <p>
          Resident Evil 2 Remake
        </p>
      </div>
      <div class="box">
        <div class="space"></div>
        <p>
          Yolo The Game
        </p>
      </div>
      <div class="box">
        <div class="space"></div>
        <p>
          Final Fantasy 7 Remake + All Additional DLC + Golden Tophat
        </p>
      </div>
      <div class="box">
        <div class="space"></div>
        <p>
          DerpVille
        </p>
      </div>

      JS Fiddle

      【讨论】:

      • 我喜欢这个解决方案。结合绝对定位、嵌套列表和指针事件,我设法获得了一个相当不错的菜单。我没有发现很多缺点。有人吗?
      • 我的 JSFiddle 中的 CSS 由于某种原因损坏了,但是是的,我喜欢这个解决方案,谢谢!
      【解决方案18】:

      一句话解决:使用填充过渡.对于手风琴等大多​​数情况来说已经足够了,甚至更好,因为填充值通常不大,所以速度很快。

      如果你想让动画过程更好,只要提高padding值即可。

      .parent{ border-top: #999 1px solid;}
      h1{ margin: .5rem; font-size: 1.3rem}
      .children {
        height: 0;
        overflow: hidden;
        background-color: #dedede;
        transition: padding .2s ease-in-out, opacity .2s ease-in-out;
        padding: 0 .5rem;
        opacity: 0;
      }
      .children::before, .children::after{ content: "";display: block;}
      .children::before{ margin-top: -2rem;}
      .children::after{ margin-bottom: -2rem;}
      .parent:hover .children {
        height: auto;
        opacity: 1;
        padding: 2.5rem .5rem;/* 0.5 + abs(-2), make sure it's less than expected min-height */
      }
      <div class="parent">
        <h1>Hover me</h1>
        <div class="children">Some content
          <br>Some content
          <br>Some content
          <br>Some content
          <br>Some content
          <br>Some content
          <br>
        </div>
      </div>
      <div class="parent">
        <h1>Hover me(long content)</h1>
        <div class="children">Some content
          <br>Some content<br>Some content
          <br>Some content<br>Some content
          <br>Some content<br>Some content
          <br>Some content<br>Some content
          <br>Some content<br>Some content
          <br>
        </div>
      </div>
      <div class="parent">
        <h1>Hover me(short content)</h1>
        <div class="children">Some content
          <br>Some content
          <br>Some content
          <br>
        </div>
      </div>

      【讨论】:

        【解决方案19】:

        扩展@jake 的答案,过渡将一直达到最大高度值,从而导致非常快的动画 - 如果您同时设置过渡:悬停和关闭,您可以稍微控制疯狂的速度。

        所以 li:hover 是当鼠标进入状态,然后非悬停属性上的过渡将是鼠标离开。

        希望这会有所帮助。

        例如:

        .sidemenu li ul {
           max-height: 0px;
           -webkit-transition: all .3s ease;
           -moz-transition: all .3s ease;
           -o-transition: all .3s ease;
           -ms-transition: all .3s ease;
           transition: all .3s ease;
        }
        .sidemenu li:hover ul {
            max-height: 500px;
            -webkit-transition: all 1s ease;
           -moz-transition: all 1s ease;
           -o-transition: all 1s ease;
           -ms-transition: all 1s ease;
           transition: all 1s ease;
        }
        /* Adjust speeds to the possible height of the list */
        

        这是一个小提琴:http://jsfiddle.net/BukwJ/

        【讨论】:

          【解决方案20】:

          该解决方案使用了一些技术:

          但结果是我们只使用 CSS 就可以实现高效的过渡,并且可以使用一个过渡函数来平滑地实现过渡;圣杯!

          当然,也有缺点!我不知道如何控制内容被截断的宽度 (overflow:hidden);由于 padding-bottom hack,宽度和高度密切相关。虽然可能有办法,所以会回到它。

          https://jsfiddle.net/EoghanM/n1rp3zb4/28/

          body {
            padding: 1em;
          }
          
          .trigger {
            font-weight: bold;
          }
          
          /* .expander is there for float clearing purposes only */
          .expander::after {
            content: '';
            display: table;
            clear: both;
          }
          
          .outer {
            float: left; /* purpose: shrink to fit content */
            border: 1px solid green;
            overflow: hidden;
          }
          
          .inner {
            transition: padding-bottom 0.3s ease-in-out;  /* or whatever crazy transition function you can come up with! */
            padding-bottom: 0%;  /* percentage padding is defined in terms of width. The width at this level is equal to the height of the content */
            height: 0;
          
            /* unfortunately, change of writing mode has other bad effects like orientation of cursor */
            writing-mode: vertical-rl;
            cursor: default; /* don't want the vertical-text (sideways I-beam) */
            transform: rotate(-90deg) translateX(-100%);  /* undo writing mode */
            transform-origin: 0 0;
            margin: 0;  /* left/right margins here will add to height */
          }
          
          .inner > div { white-space: nowrap; }
          
          .expander:hover .inner,  /* to keep open when expanded */
          .trigger:hover+.expander .inner {
            padding-bottom: 100%;
          }
          <div class="trigger">HoverMe</div>
          <div class="expander">
            <div class="outer">
              <div class="inner">
                <div>First Item</div>
                <div>Content</div>
                <div>Content</div>
                <div>Content</div>
                <div>Long Content can't be wider than outer height unfortunately</div>
                <div>Last Item</div>
              </div>
            </div>
          </div>
          <div>
            after content</div>
          </div>

          【讨论】:

          • 正如您的消息来源所说的“响应式方形”,它仅在您具有方形内容时才有效。所以它甚至不接近公认的答案......
          【解决方案21】:

          我最近一直在转换 li 元素上的 max-height 而不是包装 ul

          原因是与大max-heights 相比,小max-heights 的延迟远没有那么明显(如果有的话),我也可以设置我的max-height 值相对于 font-sizefont-size 而不是使用emsrems 任意大数。

          如果我的字体大小是 1rem,我会将我的 max-height 设置为类似 3rem 的内容(以适应换行文本)。你可以在这里看到一个例子:

          http://codepen.io/mindfullsilence/pen/DtzjE

          【讨论】:

          • 如果您需要转换包含多个具有设置/已知高度的子元素的父元素,这确实是一个有用的解决方案,就像大多数导航场景一样。
          【解决方案22】:

          您可以通过使用剪辑路径创建反向(折叠)动画来做到这一点。

          #child0 {
              display: none;
          }
          #parent0:hover #child0 {
              display: block;
              animation: height-animation;
              animation-duration: 200ms;
              animation-timing-function: linear;
              animation-fill-mode: backwards;
              animation-iteration-count: 1;
              animation-delay: 200ms;
          }
          @keyframes height-animation {
              0% {
                  clip-path: polygon(0% 0%, 100% 0.00%, 100% 0%, 0% 0%);
              }
              100% {
                  clip-path: polygon(0% 0%, 100% 0.00%, 100% 100%, 0% 100%);
              }
          }
          <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>

          【讨论】:

          • 很好,但是如何用动画取消显示呢?
          【解决方案23】:

          灵活高度 仅 CSS 解决方案

          我偶然发现了一个使用弹性行为的古怪解决方案。它至少适用于 Chrome 和 Firefox。

          • 首先,高度过渡只在 0 到 100% 之间起作用,两个 数值。由于“auto”不是数值,因此是小数 增量不存在于 0 和“自动”之间。 100% 是灵活的 值,因此不需要特定的高度。

          • 其次,隐藏内容的外层容器和内层容器都必须设置为display:flex和flex-direction:column。

          • 第三,外层容器必须有高度属性。高度是多少并不重要,因为弹性行为优先于高度,所以我使用高度 0。

          .outer-container { height: 0; display: flex; flex-direction: column; }
          
          .inner-container { display: flex; flex-direction: column; }
          
          .hidden-content { 
              height: 0; 
              opacity: 0; 
              transition: height 1s 0.5s ease-in-out, opacity 0.5s ease-in-out;
              /* transition out: first fade out opacity, then shrink height after a delay equal to the opacity duration */
              }
          
          .trigger:hover + .inner-container > .hidden-content { 
              height: 100%; 
              opacity: 1; 
              transition: height 1s ease-in-out, opacity 0.5s 1s ease-in-out;
              /* transition in: first expand height, then fade in opacity after a delay equal to the height duration */
          }
          <div class="outer-container">
            <a href="#" class="trigger">Hover to Reveal Inner Container's Hidden Content</a>
            <div class="inner-container">
              <div class="hidden-content">This is hidden content. When triggered by hover, its height transitions from 0 to 100%, which pushes other content in the same container down gradually.</div>
              <div>Within the same container, this other content is pushed down gradually as the hidden content's height transitions from 0 to 100%.</div>
            </div>
          </div>

          按运行代码片段按钮以查看正在运行的过渡。 它只是 CSS,没有特定的高度。

          【讨论】:

          • 而不是不透明度,为什么不将隐藏内容设置为使用overflow: hidden。然而,对我来说看起来不错,因为外部容器的高度为 0,所以它不会调整父级高度的大小,这可能是不需要的。 play.tailwindcss.com/aFH08CKXpU
          • 我的理解是切换溢出:隐藏会使许多转换不起作用。在其他项目中,我使用 javascript 在应用转换之前切换溢出:隐藏(我更喜欢),但对于这篇文章,目标是仅使用 CSS。
          • 将外部容器高度设置为“适合内容”为我解决了这个问题,因为我在外部容器下方还有其他内容也需要向下推。
          【解决方案24】:

          我知道这个问题要求没有 JavaScript 的解决方案。但对于那些感兴趣的人,这里是我使用一点 JS 的解决方案。

          ok,所以默认高度会变化的元素的css设置为height: 0;,打开时为height: auto;。它还具有transition: height .25s ease-out;。但当然问题是它不会转换到或从height: auto;

          所以我所做的是在打开或关闭时将高度设置为元素的 scrollHeight 属性。这种新的内联样式将具有更高的特异性并覆盖 height: auto;height: 0; 以及过渡运行。

          打开时,我添加一个 transitionend 事件侦听器,它将只运行一次,然后删除内联样式,将其设置回 height: auto;,这将允许元素在必要时调整大小,如在这个带有子菜单 https://codepen.io/ninjabonsai/pen/GzYyVe 的更复杂的示例中

          关闭时,我会在下一个 event loop 循环之后立即使用 setTimeout 立即删除内联样式。这意味着 height: auto; 被暂时覆盖,允许转换回 height 0;

          const showHideElement = (element, open) => {
            element.style.height = element.scrollHeight + 'px';
            element.classList.toggle('open', open);
          
            if (open) {
              element.addEventListener('transitionend', () => {
                element.style.removeProperty('height');
              }, {
                once: true
              });
            } else {
              window.setTimeout(() => {
                element.style.removeProperty('height');
              });
            }
          }
          
          const menu = document.body.querySelector('#menu');
          const list = document.body.querySelector('#menu > ul')
          
          menu.addEventListener('mouseenter', () => showHideElement(list, true));
          menu.addEventListener('mouseleave', () => showHideElement(list, false));
          #menu > ul {
            height: 0;
            overflow: hidden;
            background-color: #999;
            transition: height .25s ease-out;
          }
          
          #menu > ul.open {
            height: auto;
          }
          <div id="menu">
            <a>hover me</a>
            <ul>
              <li>item</li>
              <li>item</li>
              <li>item</li>
              <li>item</li>
              <li>item</li>
            </ul>
          </div>

          【讨论】:

            【解决方案25】:

            使用 line-heightpaddingopacitymargin 的替代 CSS-only 解决方案:

            body {
              background-color: linen;
            }
            
            main {
              background-color: white;
            }
            
            [id^="toggle_"] ~ .content {
              line-height: 0;
              opacity: 0;
              padding: 0 .5rem;
              transition: .2s ease-out;
            }
            
            [id^="toggle_"] ~ .content > p {
              margin: 0;
                transition: .2s ease-out;
            }
            
            [id^="toggle_"]:checked ~ .content   {
              opacity: 1;
              padding: .5rem;
              line-height: 1.5;
            }
            
            [id^="toggle_"]:checked ~ .content p  {
                margin-bottom: .75rem;
            }
            
            [id^="toggle_"] + label {
              display: flex;
              justify-content: space-between;
              padding: 0.5em 1em;
              background: lightsteelblue;
              border-bottom: 1px solid gray;
              cursor: pointer;
            }
            
            [id^="toggle_"] + label:before {
              content: "Show";
            }
            
            [id^="toggle_"]:checked + label:before {
              content: "Hide";
            }
            
            [id^="toggle_"] + label:after {
              content: "BC";
            }
            
            [id^="toggle_"]:checked + label:after {
              content: "B2";
            }
            <main>
                <div>
                    <input type="checkbox" id="toggle_1" hidden>
                    <label for="toggle_1" hidden></label>
                    <div class="content">
                        <p>
                            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dolor neque, commodo quis leo ut, auctor tincidunt mauris. Nunc fringilla tincidunt metus, non gravida lorem condimentum non. Duis ornare purus nisl, at porta arcu eleifend eget. Integer lorem ante, porta vulputate dui ut, blandit tempor tellus. Proin facilisis bibendum diam, sit amet rutrum est feugiat ut. Mauris rhoncus convallis arcu in condimentum. Donec volutpat dui eu mollis vulputate. Nunc commodo lobortis nunc at ultrices. Suspendisse in lobortis diam. Suspendisse eget vestibulum ex.
                        </p>
                    </div>
                </div>
                <div>
                    <input type="checkbox" id="toggle_2" hidden>
                    <label for="toggle_2" hidden></label>
                    <div class="content">
                        <p>
                            Maecenas laoreet nunc sit amet nulla ultrices auctor. Vivamus sed nisi vitae nibh condimentum pulvinar eu vel lorem. Sed pretium viverra eros ut facilisis. In ut fringilla magna. Sed a tempor libero. Donec sapien libero, lacinia sed aliquet ut, imperdiet finibus tellus. Nunc tellus lectus, rhoncus in posuere quis, tempus sit amet enim. Morbi et erat ac velit fringilla dignissim. Donec commodo, est id accumsan cursus, diam dui hendrerit nisi, vel hendrerit purus dolor ut risus. Phasellus mattis egestas ipsum sed ullamcorper. In diam ligula, rhoncus vel enim et, imperdiet porta justo. Curabitur vulputate hendrerit nisl, et ultricies diam. Maecenas ac leo a diam cursus ornare nec eu quam.
                        </p>
                        <p>Sed non vulputate purus, sed consectetur odio. Sed non nibh fringilla, imperdiet odio nec, efficitur ex. Suspendisse ut dignissim enim. Maecenas felis augue, tempor sit amet sem fringilla, accumsan fringilla nibh. Quisque posuere lacus tortor, quis malesuada magna elementum a. Nullam id purus in ante molestie tincidunt. Morbi luctus orci eu egestas dignissim. Sed tincidunt, libero quis scelerisque bibendum, ligula nisi gravida libero, id lacinia nulla leo in elit.
                        </p>
                        <p>Aenean aliquam risus id consectetur sagittis. Aliquam aliquam nisl eu augue accumsan, vel maximus lorem viverra. Aliquam ipsum dolor, tempor et justo ac, fermentum mattis dui. Etiam at posuere ligula. Vestibulum tortor metus, viverra vitae mi non, laoreet iaculis purus. Praesent vel semper nibh. Curabitur a congue lacus. In et pellentesque lorem. Morbi posuere felis non diam vulputate, non vulputate ex vehicula. Vivamus ultricies, massa id sagittis consequat, sem mauris tincidunt nunc, eu vehicula augue quam ut mauris.
                        </p>
                    </div>
                </div>
                    <div>
                    <input type="checkbox" id="toggle_3" hidden>
                    <label for="toggle_3" hidden></label>
                    <div class="content">
                        <p>
                            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dolor neque, commodo quis leo ut, auctor tincidunt mauris. Nunc fringilla tincidunt metus, non gravida lorem condimentum non. Duis ornare purus nisl, at porta arcu eleifend eget. Integer lorem ante, porta vulputate dui ut, blandit tempor tellus. Proin facilisis bibendum diam, sit amet rutrum est feugiat ut. Mauris rhoncus convallis arcu in condimentum. Donec volutpat dui eu mollis vulputate. Nunc commodo lobortis nunc at ultrices. Suspendisse in lobortis diam. Suspendisse eget vestibulum ex.
                        </p>
                        <p>Sed non vulputate purus, sed consectetur odio. Sed non nibh fringilla, imperdiet odio nec, efficitur ex. Suspendisse ut dignissim enim. Maecenas felis augue, tempor sit amet sem fringilla, accumsan fringilla nibh. Quisque posuere lacus tortor, quis malesuada magna elementum a. Nullam id purus in ante molestie tincidunt. Morbi luctus orci eu egestas dignissim. Sed tincidunt, libero quis scelerisque bibendum, ligula nisi gravida libero, id lacinia nulla leo in elit.
                        </p>
                    </div>
                </div>
            </main>

            【讨论】:

            • 看起来不错!不在乎身高。诚然,这最适合文本内容。 (令人震惊的是,如此简单的事情却如此难以实现。我对这个问题中作为答案而抛出的骇客数量感到震惊。)
            • 顺便说一句,我会默认添加 pointer-events: none 并在选中时添加 auto (到内容),只是为了摆脱文本选择指针并摆脱选择不可见文本的能力。
            【解决方案26】:

            我想我想出了一个非常可靠的解决方案

            好的!我知道这个问题和互联网一样古老,但我想我有一个解决方案,我把它变成了plugin called mutant-transition。只要 DOM 发生变化,我的解决方案就会为跟踪元素设置 style="" 属性。最终结果是您可以使用良好的 ole CSS 进行转换,而不是使用 hacky 修复程序或特殊的 javascript。您唯一需要做的就是使用data-mutant-attributes="X" 设置您要在相关元素上跟踪的内容。

            <div data-mutant-attributes="height">                                                                      
                    This is an example with mutant-transition                                                                                                          
                </div>
            

            就是这样!此解决方案使用 MutationObserver 来跟踪 DOM 中的更改。因此,您实际上不必设置任何东西或使用 javascript 手动设置动画。自动跟踪更改。但是,因为它使用了 MutationObserver,所以这只会在 IE11+ 中转换。

            小提琴!

            【讨论】:

            • 通常,当有人问如何“不用 JavaScript”做某事时,这意味着该解决方案必须在禁用 JavaScript 的浏览器上运行。这并不意味着“不编写我自己的脚本”。所以说你的插件可以在不使用 JavaScript 的情况下使用,充其量是一种误导——考虑到它是一个 JavaScript 插件。
            • 我应该澄清一下,当我说你不必使用任何特殊的 javascript 时,我的意思是你不必任何javascript。只需包含 JS 库并指定要在 HTML 中查看的属性。您不必使用固定高度的 css,或想出任何办法。只是风格和去。
            【解决方案27】:

            这是一种从任何起始高度(包括 0)过渡到自动(全尺寸和灵活)的方法,无需基于每个节点的硬设置代码或任何用户代码来初始化:https://github.com/csuwildcat/transition-auto。这基本上是您想要的圣杯,我相信 --> http://codepen.io/csuwldcat/pen/kwsdF。只需将以下 JS 文件插入您的页面,之后您需要做的就是从您要扩展和收缩的节点中添加/删除单个布尔属性 - reveal=""

            这是您作为用户需要做的所有事情,一旦您包含了示例代码下方的代码块:

            /*** Nothing out of the ordinary in your styles ***/
            <style>
                div {
                    height: 0;
                    overflow: hidden;
                    transition: height 1s;
                }
            </style>
            
            /*** Just add and remove one attribute and transition to/from auto! ***/
            
            <div>
                I have tons of content and I am 0px in height you can't see me...
            </div>
            
            <div reveal>
                 I have tons of content and I am 0px in height you can't see me...
                 but now that you added the 'reveal' attribute, 
                 I magically transitioned to full height!...
            </div>
            

            这是要包含在您的页面中的代码块,之后,一切都是肉汁:

            将此 JS 文件拖放到您的页面中 - 一切正常™

            /* 高度代码:自动;过渡 */

            (function(doc){
            
            /* feature detection for browsers that report different values for scrollHeight when an element's overflow is hidden vs visible (Firefox, IE) */
            var test = doc.documentElement.appendChild(doc.createElement('x-reveal-test'));
                test.innerHTML = '-';
                test.style.cssText = 'display: block !important; height: 0px !important; padding: 0px !important; font-size: 0px !important; border-width: 0px !important; line-height: 1px !important; overflow: hidden !important;';
            var scroll = test.scrollHeight || 2;
            doc.documentElement.removeChild(test);
            
            var loading = true,
                numReg = /^([0-9]*.?[0-9]*)(.*)/,
                skipFrame = function(fn){
                  requestAnimationFrame(function(){
                    requestAnimationFrame(fn);
                  });
                },
                /* 2 out of 3 uses of this function are purely to work around Chrome's catastrophically busted implementation of auto value CSS transitioning */
                revealFrame = function(el, state, height){
                    el.setAttribute('reveal-transition', 'frame');
                    el.style.height = height;
                    skipFrame(function(){
                        el.setAttribute('reveal-transition', state);
                        el.style.height = '';
                    });
                },
                transitionend = function(e){
                  var node = e.target;
                  if (node.hasAttribute('reveal')) {
                    if (node.getAttribute('reveal-transition') == 'running') revealFrame(node, 'complete', '');
                  } 
                  else {
                    node.removeAttribute('reveal-transition');
                    node.style.height = '';
                  }
                },
                animationstart = function(e){
                  var node = e.target,
                      name = e.animationName;   
                  if (name == 'reveal' || name == 'unreveal') {
            
                    if (loading) return revealFrame(node, 'complete', 'auto');
            
                    var style = getComputedStyle(node),
                        offset = (Number(style.paddingTop.match(numReg)[1])) +
                                 (Number(style.paddingBottom.match(numReg)[1])) +
                                 (Number(style.borderTopWidth.match(numReg)[1])) +
                                 (Number(style.borderBottomWidth.match(numReg)[1]));
            
                    if (name == 'reveal'){
                      node.setAttribute('reveal-transition', 'running');
                      node.style.height = node.scrollHeight - (offset / scroll) + 'px';
                    }
                    else {
                        if (node.getAttribute('reveal-transition') == 'running') node.style.height = '';
                        else revealFrame(node, 'running', node.scrollHeight - offset + 'px');
                    }
                  }
                };
            
            doc.addEventListener('animationstart', animationstart, false);
            doc.addEventListener('MSAnimationStart', animationstart, false);
            doc.addEventListener('webkitAnimationStart', animationstart, false);
            doc.addEventListener('transitionend', transitionend, false);
            doc.addEventListener('MSTransitionEnd', transitionend, false);
            doc.addEventListener('webkitTransitionEnd', transitionend, false);
            
            /*
                Batshit readyState/DOMContentLoaded code to dance around Webkit/Chrome animation auto-run weirdness on initial page load.
                If they fixed their code, you could just check for if(doc.readyState != 'complete') in animationstart's if(loading) check
            */
            if (document.readyState == 'complete') {
                skipFrame(function(){
                    loading = false;
                });
            }
            else document.addEventListener('DOMContentLoaded', function(e){
                skipFrame(function(){
                    loading = false;
                });
            }, false);
            
            /* Styles that allow for 'reveal' attribute triggers */
            var styles = doc.createElement('style'),
                t = 'transition: none; ',
                au = 'animation: reveal 0.001s; ',
                ar = 'animation: unreveal 0.001s; ',
                clip = ' { from { opacity: 0; } to { opacity: 1; } }',
                r = 'keyframes reveal' + clip,
                u = 'keyframes unreveal' + clip;
            
            styles.textContent = '[reveal] { -ms-'+ au + '-webkit-'+ au +'-moz-'+ au + au +'}' +
                '[reveal-transition="frame"] { -ms-' + t + '-webkit-' + t + '-moz-' + t + t + 'height: auto; }' +
                '[reveal-transition="complete"] { height: auto; }' +
                '[reveal-transition]:not([reveal]) { -webkit-'+ ar +'-moz-'+ ar + ar +'}' +
                '@-ms-' + r + '@-webkit-' + r + '@-moz-' + r + r +
                '@-ms-' + u +'@-webkit-' + u + '@-moz-' + u + u;
            
            doc.querySelector('head').appendChild(styles);
            
            })(document);
            

            /* 演示代码 */

                document.addEventListener('click', function(e){
                  if (e.target.nodeName == 'BUTTON') {
                    var next = e.target.nextElementSibling;
                    next.hasAttribute('reveal') ? next.removeAttribute('reveal') : next.setAttribute('reveal', '');
                  }
                }, false);
            

            【讨论】:

            • 我想对此投赞成票,但您没有回答如何使其工作的问题,而是共享了一个您编写的插件以使其工作。好奇的我们只能对您的插件进行逆向工程,这并不好玩。我希望您能更新您的答案,以包含对您的插件的作用和原因的更多解释。更改代码以使其更具解释性。例如,您有一整段代码只写出静态 CSS。我宁愿看到 CSS,也不愿看到生成它的代码。您可以省略无聊的部分,例如对所有浏览器前缀重复。
            【解决方案28】:

            Jake 对最大高度进行动画处理的回答很棒,但我发现设置较大的最大高度所导致的延迟很烦人。

            可以将可折叠内容移动到内部 div 中,并通过获取内部 div 的高度来计算最大高度(通过 JQuery,它是 outerHeight())。

            $('button').bind('click', function(e) { 
              e.preventDefault();
              w = $('#outer');
              if (w.hasClass('collapsed')) {
                w.css({ "max-height": $('#inner').outerHeight() + 'px' });
              } else {
                w.css({ "max-height": "0px" });
              }
              w.toggleClass('collapsed');
            });
            

            这是一个 jsfiddle 链接:http://jsfiddle.net/pbatey/duZpT

            这是一个 jsfiddle,所需代码量绝对最少:http://jsfiddle.net/8ncjjxh8/

            【讨论】:

              【解决方案29】:

              我能够做到这一点。我有一个 .child 和一个 .parent div。通过absolute 定位,子 div 完全适合父级的宽度/高度。然后,我为 translate 属性设置动画以将其 Y 值向下推 100%。它的动画非常流畅,没有像这里的任何其他解决方案那样的故障或缺点。

              像这样,伪代码

              .parent{ position:relative; overflow:hidden; } 
              /** shown state */
              .child {
                position:absolute;top:0;:left:0;right:0;bottom:0;
                height: 100%;
                transition: transform @overlay-animation-duration ease-in-out;
                .translate(0, 0);
              }
              
              /** Animate to hidden by sliding down: */
              .child.slidedown {
                .translate(0, 100%); /** Translate the element "out" the bottom of it's .scene container "mask" so its hidden */
              }
              

              您可以在 .parentpx% 中指定 height,或者保留为 auto。这个 div 然后在向下滑动时屏蔽掉 .child div。

              【讨论】:

              • 这方面的工作示例将不胜感激。
              【解决方案30】:

              我没有详细阅读所有内容,但最近遇到了这个问题,我做了以下事情:

              div.class{
                 min-height:1%;
                 max-height:200px;
                 -webkit-transition: all 0.5s ease;
                 -moz-transition: all 0.5s ease;
                 -o-transition: all 0.5s ease;
                 -webkit-transition: all 0.5s ease;
                 transition: all 0.5s ease;
                 overflow:hidden;
              }
              
              div.class:hover{
                 min-height:100%;
                 max-height:3000px;
              }
              

              这允许您有一个 div,它首先显示高达 200px 高度的内容,并且在悬停时它的大小至少变得与 div 的整个内容一样高。 Div 不会变成 3000px,但 3000px 是我施加的限制。确保在非 :hover 时进行过渡,否则你可能会得到一些奇怪的渲染。这样 :hover 继承自非 :hover。

              从 px 到 % 或到 auto 的转换不起作用。您需要使用相同的计量单位。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2014-04-15
                • 1970-01-01
                • 1970-01-01
                • 2022-12-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多