DEV Community

loading...

Fading in... and fading out with CSS transitions

Nic
I like problem solving and making things pretty. I also like red.
・3 min read

I've done a few sites that have what looks like a pop-up - in reality it's a div with visibility: hidden and when you click somewhere it becomes visible. I've also used opacity so you can fade it in and out. But until this week the fade out never worked.

Here's my code:

.popup {
  visibility: hidden;
  opacity: 0;
  transition: opacity 250ms ease-in;
}

.popup.show {
  visibility: visible;
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

There's also some JavaScript so when you click on a button it adds the show class to popup.

Transitions are confusing anyway because you don't put them where you initially think you do. It would make sense to add the transition to .show because it's transitioning when that class is added and when it is removed. But if you do that the transition only runs when it's added. When the class is removed it can't run the transition because it doesn't exist.

So that all works, right? No. It fades in nicely, but disappears in a flash. The reason is because when the show class is added it immediately makes it visible, but takes 250ms to change the opacity from 0 to 1. Which is what we want to happen.

But because there's no transition on visibility, when you remove the class it immediately becomes hidden, and then takes 250ms to change the opacity from 1 to 0. You just can't see it because it's hidden.

For fading out we can use the transition delay to tell it to work on opacity first, then visibility:

.popup {
  visibility: hidden;
  opacity: 0;
  transition: opacity 250ms ease-in, visibility 0ms ease-in 250ms;
}

.popup.show {
  visibility: visible;
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

So now when we remove the show class it transitions the opacity from 1 to 0 in 250ms and then immediately sets visibility to hidden. And on fade in it transitions the opacity from 0 to 1 in 25ms and then immediately sets visibility to visible. Which means it now fades out but doesn't fade in...

The solution is to take advantage of the fact that transitions on the show class happen on fade in and transitions on the popup class happen on fade in and fade out. We can add a line to the show class:

.popup {
  visibility: hidden;
  opacity: 0;
  transition: opacity 250ms ease-in, visibility 0ms ease-in 250ms;
}

.popup.show {
  visibility: visible;
  opacity: 1;
  transition-delay: 0ms;
}
Enter fullscreen mode Exit fullscreen mode

Because the .popup.show has greater specificity it overrides whatever's in .popup. Which is how we get it to become visible and opacity 1. It therefore also overrides that transition delay on visibility. So effectively when the show class is added, the transition becomes:

transition: opacity 250ms ease-in, visibility 0ms ease-in 0ms;
Enter fullscreen mode Exit fullscreen mode

So when the show class is added, it becomes visible immediately and then transitions opacity from 0 to 1 in 25ms.

And finally we have something that fades in and fades out!

Of course you could also overwrite the whole transition with:

.popup {
  visibility: hidden;
  opacity: 0;
  transition: opacity 250ms ease-in, visibility 0ms ease-in 250ms;
}

.popup.show {
  visibility: visible;
  opacity: 1;
  transition: opacity 250ms ease-in, visibility 0ms ease-in 0ms;
}
Enter fullscreen mode Exit fullscreen mode

But generally you don't want to write the same thing in multiple places: if you decided the transition was too slow you have to change the 250ms in three places. But in the previous code you only had to change it in two places.

Discussion (0)