3

Using JavaScript to change the opacity of an element with transition: opacity 1s, you expect the transition to take one second, which it does here:

onload = e => {
  var p = document.getElementsByTagName("p")[0];

  p.style.opacity = 1;
};
p {
  opacity: 0;
  transition: opacity 1s;
}
<p>
Lorem ipsum...
</p>

However, if the element has display: none and you first change it to display: initial (in order to see it) before changing the opacity the transition no longer works, which you can see here:

onload = e => {
  var p = document.getElementsByTagName("p")[0];

  p.style.display = "initial";
  p.style.opacity = 1;
};
p {
  display: none;
  opacity: 0;
  transition: opacity 1s;
}
<p>
Lorem ipsum...
</p>

Why is this?

Note: I am not using a transition on the display attribute and neither am I looking for a workaround for that.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
RaminS
  • 2,208
  • 4
  • 22
  • 30
  • What is the use case for `display: none;`? When `opacity` is set to zero the element is already not visible. – Randy Casburn Jan 20 '19 at 21:09
  • The use is to remove it from the document flow, which is not evident in this [mcve]. Regardless of what the use is, I wonder why this happens. – RaminS Jan 20 '19 at 21:10
  • It happens because that is the way the spec is written. display is a toggle and as @AndrewL64 states, you cannot animate a toggle. – Randy Casburn Jan 20 '19 at 21:11
  • 1
    I am not transitioning the display attribute, for the second time. – RaminS Jan 20 '19 at 21:12
  • An since you 'toggle' display on, you aren't animating the `opacity` either. That simple. – Randy Casburn Jan 20 '19 at 21:13
  • By the way, to include more than one snippet, just duplicate the `` tags in the raw answer and place your snippet there. – Randy Casburn Jan 20 '19 at 21:14
  • The code snippets aren't really working; you can't see the transition. I'm not looking for a workaround. – RaminS Jan 20 '19 at 21:16
  • @Gendarme Well, there is no non-workaround for this issue since you cant transition the `display` property. And it's fine if the Code Snippets don't run the transition. Atleast the codes aren't in external links and will be available for future readers who have the same issue. Cheers. – AndrewL64 Jan 20 '19 at 21:18
  • 6
    I. Am. Not. Transitioning. The. Display. Property. – RaminS Jan 20 '19 at 21:26
  • But at the time the transition starts, the computed value of the display property is `none`. This means all the other properties are discarded because that's how powerful `display:none` is. So you need to tell the browser you did change the display property to something that won't make all other rules discarded, and the easiest is to [trigger a reflow](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) after you changed the css rule so that all the computed values get updated. – Kaiido Jan 22 '19 at 02:16
  • By giving in to whoever nagged you to use snippets, you've rendered your runnable examples nonsensical. Then @MartijnPieters's edit removes your acknowledgement that the examples are nonsensical, leaving the question pretty confusing. As is often the case, this would've been better with non-snippet code included in the question and jsfiddle links for runnable demos. A reluctant -1 because thanks to the combined efforts of all contributors this whole question seems kind of nonsensical now unless you recognise that Stack Snippets are broken or look into the revision history. – Mark Amery Jan 22 '19 at 11:43
  • @markamery The JSFiddles are still there, and anyone could copy-paste the snippets into JSFiddle themselvles. The snippets seem broken yeah, perhaps I should add a P.S. to address that. – RaminS Jan 22 '19 at 11:45
  • 1
    @Gendarme Oh yeah, so they are. Maybe just un-Snippet-ify the code in the question, then? No sense making it runnable if it doesn't actually work. – Mark Amery Jan 22 '19 at 11:46
  • It is only the first snippet in the question that doesn't work. The second one works and the two in my answer also do. – RaminS Jan 22 '19 at 11:56
  • 1
    @MarkAmery: My apologies if I made things worse here, but the 'edit' headers were confusing too. – Martijn Pieters Jan 22 '19 at 13:01

2 Answers2

1

tldr: You cannot use the transition property with the display property. And yes, you can't use the transition property on any other css property of an element whose's current display property is none either.


The cleanest workaround for this would be to simultaneously transition the width property and the opacity property together.

Check this JSFiddle to see how you can use the width and height property to replicate the display:none property of not letting the element take any space in the document flow.

The <span> element is just for demonstrating how the <p> tag does not take any space while it's hidden.

You can also check out the code in the following Snippet but as you mentioned, the transition doesn't work here for some weird reason.

var p = document.getElementsByTagName("p")[0];

p.style.width = "50%";
p.style.height = "auto";
p.style.opacity = 1;
html, body{width: 100%; height: 100%; margin: 0; padding: 0;}
p {
  width: 0;
  height:0;
  opacity: 0;
  transition: width 2s, opacity 3.5s;
  float:left;
  margin: 0;
}
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis culpa nobis dolorem voluptates ut odio numquam officia provident quos labore, natus sint doloribus ducimus similique aspernatur, enim, voluptatibus vel facere!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima, adipisci? Quidem laboriosam sunt, qui non ea placeat laborum deserunt consequatur consequuntur vel, officiis magnam. Vitae officiis, quidem doloribus nesciunt voluptatem!
</p>
<span>Right side text</span>

But if you have to use display:none, then as shown in this css article, you can just use the setTimeout() workaround to set the transition property after the display property is toggled instead of during the display property toggling like this:

var p = document.getElementsByTagName("p")[0];
p.style.display = "block";

setTimeout(function(){p.style.opacity = 1;},1000);
p {
  display: none;
  opacity: 0;
  transition: opacity 1s;
}
<p>
Lorem ipsum...
</p>
AndrewL64
  • 15,794
  • 8
  • 47
  • 79
  • I wasn't really intending to use the `transition` property with the `display` property. I simply had the elements removed from the document flow, and put them back in before I did the `transition` on `opacity`. See [my answer](https://stackoverflow.com/a/54281337/5221346). I think I discovered what's going on. – RaminS Jan 20 '19 at 21:56
  • @Gendarme Using a `setInterval()` is a good idea too. You can use either approach for this. – AndrewL64 Jan 20 '19 at 21:58
  • That is indeed a nice trick. I never thought of that. `setInterval` could be a problem if those 100 milliseconds are too long, so this seems like a better workaround (if that is what one is looking for). – RaminS Jan 20 '19 at 22:00
  • @Gendarme Agreed. I was just about to edit the answer with that alternative approach found in [this article](https://tech.labs.oliverwyman.com/blog/2014/05/27/css-transitions-cant-animate-display-change/) while searching for a way to do this with `display` just now myself. – AndrewL64 Jan 20 '19 at 22:03
  • I want to choose your answer as the accepted one, but you haven't really answered the question. I asked *why* this happens rather than looking for a workaround, and you simply stated *"You cannot use the `transition` property with the `display` property."* which doesn't really mean anything. I tried answering it in my own answer, but in the end it's just a hypothesis. Can you try to answer it? I will have to choose my own answer as the accepted one otherwise (since that would perfectly have answered the question even if written by someone else). – RaminS Jan 20 '19 at 22:23
  • 3
    You can accept your own answer too mate. It's fine. I'll try searching for more info on this and will update my answer once I do. Cheers. – AndrewL64 Jan 20 '19 at 22:27
1

It seems like the problem is that the transition happens (or tries to happen) during the process of the display attribute changing (meaning that it is still none and the transition thus doesn't work). The transition could technically have started while the element had display: none and became visible only when the process of changing it to display: initial had finished, since the transition is one whole second while the change of display attribute is a fraction thereof. But that is simply not the case, it seems.

It is possible to change the opacity of an element that has display: none before changing it to display: initial as you can see here so that is clearly not the problem:

var p = document.getElementsByTagName("p")[0];
p.style.opacity = 0.3;
p.style.display = "initial";
p {
  display: none;
  opacity: 0;
  transition: opacity 1s;
}
<p>
Lorem ipsum...
</p>

It is also possible to change it afterwards (of course), so that seems to only leave our hypothesis as the only possibilty.

To make sure that the first process has finished before you start the transition, you can use a small delay like this and voilà, it works:

var p = document.getElementsByTagName("p")[0];

p.style.display = "initial";
setTimeout(function() {
 p.style.opacity = 1;
},100);
p {
  display: none;
  opacity: 0;
  transition: opacity 1s;
}
<p>
Lorem ipsum...
</p>
RaminS
  • 2,208
  • 4
  • 22
  • 30
  • 1
    Wow don't use `setInterval` here, you want this to fire once and only once. Also, better to trigger a synchronous reflow, e.g simply getting `p.offsetLeft` will do, though it is even better to do it only once per frame when we are sure everything is readied, so the best would be to hook this in a rAF callback that will get called just before the next paint `p.style.display = 'initial'; requestAnimationFrame( _ => { p.offsetLeft; p.style.opacity = '1'; })` – Kaiido Jan 22 '19 at 02:28