Paul
Paul

Reputation: 1167

Why doesn't a css transition fire immediately after display change

TL;DR A css transition on opacity does not work immediately after display change, but works with setTimeout(.., 100). Why?

What do I want?

I want to flash a message for a couple of seconds and then fade it out. Seems pretty basic, right?

What do I have?

Well, here's a jsfiddle, but let me explain in detail.

Say I have a message block

<div id="message" class="message">
  Here be dragons
</div>

Which starts hidden but opaque

.message {
  opacity: 1;      
  display: none;
}

Once I've prepared my message I want to show it.

document.getElementById("message").style.display = "block"

Now I want the message to fade out so I added a simple transition on opacity.

.flash {
  opacity: 0;
  transition: opacity 2s ease-out 1s;
}

Which I apply with the following

document.getElementById("message").classList.add("flash")

What goes wrong?

The message div is shown but it stays invisible as the opacity: 0 immediately applies. Besides, the transitionend event is not firing, which makes me think the transition does not happen at all for some reason. Weird, right?

However, everything's fine once I add the timeout

  document.getElementById("message").style.display = "block"
  setTimeout(() => (document.getElementById("message").classList.add("flash")), 100)

That works but seems like a totally dirty hack. Why is it like this?

You can see this behaviour on jsfiddle with two buttons aptly named 'Working' and 'Not working';

Upvotes: 3

Views: 534

Answers (2)

trincot
trincot

Reputation: 350310

There are two things that together cause this:

  • When items have display: none the opacity is ignored (as not relevant). And so when you apply display: block to them they render the provided opacity with the current value without any transition effect.

  • Changes you apply to the style attribute all apply together at the moment a paint (asynchronous) happens, and so the transition definition comes too late.

First, make sure to set the transition effect definition before the actual application of it, in the message CSS class.

I would then suggest using height instead of display to get the same effect. You would need to switch the border on and off also (through its width):

document.getElementById("working").addEventListener("click" , () => {
  document.getElementById("message").classList.add("flash");
})

// reset
document.getElementById("message").addEventListener("transitionend" , () => {
  document.getElementById("message").classList.remove("flash")
})
.message {
  border: solid 0px;
  background: grey;
  opacity: 1;
  transition: opacity 2s ease-out 1s;
  overflow: hidden;
  height: 0;
}

.flash {
  height: 100%; 
  opacity: 0;
  border-width: 1px
}
<div id="message" class="message">
  Here be dragons
</div>

<button id="working"> Working </button>

Upvotes: 2

Inioluwa Sogelola
Inioluwa Sogelola

Reputation: 51

Why it doesn't work is because you are applying display:block and opactiy: 0 at the same time. When you set the attribute display in css it ignores all transition, there has to be an event in between setting display and transitions. An alternative is using visibility:hidden and visibility:visible instead of display but note that this only hides the element and the element is still present in its position

Upvotes: 1

Related Questions