Transitions on the CSS display property
I’m currently designing a CSS ‘mega dropdown’ menu — basically a regular CSS-only dropdown menu, but one that contains different types of content. At the moment, it appears that CSS 3 transitions don’t apply to the ‘display’ property, i.e., you can’t do any sort of transition from display: none to display: block (or any combination). Is there a way for the second-tier menu from the above example to ‘fade in’ when someone hovers over one of the top level menu items? I’m aware that you can use transitions on the visibility: property, but I can’t think of a way to use that effectively. I’ve also tried using height, but that just failed miserably. I’m also aware that it’s trivial to achieve this using JavaScript, but I wanted to challenge myself to use just CSS, and I think I’m coming up a little short.
@Jawad: It’s recommended to never use visibility: hidden unless you want screenreaders to read it (whereas typical browsers won’t). It only defines the visibility of the element (like saying opacity: 0 ), and it’s still selectable, clickable, and whatever it used to be; it’s just not visible.
You need to display: none otherwise you’ll be stumbling into the hidden object outside the trigger and it’ll be showing accidentally. I’m just saying 🙂
38 Answers 38
You can concatenate two transitions or more, and visibility is what comes handy this time.
(Don’t forget the vendor prefixes to the transition property.)
Yeah the problem with this is anything behind it will overlap even if it’s not visible. I found using height:0 a much better solution
This is nice but the problem is that «visibility hidden» elements still occupy space while «display none» does not.
I’m probably missing something, but why do you alter both the visibility AND the opacity? Won’t setting the opacity to 0 hide the element — why do you need to set the visibility to hidden too?
@GeorgeMillo if you set only the opacity, the element is actually still on the page rendering (you can’t click thought for example).
This should not be marked as the correct answer. It does not deal with the display property and as Rui said, the element still takes up space making it impractical for many situations.
You need to hide the element by other means in order to get this to work.
I accomplished the effect by positioning both s absolutely and setting the hidden one to opacity: 0 .
If you even toggle the display property from none to block , your transition on other elements will not occur.
To work around this, always allow the element to be display: block , but hide the element by adjusting any of these means:
- Set the height to 0 .
- Set the opacity to 0 .
- Position the element outside of the frame of another element that has overflow: hidden .
There are likely more solutions, but you cannot perform a transition if you toggle the element to display: none . For example, you may attempt to try something like this:
But that will not work. From my experience, I have found this to do nothing.
Because of this, you will always need to keep the element display: block — but you could get around it by doing something like this:
Thanks Jim for a thorough answer. You’re absolutely right about the fact that if the display: property changes at all, then ALL of your transitions will not work. Which is a shame — I wonder what the reasoning behind that is. On a side note, on the same link I posted in the original question, you can see where I’m at with it. The only (small) problem I have is in Chrome [5.0.375.125] when the page loads, you can see the menu quickly fading away as the elements are loaded on the page. Firefox 4.0b2 and Safari 5.0 are absolutely fine. bug or something I’ve missed?
I implemented this, then it didn’t work, then I realised that what your saying is this won’t work. This answer assumes were not all just speed reading the important bits. 🙂
At the time of this post all major browsers disable CSS transitions if you try to change the display property, but CSS animations still work fine so we can use them as a workaround.
Example Code (you can apply it to your menu accordingly) Demo:
Add the following CSS to your stylesheet:
@-webkit-keyframes fadeIn < from < opacity: 0; >to < opacity: 1; >> @keyframes fadeIn < from < opacity: 0; >to < opacity: 1; >>
Then apply the fadeIn animation to the child on parent hover (and of course set display: block ):
Update 2019 — Method that also supports fading out:
(Some JavaScript code is required)
// We need to keep track of faded in elements so we can apply fade out later in CSS document.addEventListener('animationstart', function (e) < if (e.animationName === 'fade-in') < e.target.classList.add('did-fade-in'); >>); document.addEventListener('animationend', function (e) < if (e.animationName === 'fade-out') < e.target.classList.remove('did-fade-in'); >>);
div < border: 5px solid; padding: 10px; >div:hover < border-color: red; >.parent .child < display: none; >.parent:hover .child < display: block; animation: fade-in 1s; >.parent:not(:hover) .child.did-fade-in < display: block; animation: fade-out 1s; >@keyframes fade-in < from < opacity: 0; >to < opacity: 1; >> @keyframes fade-out < from < opacity: 1; >to < opacity: 0; >>
Thanks for this. The height: 0 trick (for transitions) mentioned above doesn’t seem to work because the height gets set to 0 on the fade-out transition, but this trick seems to work just fine.
The first paragraph of this answer doesn’t quite make sense. Browsers don’t just disable all transitions outright the moment you use the display property — there is really no reason to. And even if they did, why would animations work then? You can’t use the display property in CSS animations either.
Yeah, «change» — I’m not sure why I said «use» there. My point is you can’t transition or animate display , but that doesn’t prevent all other properties from animating either so long as you’re not transitioning to none.
This works perfectly for mouse-over dropdown menus where the height isn’t a problem. My dropdown simply fades in on hover of the parent nav item, and fades out when moused-out.
Instead of callbacks, which don’t exist in CSS, we can use transition-delay property.
- When visible class is added, both height and opacity start animation without delay (0 ms), though height takes 0 ms to complete animation (equivalent of display: block ) and opacity takes 600 ms.
- When visible class is removed, opacity starts animation (0 ms delay, 400 ms duration), and height waits 400 ms and only then instantly (0 ms) restores initial value (equivalent of display: none in the animation callback).
Note, this approach is better than ones using visibility . In such cases, the element still occupies the space on the page, and it’s not always suitable.
For more examples please refer to this article.
It only works with height:100% which can destroy the layout in some cases. Great solution, if that’s not a problem. One of the few bidirectional working ones.
I suspect that the reason that transitions are disabled if display is changed is because of what display actually does. It does not change anything that could conceivably be smoothly animated.
display: none; and visibility: hidden; are two entirely different things.
Both do have the effect of making the element invisible, but with visibility: hidden; it’s still rendered in the layout, but just not visibly so.
The hidden element still takes up space, and is still rendered inline or as a block or block-inline or table or whatever the display element tells it to render as, and takes up space accordingly.
Other elements do not automatically move to occupy that space. The hidden element just doesn’t render its actual pixels to the output.
display: none on the other hand actually prevents the element from rendering entirely.
It does not take up any layout space.
Other elements that would’ve occupied some or all of the space taken up by this element now adjust to occupy that space, as if the element simply did not exist at all.
display is not just another visual attribute.
It establishes the entire rendering mode of the element, such as whether it’s a block , inline , inline-block , table , table-row , table-cell , list-item , or whatever!
Each of those have very different layout ramifications, and there would be no reasonable way to animate or smoothly transition them (try to imagine a smooth transition from block to inline or vice-versa, for instance!).
This is why transitions are disabled if display changes (even if the change is to or from none — none isn’t merely invisibility, it’s its own element rendering mode that means no rendering at all!).
How do you transition between display:none and display:block?
I’ve read elsewhere that modern browsers will disregard any animation or transition specifications if display: changes. Is this still the case? How can I overcome this and delay the appearance of the nav items until the overlay fully expands?
It appears to be working here: https://codepen.io/KingKabir/pen/QyPwgG but I’m not sure how they are accomplishing it?
I’ve tried using visibility: and opacity: but they don’t completely hide the 4 nav elements in the non-overlay state, thus messing up my name in the right side of the nav menu in the non-overlay state.