Animating from display: none with CSS and callbacks

Animating from display: none with CSS and callbacks

Sometimes we want to make some animations for an element entrance, like a fade-in effect. It’s common to use jQuery for such cases to make simple animations, for example:

$('.foo').fadeIn(300); /* or */ $('.foo').fadeOut(300);

And the reason for that is because it’s simple to use that way, jQuery even provides us a callback to be called at the end of an animation.

$('.foo').fadeIn(300, doSomethingAfterIt);

This is very useful, but it brings us a problem, the animation in which jQuery makes use is purely Javascript.

As a good Front-end developer you should know that transitions/animations must be a CSS responsability, and by doing so, your transitions will have a better performance.

A thread on Github talks more about not using methods like these:

.hide()  .show()  .toggle()  .fadeIn()  .slideUp()  //...

CSS Transitions

Well, for this kind of transition, we can make a better aproach using CSS, and we just need to add a class on the element.

.foo {
  opacity: 0;
  transition: opacity 300ms;
}

.fade-in {
  opacity: 1;
}
$('.foo').addClass('fade-in');

However, to use CSS to make transitions brings us two problems.

Problem 1:

How can I use a callback after a transition?

You need to use a trick here, there’s an event called transitionend, in order to have a cross-browser compatibility you can do something like this:

var transitionEnd = 'webkitTransitionEnd otransitionend
                oTransitionEnd msTransitionEnd transitionend';

$('.foo')
  .addClass('fade-in')
  .one(transitionEnd, doSomethingAfterIt);

And now you’re able to callback every animation.

Problem 2:

How to animate from a display:none element to something?

You aren’t ever animating just from one state to another, sometimes you need to animate from a hidden element.

By default you can’t animate from a display none element like the example below:

// from                           // to
.foo {                            .foo.fade-in {
  display: none;                    display: block;
  opacity: 0;                       opacity: 1;
  transition: opacity 300ms;      }
}

Yep, this won’t work and the explanation for this is that the element isn’t painted and occupying size on the screen, and it needs to be done before the animation.

The Reflow technique

First, we need to know what is reflow and repaint. Here is a “crash course”:

  • A Repaint occurs when changes are made to an element’s skin that changes visibility, but do not affect its layout. Examples of this include outline, opacity or background color.

  • A Reflow occurs when changes are made to an elements size, that causes a reflow and the page layout need to be calculate again to update with that change.

There’s a technique you can use to animate from display: none, what you need to do is add a class that makes the element display: block first, then add a class that will animate the element, however before adding the animation class you need to force a reflow on that element.

To force it you can just execute a method to return element’s size, because the browser needs to update the element to get the real size and then it’ll force a reflow.

This will make the element update its size, in that case, from nothing to something (from none to block), as the element is taking up space now you can animate it adding the transition class.

See the code and a working example below:

var $foo = $('.foo');

$foo
  .addClass('block')
  .outerWidth(); // Reflow

$foo
  .addClass('fade-in')
  .one(transitionEnd, function() {
    alert('Animated');
  });
.block {
  display: block;
}

.fade-in {
  opacity: 1;
}

It’s not an unusual technique, it’s well safe, Bootstrap uses it too on their Modal Component.

Libraries to help

The easiest way to solve these problems and work with a cleaner code without add by your own is to use a library for css animations.

They’ll provide a good API and the difference here from jQuery’s .fadeIn for example, is that jQuery uses Javascript during the animation and these libraries will only apply css transitions through the Javascript API.