Transitions on the CSS display property

Viewed 2144136

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.

37 Answers

Instead of callbacks, which don't exist in CSS, we can use transition-delay property.

#selector {
    overflow: hidden;  /* Hide the element content, while height = 0 */
    height: 0;
    opacity: 0;
    transition: height 0ms 400ms, opacity 400ms 0ms;
}
#selector.visible {
    height: auto; opacity: 1;
    transition: height 0ms 0ms, opacity 600ms 0ms;
}

So, what's going on here?

  1. 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.

  2. 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.

I found better way for this issue, you can use CSS Animation and make your awesome effect for showing items.

.item {
     display: none;
}

.item:hover {
     display: block;
     animation: fade_in_show 0.5s
}

@keyframes fade_in_show {
     0% {
          opacity: 0;
          transform: scale(0)
     }

     100% {
          opacity: 1;
          transform: scale(1)
     }
}

Fade it in with CSS Animations:

.item {
     display: none;
}

.item:hover {
     display: block;
     animation: fadeIn 0.5s;
}

@keyframes fadeIn {
     from {
          opacity: 0;
     }

     to {
          opacity: 1;
     }
}

It is as simple as the following :)

@keyframes fadeout {
    0% { opacity: 1; height: auto; }
    90% { opacity: 0; height: auto; }
    100% { opacity: 0; height: 0;
}
animation: fadeout linear 0.5s 1 normal forwards !important;

Get it to fade away, and then make it height 0;. Also make sure to use forwards so that it stays in the final state.

I appreciate all the answers. Here is what I'm using for similar purposes: transition vs animation.

Example: https://jsfiddle.net/grinevri/tcod87Le/22/

<div class="animation"></div>
<div class="transition"></div>
@keyframes animationTo {
  0% { background-color: rgba(0, 0, 0, 0.1); }
  100% { background-color: rgba(0, 0, 0, 0.5); }
}

@keyframes animationFrom {
  0% { background-color: rgba(0, 0, 0, 0.5); }
  100% { background-color: rgba(0, 0, 0, 0.1); }
}

.animation,
.transition{
  margin: 5px;
  height: 100px;
  width: 100px;
  background-color: rgba(0, 0, 0, 0.1);
}

.animation{
  animation: animationFrom 250ms;
}

.animation:hover{
  background-color: rgba(0, 0, 0, 0.5);
  animation: animationTo 250ms;
}

.transition{
  transition: background-color 250ms;
}

.transition:hover{
  background-color: rgba(0, 0, 0, 0.5);
}

I finally found a solution for me, by combining opacity with position absolute (not to occupy space when hidden).

.toggle {
  opacity: 0;
  position: absolute;
  transition: opacity 0.8s;
}

.parent:hover .toggle {
  opacity: 1;
  position: static;
}

Well another way to apply transition in this situation without using keyframes is to set the width of your element to zero and then unset it on hover

.className{
  visibility:hidden;
  opacity: 0;
  transition: .2s;
  width:0;
}

.className:hover{
  visibility:visible;
  margin-right: .5rem;
  opacity: 1;
  width:unset;
}

I had a similar issue that I couldn't find the answer to. A few Google searches later led me here. Considering I didn't find the simple answer I was hoping for, I stumbled upon a solution that is both elegant and effective.

It turns out the visibility CSS property has a value collapse which is generally used for table items. However, if used on any other elements it effectively renders them as hidden, pretty much the same as display: hidden but with the added ability that the element doesn't take up any space and you can still animate the element in question.

Below is a simple example of this in action.

function toggleVisibility() {
  let exampleElement = document.querySelector('span');
  if (exampleElement.classList.contains('visible')) {
    return;
  }
  exampleElement.innerHTML = 'I will not take up space!';
  exampleElement.classList.toggle('hidden');
  exampleElement.classList.toggle('visible');
  setTimeout(() => {
    exampleElement.classList.toggle('visible');
    exampleElement.classList.toggle('hidden');
  }, 3000);
}
#main {
  display: flex;
  flex-direction: column;
  width: 300px;
  text-align: center;
}

.hidden {
  visibility: collapse;
  opacity: 0;
  transition: visibility 2s, opacity 2s linear;
}

.visible {
  visibility: visible;
  opacity: 1;
  transition: visibility 0.5s, opacity 0.5s linear;
}
<div id="main">
  <button onclick="toggleVisibility()">Click Me!</button>
  <span class="hidden"></span>
  <span>I will get pushed back up...</span>
</div>

If you are using jQuery to set your classes, this will work 100%:

$(document).ready(function() {
  $('button').click(function() {
    var container = $('.container');
    
    if (!container.hasClass('active')) {
      container.addClass('show').outerWidth();
      container.addClass('active');
    }
    else {
      container.removeClass('active').one('transitionend', function() {
        container.removeClass('show');
      });
    }
  });
});
.container {
  display: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.container.show {
  display: flex;
}
 
.container.active {
  opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button type="button">Toggle</button>

<div class="container">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>

Of course you could just use jQuery .fadeIn() and .fadeOut() functions, but the advantage of setting classes instead is in case you want to transition to a display value other than block (as is the default with .fadeIn() and .fadeOut()).

Here I am transitioning to display flex with a nice fade effect.

In my case I was using the Jquery toggle function to manage the visibility of the element, so instead of handling the transition with css I used to slideToggle to manage the visibility and to handle the transition part too.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>slideToggle demo</title>
  <style>
  p {
    width: 400px;
  }
  </style>
  <script src="https://code.jquery.com/jquery-3.5.0.js"></script>
</head>
<body>
 
<button id="toggle-button">Toggle</button>
<button id="slide-toggle-button">Slide Toggle</button>
<p>
  This is the paragraph to end all paragraphs.  You
  should feel <em>lucky</em> to have seen such a paragraph in
  your life.  Congratulations!
</p>
 
<script>
$( "#toggle-button" ).click(function() {
   $( "p" ).toggle();
});
$( "#slide-toggle-button" ).click(function() {
  $( "p" ).slideToggle( "slow" );
});
</script>
 
</body>
</html>

I've found the best solution, overall, to be: use a negative top margin instead of display:none.

The solution that worked well for me was to use a negative top margin - margin being valid for animation/transition - to shift the menu off the top of the page after my transform transition. One of the issues for me was that it was taking up space even after being hidden using visibility, and this solution below (combined with absolute positioning) solved that problem. Doing this (and setting visibility to hidden for semantic purposes) is almost as good as giving it display:none. Note, however, that the visibility setting below is optional, and the real work is being done by the negative margin.

I like this solution because it avoids JavaScript - you can see I'm using an input element (hidden underneath a burger icon) to hide/unhide the menu which slides in from the left of the screen. The one slight annoyance is having to use an "arbitrarily large number" for the negative margin, but it works well enough. The only fully clean solution IMHO is for transitions to support things like display where the transition just waits for the delay then transitions instantly, but with the current CSS standards spec here is my best solution:

CSS

#menu {
    /* Hide menu */
    position: absolute;
    overflow: hidden;

    margin: -99999px 0 0 -50px;
    padding: 0;
    visibility: hidden;
    transform-origin: 0% 0%;
    transform: translate(-100%, 0);

    transition: margin 0s 0.5s, padding 0s 0.5s, visibility 0s 0.5s, transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1.0);
}
#menuToggle input:checked~#menu {
    /* Display menu */
    overflow: unset;

    margin: -80px 0 0 -50px;
    padding: 125px 50px 25px 50px;
    visibility: unset;
    transform: none;

    transition: margin 0s 0s, padding 0s 0s, visibility 0s 0s, transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1.0);
}

HTML

<div id="menuToggle">
    <input type="checkbox" id="chkBurgerMenu" />
    <span></span><span></span><span></span>
    <ul class="navbar-nav">
        <li><a href="#" class="nav-link"">Home</a></li>
        <li><a href="#" class="nav-link"">Link1</a></li>
        <li><a href="#" class="nav-link"">Link2</a></li>
    </ul>
</div>
Related