84

I want to create an expand/collapse animation that's powered only by classnames (javascript is used to toggle the classnames).

I'm giving one class max-height: 4em; overflow: hidden;

and the other max-height: 255em; (I also tried the value none, which didn't animate at all)

this to animate: transition: max-height 0.50s ease-in-out;

I used CSS transitions to switch between them, but the browser seems to be animating all those extra em's, so it creates a delay in the collapse effect.

Is there a way of doing it (in the same spirit - with css classnames) that doesn't have that side-effect (I can put a lower pixel count, but that obviously has drawbacks, since it might cut off legit text - that's the reason for the big value, so it doesn't cut off legit long text, only ridiculously long ones)

See the jsFiddle - http://jsfiddle.net/wCzHV/1/ (click on the text container)

4
  • 1
    Not perfect solution: jsfiddle.net/wCzHV/2 Is this acceptable? Commented Apr 16, 2013 at 5:06
  • 1
    @Passerby - it might be possible with negative margin, but your example doesn't seem to support having arbitrary height or exposing a preset number of lines (both these things are supported in the original and are essential) Commented Apr 16, 2013 at 5:26
  • That's why I don't post that as an answer. This question has somewhat been asked on SO, and I would say that it's almost impossible to achieve in pure CSS (if you don't want to hard-code some value). If you really really need to do that, involve JS. Commented Apr 16, 2013 at 5:39
  • 1
    I was worried that it might not be possible, that's too bad, I hate to see it almost working Commented Apr 16, 2013 at 7:11

8 Answers 8

143

Fix delay solution:

Put cubic-bezier(0, 1, 0, 1) transition function for element.

scss

.text { overflow: hidden; max-height: 0; transition: max-height 0.5s cubic-bezier(0, 1, 0, 1); &.full { max-height: 1000px; transition: max-height 1s ease-in-out; } } 

css

.text { overflow: hidden; max-height: 0; transition: max-height 0.5s cubic-bezier(0, 1, 0, 1); } .text.full { max-height: 1000px; transition: max-height 1s ease-in-out; } 
Sign up to request clarification or add additional context in comments.

10 Comments

Best way to do it pure-css. Thanks for the reminder, lost that curve a while ago.
Could someone explain what does &.full mean in this context?
@Jakub &.full is saying - "When the class .full gets added to an element with the class .text, update the CSS as follows..." Its basically the SCSS way of doing this .text.full {}, which is how it would look in normal CSS. It's called nesting and is a feature of Sass/SCSS.
This answer desperately needs an explanation. It's upvoted quite a bit, but it doesn't seem to work properly. The only effect I'm seeing, is that the retracting transition is only 0.5s vs the expanding transition that's 1s.
After looking into it more, I can say with some certainty that this is not a fix, it's a workaround, and it's not even a good one. The only things happening are basically the shorter duration of the animation, and the fact that the function being used is basically an extreme ease-out. So basically, you're just contracting those 1000px of max height really quickly, and slowing down in the end, so the final bit looks somewhat like a regular ease-in-out. Again: not a fix, but a workaround, and not a good one.
|
13

This is an old question but I just worked out a way to do it and wanted to stick it somewhere so I know where to find it should I need it again :o)

So I needed an accordion with clickable "sectionHeading" divs that reveal/hide corresponding "sectionContent" divs. The section content divs have variable heights, which creates a problem as you can't animate height to 100%. I've seen other answers suggesting animating max-height instead but this means sometimes you get delays when the max-height you use is larger than the actual height.

The idea is to use jQuery on load to find and explicitly set the heights of the "sectionContent" divs. Then add a css class 'noHeight' to each a click handler to toggle it:

$(document).ready(function() { $('.sectionContent').each(function() { var h = $(this).height(); $(this).height(h).addClass('noHeight'); }); $('.sectionHeader').click(function() { $(this).next('.sectionContent').toggleClass('noHeight'); }); }); 

For completeness, the relevant css classes:

.sectionContent { overflow: hidden; -webkit-transition: all 0.3s ease-in; -moz-transition: all 0.3s ease-in; -o-transition: all 0.3s ease-in; transition: all 0.3s ease-in; } .noHeight { height: 0px !important; } 

Now the height transitions work without any delays.

2 Comments

Works like a charm, though I did notice that the height doesn't update properly if the window is resized enough to squeeze elements below the overflow-hidden container. To get around this I put the accordian contents in another div without overflow-hidden, set that div's height on page load, and then put a resize handler on the page that makes sure the accordian's height always matches the inner div. Works beautifully!
From what I can tell, this solution only works when the content has a fixed height (100px/100em/etc) but no animation will occur if content has height auto, unset, 100%, or anything like that
11

There is a new pure CSS solution that solves all the issues that other CSS solutions had. You can use grid-template-rows: 0fr; for collapsed and 1fr; for expanded. Look at the code for details:

/* theme */ .panel-expand { background-color: black; color: white; padding: 20px; width: 400px; transition: all 200ms ease-in-out; } /* mechanism */ .panel-expand { display: grid; grid-template-rows: 0fr; } .panel-expand:hover { grid-template-rows: 1fr; } .panel-expand__content { overflow: hidden; }
<div class="panel-expand"> <div class="panel-expand__content"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Modi corrupti, sint distinctio deserunt vel unde consequatur sequi nobis necessitatibus quis ad officiis doloremque ab, blanditiis facere possimus obcaecati voluptate sed!</p> <p>Voluptas, ipsa porro? Ex nostrum culpa magnam magni officiis laboriosam fugit molestias amet reiciendis sunt facere, debitis odit neque totam natus ad aliquam minus, sed repellat eveniet, et impedit ipsa.</p> </div> </div>

I found it mentioned in Kevin Powell video https://www.youtube.com/watch?v=B_n4YONte5A

3 Comments

This is fine if the height of the grid itself doesn't change, but if you need to expand the grid obviously it won't work.
Nice ! That is very usefull.
This is hilarious, and amazing
6

2024

The upcoming support for interpolate-size makes this straightforward in pure CSS. The allow-keywords value enables computed properties (e.g. height: auto) to be animated.

Browser compatibility

document.getElementById('button').addEventListener('click', e => { const btn = e.currentTarget const expand = btn.className !== 'expanded' btn.className = expand ? 'expanded' : '' btn.innerText = expand ? 'Collapse' : 'Expand' })
/* Styling */ body { background: #111; color: #ccc; font-family: sans-serif; } #button { border-radius: 7px; background: #13a; border: none; height: 30px; font-weight: bold; padding-inline: 1rem; color: #fff; } .content p { margin: .5rem 1rem; } /* Transition */ :root { interpolate-size: allow-keywords; } .content { height: 0; overflow: hidden; transition: height 250ms ease-in-out; } .container:has(#button.expanded) .content { height: auto; }
<div class="container"> <button id="button"> Expand </button> <div class="content"> <p> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </p> </div> </div>

MDN

The interpolate-size CSS property allows you to enable animations and transitions between a value and an intrinsic size value such as auto, fit-content, or max-content.

This property is typically used to animate the width and/or height of a container between a and the full size of its content (i.e. between "closed" and "open" or "hide" and "reveal" states) when animating a non-box-model CSS property, such as transform, is not a viable solution.

3 Comments

Hmmm. Even on chrome 133, this doesn't work.
@VictoryOsiobe It is working perfectly for me on MS Edge 133.0.3065.69 (which is a fork of Chrome/Chromium)
Thank you so much, this works great. interpolate-size: allow-keywords; is my new favorite.
2

In case anyone is reading this, I have not found a solution and went with an expand-only effect (which was achieved by moving the transition style to the expanded class definition)

2 Comments

would you mind posting the solution in a JS Fiddle or similar?
It's 2016 and it still doesn't have a solution. It's a shame.
1

The solution is actually quite simple. Make a child div, that has the content. The parent div will be the one that expands collapses.

On load the parent div will have a max-height. when toggling, you can check the child height by writing document.querySelector('.expand-collapse-inner').clientHeight; and set the maxheight with javascript.

In your CSS, you will have this

.parent { transition: max-height 250ms; } 

1 Comment

This is the only real solution. React: maxHeight: isOpen ? `min(15rem, ${contentRef.current?.clientHeight}px)` : 0
0

Use display:flex. This will work:

.parent > div { display: flex; flex-direction: column; height: 0px; max-height: 0px; opacity: 0; overflow: hidden; transition: all 0.3s; } .parent > div.active { opacity: 1; height: 100%; max-height: none; /* important for animation */ } 

3 Comments

did not work for me
Me neither. It doesn't seem to transition at all, just snap.
it works only for paddings of the active element.
-3

You can accomplish this just fine using jQuery Transit:

$(function () { $(".paragraph").click(function () { var expanded = $(this).is(".expanded"); if (expanded) { $(this).transition({ 'max-height': '4em', overflow: 'hidden' }, 500, 'in', function () {$(this).removeClass("expanded"); }); } else { $(this).transition({ 'max-height': $(this).get(0).scrollHeight, overflow: ''}, 500, 'out', function () { $(this).addClass("expanded"); }); } }); }); 

You can definitely tidy it up a bit to your liking, but that should do what you want.

JS Fiddle Demo

1 Comment

thanks, I know of ways to do it with javascript, I wanted a way to do it by only changing the classname

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.