Search code examples
angularjsng-animateangular-transitions

ng-animate: only add to the dom after the animation delay


I'm trying to use ng-animate with an ng-repeat (and ng-show) to fade out old content and replace it with new.

The problem I'm having is that during the remove and add animations, both the element(s) being added and the element(s) being removed have display:block.

I thought I could avoid this by using an animation-delay in the CSS, but that just delays the fade, and not the addition of the class that sets display on the element.

The result is a jerky transition.

This is my animation CSS (cut down):

.keyframe-fade.ng-enter,
.keyframe-fade.ng-move {
  animation: 0.5s fade-in;
  animation-delay: 1s;
  animation-fill-mode: backwards;
}
.keyframe-fade.ng-leave {
  animation: 0.5s fade-out;
}

But it's easier to demonstrate with this plunkr.

Any ideas?

NOTE: To be clear, the desired behaviour on the plunkr linked is that the coloured squares always take up the same space, i.e. they sit on the same line and the button doesn't move. If possible, I'd like to fix this without absolute positioning 'bodges' as the actual page I'm using this on is much more complex than the demo given.


Solution

  • The solution that I found for this is to augment the pure CSS animation with a very small amount of JavaScript.

    To summarise the problem:

    • The entering element is added to the DOM with the ng-enter class at the same time that the leaving element is given the ng-leave class.

    • Though there is an animation delay, the entering element still takes up space

    So this piece of javascript takes the element and adds ng-hide for the duration of the leave-animation, removing it afterwards.

    .animation('.keyframe-fade', ['$timeout', function ($timeout){
      return {
        enter: function (element, done){
    
          // Add ng-hide for the duration of the leave animation.
          element.addClass('ng-hide');
    
          $timeout(function(){
            element.removeClass('ng-hide');
          }, 500)
    
          done();
    
        }
      }
    }])
    

    The duration is hard-coded here but I don't see any reason that you couldn't grab it from the element instead.

    Improvements/suggestions welcomed.

    Here's the original plunkr with the change.