Animate the overflow property

22,286

Solution 1

The solution is to use AnimationEvent listeners. Here's my raw implementation:

CSS

• 2 animations (open, close)

• 2 classes (opened, closed)

• 2 states (overflow hidden/visible)

opened and closed are always toggled at animationstart, while hidden/visible states are differently worked out on animationend.

Note: you'll see a #main-menu element: it's an UL with transitioned translations on y-axis, because the whole thing is a menu slide-down/up effect.

    @include keyframes(open) {
       0% {
         height:0;
       }
       100% {
         height:$main_menu_height;
       }
    }

    @include keyframes(close) {
       0% {
         height:$main_menu_height;
       }
       100% {
         height:0;
       }
    }


 #main-menu-box{
    overflow-y:hidden;
    height:0; // js

    &.closed{
        @include animation('close 200ms ease-out 0s');
    }

    &.opened{
        @include animation('open 200ms ease-out 0s 1');

        //#main-menu{
        //  @include translate(0, 0);
        //}
    }

    &.overflow-hidden{
        overflow-y:hidden;
    }

    &.overflow-visible{
        overflow-y:visible;
    }
 }

JS

• hamburger is a simple on/off button

• for now I had to use both jquery and vanilla selectors..

function poly_event_listener(element, type, callback) {
    var pfx = ['webkit', 'moz', 'MS', 'o', ''];
    for(var i=0; i<pfx.length; i++) {
        if (pfx[i] === '') type = type.toLowerCase();
        element.addEventListener(pfx[i]+type, callback, false);
    }
}

var hamburger = $('header .hamburger');
var main_menu_box = $('#main-menu-box');
var main_menu_box_std = document.querySelector('#main-menu-box');
var init_menu = true;

hamburger.click(function(){
  if(init_menu){
    main_menu_box.addClass('opened');
    init_menu = false;
    return;
  }

  main_menu_box.toggleClass('opened closed');
});

poly_event_listener(main_menu_box_std,'AnimationStart',function(e){
  main_menu_box.addClass('overflow-hidden');
  main_menu_box.removeClass('overflow-visible');
});

poly_event_listener(main_menu_box_std,'AnimationEnd',function(e){

  // in all the other cases I want hidden:true, visible:false
  // if class == closed, since animationend comes after animationstart, the state will already be hidden:true, visible:false
  // so non need to check for 'closed' here
  if(main_menu_box.hasClass('opened')){
    main_menu_box.addClass('overflow-visible');
    main_menu_box.removeClass('overflow-hidden');
  }
});

This works for me.

Solution 2

In most modern browsers, clip-path (prefixed with -webkit- in Safari) is an animatable property which can sometimes be used as an alternative to overflow.

Given the original example, the closest way to use clip-path to emulate flipping overflow on the last frame would look something like this:

@include keyframes(open) {
  0% {
    height: 0;
    clip-path: inset(0);
  }
  99.99999% {
    clip-path: inset(0);
  }
  100% {
    height: $main_menu_height;
    clip-path: inset(-100vh -100vw);
  }
}

#main-menu-box {
  clip-path: inset(0);
  height: 0;

  &.opened {
     @include animation('open 200ms ease-out 0s 1 normal forwards');
  }
}

Since this animation is a simple linear animation it could even be replaced with a regular CSS transition:

#main-menu-box {
  clip-path: inset(0);
  height: 0;
  transition: clip-path 0s ease-out, height 200ms ease-out;

  &.opened {
    height: $main_menu_height;
    clip-path: inset(-100vh -100vw);
    transition-delay: 200ms, 0s;
  }
}

However, there are two notable differences between clip-path and overflow which make it not suitable in all cases.

First, unlike an element with overflow: visible, an element with any clip-path has a stacking context, so the way that overflowing content is rendered will be different—though in the case of a menu with overflowing content, you probably wanted this anyway!

Second, unlike overflow which only clips children, clip-path clips the entire element. This means if you have borders, box shadows, etc., those are going to get clipped, too. Depending upon the design of the container, this can sometimes be worked around by applying the clip to a child wrapper element instead.

Solution 3

The overflow property can't be animated with CSS. See the W3 specs here : overflow properties

Animatable: no

You can also see a list of animatable properties on MDN : Animated properties


Workaround :

If you need to change the overflow property, you can use JS. Here is an example with the jQuery library.

The overflow property changes with a class toggle. It is fired when the div is clicked :

$('#w').click(function() {
  $(this).toggleClass('open');
});
#w {
  width: 100px;
  height: 100px;
  border: 1px solid red;
  overflow: hidden;
}
#w.open {
  overflow: visible;
}
#w div {
  height: 200px;
  width: 50px;
  background: gold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="w">
  <div></div>
</div>
Share:
22,286
Luca Reghellin
Author by

Luca Reghellin

I'm a full stack web developer since 1999. I'm an advanced html, css, javascript, php, wordpress and prestashop programmer and if needed, I can fully manage all projects sides, including design and art direction, and coordinate all the assets involved.

Updated on April 25, 2020

Comments

  • Luca Reghellin
    Luca Reghellin about 4 years

    I need to animate height, and set overflow: hidden for the first keyframe, and overflow: visible (and keep it) for the last one.

    I'm trying this, but at the end, overflow is still hidden.

    How can I solve this issue?

    The 2 includes are merely SCSS polifill mixins.

    @include keyframes(open) {
      0% {
        height: 0;
        overflow: hidden;
      }
      100% {
        height: $main_menu_height;
        overflow: visible;
      }
    }
    
    
    #main-menu-box {
        overflow: hidden;
        height: 0;
    
        &.opened{
           @include animation('open 200ms ease-out 0s 1 normal forwards');
        }
    }