Animating from “display: block” to “display: none”

Animating display: block to display: noneAs you might already know, CSS3 transitions and animations allow you to animate a specific set of CSS properties. One of the properties that cannot be animated is the display property.

It would be great if we could do it, but it’s not currently possible and I’m guessing it never will be (e.g. how would you animate to “display: table”?). But there are ways to work around it, and I’ll present one way here.

Why the Need to Animate “display”?

The need to animate the display property comes from wanting to solve the following problem:

  • You want to make an element gradually disappear visually from the page.
  • You don’t want that element to take up space after it has disappeared (i.e., you want the disappearance to cause reflow).
  • You want to use CSS for the animation, not a library.

For this reason, animating opacity to zero is simply not enough because an element with zero opacity still occupies the same space on the page.

Let’s look at how we might attempt to solve this problem, step by step.

Use Opacity and Display

The first thing we might think of doing is using both the opacity property and the display property. Our HTML might look like this:

<div id="box" class="box">
</div>

<button>TOGGLE VISIBILITY</button>

And our CSS might look like this:

.box {
  background: goldenrod;
  width: 300px;
  height: 300px;
  margin: 30px auto;
  transition: all 2s linear;
  display: block;
}

.hidden {
  display: none;
  opacity: 0;
}

Notice we have display: none and opacity: 0 on our “hidden” class. If we toggle this “hidden” class using jQuery, we might have code that looks like this:

var box = $('#box');

$('button').on('click', function () {

  box.toggleClass('hidden');

});

But if we do that, we will not see the transition (defined in our .box declaration block) take effect. Instead we see this (which is slightly different in Firefox vs. Chrome/IE10):

JS Bin

Click the ‘toggle visibility’ button repeatedly and you’ll see the box disappear and appear suddenly, with no transition.

To fix this, we might try to separate our display property from opacity in our CSS:

.hidden {
  display: none;
}

.visuallyhidden {
  opacity: 0;
}

Then we could toggle both classes, one after the other:

var box = $('#box');

$('button').on('click', function () {
  box.toggleClass('visuallyhidden');
  box.toggleClass('hidden');
});

Besides the fact that we want those two lines reversed when the box is appearing, this won’t work, as shown here:

JS Bin

What we want is for the element to disappear visually, then be removed from the page after it finishes disappearing visually, in a manner similar to a callback function.

(As a side point here, even if we combined opacity: 0 with visibility: hidden, it would animate just fine but the element would still occupy space on the page after it disappears, so that won’t work either.)

Why Doesn’t This Work?

Before getting to my solution, I’ll just explain why you can’t do this by just changing the classes one after the other. First, if you’re adding classes like in the examples above, even if the transition worked, you’d have to set up a separate section for removing the classes and reverse how that’s done (i.e. if the box starts out hidden you have to first set it to display: block, then change the opacity).

But that’s beside the point, because it doesn’t work anyhow. JavaScript executes one line after the other, but it doesn’t wait for all things associated with one line to be finished before executing the next line. (In other words, if you execute an animation or other asynchronous event on line 1, line 2 will proceed even if the animation isn’t done; this clarifies the previous wording I used, as pointed out by Chris).

What’s happening is that the ‘opacity’ is attempting to animate immediately, and even if for a fraction of a millisecond it does start to animate, you won’t see it because the display: none part will take effect just as quickly.

We can sum up the problem we want to solve as follows:

  • When the element is visible, first animate the opacity, then, when that’s finished, make it display: none.
  • When the element is inivisible, first make it display: block, then (while it’s still visually hidden, but existing on the page), animate the opacity.

A Possible Solution

There are probably other ways to do this, and I’d be glad to hear how you’d solve this problem. But here is my solution.

The CSS is the same (with the two ‘hidden’ classes still separated), but the jQuery will look like this:

var box = $('#box');

$('button').on('click', function () {
  
  if (box.hasClass('hidden')) {
    
    box.removeClass('hidden');
    setTimeout(function () {
      box.removeClass('visuallyhidden');
    }, 20);

  } else {
    
    box.addClass('visuallyhidden');
    
    box.one('transitionend', function(e) {

      box.addClass('hidden');

    });
    
  }

});

And here it is in action:

JS Bin

Here’s a summary of what the code does when the box is visible:

  • Add the visuallyhidden class, which will animate it until it disappears.
  • At the same time that class is added, a single event handler is added using jQuery’s .one() method, waiting for the transitionend event.
  • The transitionend event fires when the opacity is done animating, and when this occurs the element is set to display: block.

Because we can’t detect a transitionend event on the display property, we have to use a different method for when the box is invisible:

  • First, we remove the hidden class, making it display: block while it’s still visually hidden.
  • While this is occuring, the script has executed a delay using setTimeout(), after which the opacity begins to animate.

Take note that some browsers require a prefix for transitionend. Modernizr.prefixed() will help with this.

The delay is very small (20 milliseconds), but because display: block occurs instantly, we don’t need much of a delay at all, just enough to allow the element to take its full place on the page before animating the opacity.

(Another sidepoint: Inside JS Bin in Chrome, I was able to use a delay of just a hundredth of a millisecond (0.1). But Firefox and IE10 both required at least 20ms. Maybe that’s something to do with JS Bin, I really don’t know.)

How Would You Do It?

As mentioned, there are probably other ways to do this, or maybe there’s some improvement that could be made to my code.

Personally, I feel the use of setTimeout is a bit lame and hacky. But hey, it works, and I don’t think it’s going to cause any problems unless you had tons of similar animations on the page. But that would be another issue altogether.

There are a few possible solutions to this on StackOverflow, but I find most, if not all, of the proposed solutions don’t really solve the problem at hand.

Feedback is welcome.

Update (Nov. 21, 2013): See this post by Christian Heilmann, as it addresses the same basic idea. His solution of CSS animations is nice and clean. He also offers one using transitions that someone else suggested.

Advertise Here

43 Responses

  1. I usually avoid display:none and use instead transform:translate(9999px);

    .box {
    transition: opacity 2s linear;
    transform:translate(0);
    opacity: 1;
    }
    .hidden {
    transform:translate(9999px);
    opacity: 0;
    }

    • Yes, but that doesn’t solve the problem of removing the space occupied by the original element. Also, in your example, I don’t think you’d see the animation, because the translate transform would occur instantly, removing it from view.

  2. Kalley Powell:

    What if you did something like the following: http://jsbin.com/IBumuMo/1/

    Using step-start and step-end opens up a few other possibilities.

  3. Jon Christensen:

    How about a delay when the hidden class is added and no delay when removed?

    
    .box {
    opacity: 1;
    transition: opacity 1s, height 0;
    }
    
    .hidden {
    opacity: 0;
    transition: opacity 1s, height 0 1s;
    height: 0;
    }
    
  4. JavaScript executes one line after the other, but it doesn’t wait for one line to be finished before executing the next line.

    This is not true at all. JavaScript statements are not asynchronous, although they can invoke asynchronous behavior in other libraries. You may be referring to jQuery transitions here (which are asynchronous) but as written this statement is false. If JavaScript did not guarantee complete execution of one statement before executing the next, the language would be effectively useless.

    • Ah, you’re right. What I’m trying to say is that if the previous line contains an asynchronous event, then it will not wait for that one to finish (in this case, I’m referring to the result of the class being added/removed, which causes asynchronous events to occur). I’ll correct the wording.

  5. How a bout keyframes?

    Where the 0% would set the display property and the 1% – 99% do the opacity and 100% does the display again. ;-)

  6. Andrei Pham:

    Umm, I believe that what you did isn’t enough. Just like you said, when setting display: none on an element, it doesn’t just fade away. It also disappears and the page acts like the element weren’t there. So theoretically, you should animate the “disappearing” thing too, not just the opacity. And this means that after fading it away and before setting display: none to it, you should set the height to 0px smoothly (or width if content flow is horizontal).

    • I’ve updated my codepen to reflect that, I think that works better indeed.
      Width and height are optional of course, depending on what you want animated.

    • I agree, from a visual standpoint, it’s probably jarring to see the content around flow into a new area so suddenly. So yes, you could animate height or width. That’s just one way to do it. And I think you’d also need to do “overflow: hidden” if you use that, depending on what’s inside the element.

      But if you use height, then you have to have a specified height for the element, which is unlikely. Usually content dictates height. And you can’t animate to “height: auto”, so that’s not always an option. Of course there are workarounds for that too, but again, they always have some drawbacks.

      • To solve this in the past I’ve just animated min-height: property. If you have a rough idea of the general height it would have you can get away with using that to hide it.

  7. If in the end you use so much JS, why would you not go for something like $(‘.box’).fadeToggle() ?

    • Because I want to use CSS animations, which generally the browser can optimize better. But probably in a case like this, using it one or two times wouldn’t matter. The idea is to try to get away from jQuery animations, leaving the animating part to CSS where I believe it is more appropriate.

  8. Hi,

    Nice article Louis.

    It’s a shame that you can’t animate from height:auto to height:0 as usually that is what you want. However, if you need a fluid height then don’t use height but use max-height instead although you can’t actually animate the height you can create a decent fade effect and remove the element from the flow.

    Here’s a codepen:
    http://codepen.io/paulobrien/full/tpmAi

    It’s not perfect but can be useful in certain situations and needs no JS in modern browsers (using the :checked pseudo class instead).

  9. IMO playing with delays is the way to go. So you can combine opacity and top properties, which means our “fadeable” elements are always printed but they are offscreen.

    If you absolutely need to support ie8 then you’d use opacity + top properties:

    
    .elem {
      top:-2000px;
      opacity:0;
      transition: opacity .3s ease 0s, top 0s linear .9s;
    }
    
    .visible {
      top:0px;
      opacity:1;
      transition: top 0s linear 0s, opacity .3s ease .01s;
    }

    And if you don’t care about oldies, then you can go with opacity + translateY properties. This way is surely the best possible because your element gets uploaded to the GPU (translate property) and so you’ll get the lowest possible repaint cost. It could look like this:

    
    .elem {
      transform: translateY(-2000px) translateZ(0);
      opacity:0;
      transition: opacity .3s ease 0s, transform 0s linear .9s;
    }
    
    .visible { 
      transform: translateY(0px);
      opacity:1;
      transition: top 0s linear 0s, opacity .3s ease .01s;
    }

    Of course I omitted all the vendor prefixes in these little snippets.
    Grunt (or Brunch) Autoprefixer FTW

    • In both those examples, you’re not really solving the problem I outlined at the beginning. In the case of both “top” and “translate”, the element is still going to take up the same amount of space on the page, so it will not cause reflow.

      Your solution is fine if you want to just fade an element out, and don’t care about reflow. But if you want the original occupied space to be gone, then you have to use another method. For details on what I’m talking about, see this post:

      http://www.impressivewebs.com/css-things-that-dont-occupy-space/

  10. Cristian:

    Playing with paddings and height works pretty fine to emulate the desired display effect: http://jsfiddle.net/catharsis/n5XfG/17/

    • Interesting.:) Adding the large top and bottom padding is a neat trick to make the height animate.

      • Cristian:

        Well you need to add 0 padding and then only the needed padding to the container and it will be animated, because height: auto is not possible to animate right now.

  11. Chris:

    How about scaleX(0.00001) as hidden state, then scaleX(1) as visible state, then couple that with opacity:0, opacity:.999 and transition both. scaleX(0.00001) gets the element out of view, like a better visibility: hidden, but uses hw acceleration.

    • Again, that would work fine in many circumstances, but not in the scenario I’ve presented in this post (which is, admittedly, not that common). As I mentioned to others, and in the article, stuff like transforms and opacity make things invisible but they don’t cause page reflow. In other words, if an element is 200×200 in size, if you shrink it down using scaleX/Y, it will still take up 200×200 pixels in space on the page.

      The only way to do what you’re saying is to add “position: absolute” to the element, which takes it out of the flow. So I wonder if that would work better like that…. might be worth experimenting with.

      • Chris Wigley:

        Ah yes, I was assuming the element would be positioned absolute. I use the scale(0.0001) technique and it works well — for elements that are absolutely positioned.

  12. If you need a reflow, then you should read a property which triggers it (e.g. “offsetWidth”). Here’s an eample:

    box.removeClass('hidden');
    box.prop("offsetWidth");
    box.removeClass('visuallyhidden');
  13. We could also do this animation without any JQuery or javascript by only css properies.
    Some browser in client computers are javascript disabled for security reasons. So I think
    that would be beneficial if you could also mentioned making animation with css only too.

  14. I worked around the display none/block limitation by using height 0 like the earlier comments above. Needed to play an animation then remove it afterwards:

    
    .item-to-animate {
        height: 0;
        animation: some-animation
    }
    @keyframes some-animation {
    	0%, 100% {
    		height: auto;
    	}
    	0% {
                    /* start animating stuff */
            }
    	99.99999% {
                    /* end animating stuff */
    	}
    }
    
  15. We often use JQuery because it is possible to make pages more user friendly and fun. Thanks for the useful tutorial.

    Serkan

  16. sachin Tendulkar:

    Thanks for the useful tutorial.

  17. Pabzt:

    this is what i use:

    I style my element in a way i want to display it and add display:none:

    
    #menu {
     display: none;
     /* other styles... */
    }
    

    Then I create a keyframe animation

    
    @keyframes animation{
    
    	0% { opacity: 0; }
    
    	100% {opacity: 1;}
    }

    Add a class for the animation

    
    .someClass {
    animation-name: animation;
    	animation-duration: 0.1s;
    	animation-iteration-count: 1;
    	animation-timing-function: ease-in;
    }

    I created a jquery function to handle display none which gets the timeout from the animation class and also has a callback function for after animation stuff…

    
    $.fn.css3In = function(a, args)
    {
    	return this.each(function()
    	{
    		$(this).css('display', 'block').addClass(a);
    		var duration = $('.' + a).css('animation-duration');
    		duration = duration.substring(0, duration.length-1) * 1000;
    
    		var object = $(this);
    		
    		setTimeout(function() {
    			
    			$(object).removeClass();
    			args.AnimationEnd();
    
    		}, duration);
    
    	});
    }
    

    Then I’m able to use any css3 Animation with keyframes on any object that has display none

    
    $('#menu').css3In('someClass', { AnimationEnd:function() {
            //Do Something after animation
    
    	}
    });
    

    from display block to display none i use another jQuery function that does basicly the same:

    
    $.fn.css3Out = function(a, args)
    {
    	return this.each(function()
    	{
    		console.log("out animation running...");
    		$(this).addClass(a);
    		var duration = $('.' + a).css('animation-duration');
    		duration = duration.substring(0, duration.length-1) * 1000;
    
    		var object = $(this);
    
    		setTimeout(function() {
    			
    			$(object).removeClass();
    			$(object).css('display', 'none');
    			args.AnimationEnd();
    
    		}, duration);
    
    	});
    }
    
  18. Alvin:

    Very nice tutorial!
    is it possible to animate the button to slide up after the box’s animation?

  19. Lurker:

    You may use ‘pointer-events:none’ with opacity to achieve the effects but with some fallback in >IE10.

  20. Eva:

    we always look for nice transition techniques like this one. Thank you for the idea.

  21. Sérgio Santana:

    A beter way i thing

    div.yourclass{


    height: 0;
    transition: opacity 0.3s

    }

    div.yourclass:hover{

    height: 200px;

    }

    that code make just de opacity has transition :)

  22. Thanks for the useful article, it works perfectly.

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field. If you use keywords, your comment will be deleted, or your name will be replaced with the alias from your email address. No foul language, please. Thank you for cooperating.

Instructions for code snippets: Wrap inline code in <code> tags; wrap blocks of code in <pre> and <code> tags. When you want your HTML to display on the page in a code snippet inside of <code> tags, make sure you use &lt; and &gt; instead of < and >, otherwise your code will be eaten by pink unicorns.