How can I transition height: 0; to height: auto; u

2018-12-30 23:37发布

I am trying to make a <ul> slide down using CSS transitions.

The <ul> starts off at height: 0;. On hover, the height is set to height:auto;. However, this is causing it to simply appear, not transition,

If I do it from height: 40px; to height: auto;, then it will slide up to height: 0;, and then suddenly jump to the correct height.

How else could I do this without using 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>

30条回答
伤终究还是伤i
2楼-- · 2018-12-31 00:08

The solution that I've always used was to first fade out, then shrink the font-size, padding and margin values. It doesn't look the same as a wipe, but it works without a static height or max-height.

Working example:

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

查看更多
旧时光的记忆
3楼-- · 2018-12-31 00:08

I've recently been transitioning the max-height on the li elements rather than the wrapping ul.

The reasoning is that the delay for small max-heights is far less noticeable (if at all) compared to large max-heights, and I can also set my max-height value relative to the font-size of the li rather than some arbitrary huge number by using ems or rems.

If my font size is 1rem, I'll set my max-height to something like 3rem (to accommodate wrapped text). You can see an example here:

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

查看更多
公子世无双
4楼-- · 2018-12-31 00:08

The max-height solution from Jake works well, if the hard-coded max-height value supplied is not much bigger than the real height (because otherwise there are undesirable delays and timing problems). On the other hand if the hard-coded value accidentially is not bigger than the real height the element won't open up completely.

The following CSS only solution also requires a hard-coded size that should be bigger than most of the occurring real sizes. However this solution also works if the real size is in some situations bigger than the hard-coded size. In that event the transition might jump a bit, but it will never leave a partially visible element. So this solution could also be used for unknown content, e.g. from a database, where you just know that the content is usually not bigger than x pixels, but there are exceptions.

Idea is to use a negative value for margin-bottom (or margin-top for a slightly diffenrent animation) and to place the content element into a middle element with overflow:hidden. The negative margin of the content element so reduces the height of the middle element.

The following code uses a transition on margin-bottom from -150px to 0px. This alone works fine as long as the content element is not higher than 150px. In addition it uses a transition on max-height for the middle element from 0px to 100%. This finally hides the middle element if the content element is higher than 150px. For max-height the transition is just used to delay its application by a second when closing, not for a smooth visiual effect ( and therefore it can run from 0px to 100%).

CSS:

.content {
  transition: margin-bottom 1s ease-in;
  margin-bottom: -150px;
}
.outer:hover .middle .content {
  transition: margin-bottom 1s ease-out;
  margin-bottom: 0px
}
.middle {
  overflow: hidden;
  transition: max-height .1s ease 1s;
  max-height: 0px
}
.outer:hover .middle {
  transition: max-height .1s ease 0s;
  max-height: 100%
}

HTML:

<div class="outer">
  <div class="middle">
    <div class="content">
      Sample Text
      <br> Sample Text
      <br> Sample Text
      <div style="height:150px">Sample Test of height 150px</div>
      Sample Text
    </div>
  </div>
  Hover Here
</div>

The value for margin bottom should be negative and as close as possible to the real height of the content element. If it('s absoute value) is bigger there are similar delay and timing problems as with the max-height solutions, which however can be limited as long as the hard coded size is not much bigger than the real one. If the absolute value for margin-bottom is smaller than the real height the tansition jumps a bit. In any case after the transition the content element is either fully displayed or fully removed.

For more details see my blog post http://www.taccgl.org/blog/css_transition_display.html#combined_height

查看更多
永恒的永恒
5楼-- · 2018-12-31 00:09

You should use scaleY instead.

HTML:

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

CSS:

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

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

I've made a vendor prefixed version of the above code on jsfiddle, http://jsfiddle.net/dotnetCarpenter/PhyQc/9/ and changed your jsfiddle to use scaleY instead of height, http://jsfiddle.net/dotnetCarpenter/7cnfc/206/.

查看更多
妖精总统
6楼-- · 2018-12-31 00:09

Here's a solution I just used in combination with jQuery. This works for the following HTML structure:

<nav id="main-nav">
    <ul>
        <li>
            <a class="main-link" href="yourlink.html">Link</a>
            <ul>
                <li><a href="yourlink.html">Sub Link</a></li>
            </ul>
        </li>
    </ul>
</nav>

and the function:

    $('#main-nav li ul').each(function(){
        $me = $(this);

        //Count the number of li elements in this UL
        var liCount = $me.find('li').size(),
        //Multiply the liCount by the height + the margin on each li
            ulHeight = liCount * 28;

        //Store height in the data-height attribute in the UL
        $me.attr("data-height", ulHeight);
    });

You could then use a click function to set and remove the height using css()

$('#main-nav li a.main-link').click(function(){
    //Collapse all submenus back to 0
    $('#main-nav li ul').removeAttr('style');

    $(this).parent().addClass('current');

    //Set height on current submenu to it's height
    var $currentUl = $('li.current ul'),
        currentUlHeight = $currentUl.attr('data-height');
})

CSS:

#main-nav li ul { 
    height: 0;
    position: relative;
    overflow: hidden;
    opacity: 0; 
    filter: alpha(opacity=0); 
    -ms-filter: "alpha(opacity=0)";
    -khtml-opacity: 0; 
    -moz-opacity: 0;
    -webkit-transition: all .6s ease-in-out;
    -moz-transition: all .6s ease-in-out;
    -o-transition: all .6s ease-in-out;
    -ms-transition: all .6s ease-in-out;
    transition: all .6s ease-in-out;
}

#main-nav li.current ul {
    opacity: 1.0; 
    filter: alpha(opacity=100); 
    -ms-filter: "alpha(opacity=100)";
    -khtml-opacity: 1.0; 
    -moz-opacity: 1.0;
}

.ie #main-nav li.current ul { height: auto !important }

#main-nav li { height: 25px; display: block; margin-bottom: 3px }
查看更多
无与为乐者.
7楼-- · 2018-12-31 00:10

The accepted answer works for most cases, but it doesn't work well when your div can vary greatly in height — the animation speed is not dependent on the actual height of the content, and it can look choppy.

You can still perform the actual animation with CSS, but you need to use JavaScript to compute the height of the items, instead of trying to use auto. No jQuery is required, although you may have to modify this a bit if you want compatibility (works in the latest version of 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>

查看更多
登录 后发表回答