Skip to main content

Fading elements in CSS3 without blocking click events

Front-end Development

I love jQuery (and JavaScript in general), but it's been an ongoing project for me to avoid using it as a crutch. Newer web technologies can accomplish many of the tasks for which I used to rely wholly on jQuery. I'm challenging myself to learn new ways of doing things I already know how to do, so that I can evaluate which is the better approach.

One rich avenue for investigation is animations. It's really easy to create complicated animations in jQuery. There are some drawbacks to this, though: jQuery animations can be processor-intensive, they can be choppy especially on iOS devices, and they sometimes mingle presentation with behavior a little too much for my taste.

CSS3 animations address these issues, but they are by no means a drop-in replacement. Some animations will still require jQuery, but for a lot of simple ones we can get away with simple CSS3 transitions or keyframe animations. Today I decided to try implementing a fade effect for an overlaid item (like a lightbox) using CSS3 only.

My First Attempt

Fading an item in and out is generally very simple in CSS3. In this case, I was applying a .visible class to the element when I wanted it shown, so the CSS looked like this:

#popup-container {
  z-index: 1000;
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  -webkit-transition: opacity 1s;
}
#popup-container.visible {
  opacity: 1;
}

Only the Webkit prefix is shown, to keep things brief. This code animates the opacity property between 0 and 1 over a duration of one second each time .visible is added to or removed from the element.

The Problem

Animating opacity looks great, but it isn't a substitute for display: none. Since the item with 0 opacity is still in the same location, it interrupts any clicks that should pass through to the content below. If we add display: none to the item and display: block to the .visible state, then the animation breaks. No good!

I found suggestions online to move the item out of the way when it is hidden. That's fine, but I didn't want to add another animation to the mix.

Z-Index to the Rescue

"Aha!" I thought. "I'll just change the element's z-index." This is easy to do.

#popup-container {
  z-index: -1000;
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  -webkit-transition: opacity 1s;
}
#popup-container.visible {
  z-index: 1000;
  opacity: 1;
}

Now when the item is hidden, it is also at the back of the element stack, so clicks don't reach it. Great. But since z-index isn't animated, it is changed right when the class is added or removed. This is just fine when the class is added, but when the class is removed the element is shoved to the back of the stack too early, and the transition does not occur.

My next thought was to animate z-index too. However, this just moves the problem to the other side of the fence. Now the element isn't visible at the beginning of the transition. What to do?

The Trick

My solution is to animate z-index, but to use a different transition when hiding the element than when showing it. The trick here is that CSS applies the new transition property before it starts the animation. By setting a transition property in both style rules, we can have a different effect each direction.

#popup-container {
  z-index: -1000;
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  -webkit-transition: opacity 1s, z-index 0 1s;
}
#popup-container.visible {
  z-index: 1000;
  opacity: 1;
  -webkit-transition: opacity 1s;
}

Now when the element is made visible, it animates opacity over the course of a second, as before. The z-index changes immediately.

When the .visible class is removed, on the other hand, opacity animates for a second, while z-index animates over 0 seconds after a delay of 1 second. This way the element is visible until it is completely transparent, and then it gets out of the way.