javascriptcsscss-transitionshtml5-animation

CSS3 Smooth transition when dynamically changing animations


I have two keyframe animations "Bounce-In" and "Bounce-Out" the Bounce-In animation takes 1.2 seconds to complete, but if a user triggers the Bounce-Out function before it's finished it will jump to 100% scale and doesn't gracefully scale out from it's current animation position.

Is this possible with keyframe animations? I've seen it done with transition property but not using scale().

@-webkit-keyframes Bounce-In 
{
0% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
60% { -webkit-transform: scale(0.7) }
80% { -webkit-transform: scale(1.0) }
90% { -webkit-transform: scale(0.9) }
100% { -webkit-transform: scale(1.0) }
}

@-webkit-keyframes Bounce-Out 
{
0% { -webkit-transform: scale(1.0) }
40% { -webkit-transform: scale(0.1) }
60% { -webkit-transform: scale(0.4) }
80% { -webkit-transform: scale(0.1) }
90% { -webkit-transform: scale(0.2) }
100% { -webkit-transform: scale(0) }
}

I have a demo on JSFiddle: http://jsfiddle.net/Vn3bM/98/ *if you click the "Games" circle before the animation is finished you will notice the other two jump to 100% and then animate out (that's what I'm trying to make smooth).

I even tried removing the 0% keyframe from Bounce-Out and that didn't help...


Solution

  • In your case, the "jump" you notice in your animation comes from the change of animations you have installed on onmouseup. The "Bounce-Out" Animation has an initial scale of 1 (first Keyframe), and this is what the two circles immediately get set to when the animation is installed.

    There are two solutions to this, which I can explain in some detail:


    The Easy Way

    You could just wait for the initial animation to end via the 'webkitAnimationEnd' Event and install the onmouseup event with a recursive function waiting for the animation to finish:

    var initAnimationEnded = false;
    document.getElementById('sports').addEventListener('webkitAnimationEnd', function() {
     initAnimationEnded = true;
    }, false);​
    

    And here's the onmouseup handler:

    document.getElementById('games').onmouseup = function() {
      function bounceOut() {
        if (initAnimationEnded) {
            events.style.webkitAnimationName = "Bounce-Out";
            sports.style.webkitAnimationDelay = "0.2s";
            sports.style.webkitAnimationName = "Bounce-Out";
        } else {
            setTimeout(bounceOut, 20);
        }
      }
      bounceOut();
    }
    


    I installed a jsfiddle here so you can see it working. The Bounce Out animation is only triggered after the animation finished, nothing unusual about that.


    The Hard Way

    You can pause the animation and parse the current values of the transformation, then install a temporary keyframe animation to bounce out. This gets much more verbose though:

    First, you have to stop the animations:

    events.style.webkitAnimationPlayState = "paused";
    sports.style.webkitAnimationPlayState = "paused";
    

    Then, you set up a helper to insert new css rules:

    var addCssRule = function(rule) {
      var style = document.createElement('style');
      style.innerHTML = rule;
      document.head.appendChild(style);
    }
    

    Then create the css keyframe rules on the fly and insert them:

        // get the current transform scale of sports and events
    
        function getCurrentScaleValue(elem) {
          return document.defaultView.
          getComputedStyle(elem, null).
          getPropertyValue('-webkit-transform').
          match(/matrix\(([\d.]+)/)[1];
        }
    
        var currentSportsScale = getCurrentScaleValue(sports);
        var currentEventsScale = getCurrentScaleValue(events);
    
         // set up the first string for the keyframes rule
    
        var sportsTempAnimation = ['@-webkit-keyframes Sports-Temp-Bounce-Out {'];
        var eventsTempAnimation = ['@-webkit-keyframes Events-Temp-Bounce-Out {'];
    
         // basic bounce out animation
    
        var bounceOutAnimationBase = {
            '0%': 1,
            '40%': 0.1,
            '60%': 0.4,
            '80%': 0.1,
            '90%': 0.2,
            '100%': 0
        };
    
         // scale the animation to the current values
    
        for (prop in bounceOutAnimationBase) {
            sportsTempAnimation.push([
              prop, ' 
              { -webkit-transform: scale(', 
              currentSportsScale * bounceOutAnimationBase[prop], 
              ') } '].join(''));
            eventsTempAnimation.push([
               prop, 
               ' { -webkit-transform: scale(', 
               currentEventsScale * bounceOutAnimationBase[prop], 
               ') } '
             ].join(''));
        }
    
         // add closing brackets
    
        sportsTempAnimation.push('}');
        eventsTempAnimation.push('}');
    
         // add the animations to the rules
    
        addCssRule([sportsTempAnimation.join(''), 
                    eventsTempAnimation.join('')].join(' '));
    

    Then, you restart the animations with these rules:

        events.style.webkitAnimationName = "Events-Temp-Bounce-Out";
        events.style.webkitAnimationDelay = "0s";
        sports.style.webkitAnimationDelay = "0s";
        sports.style.webkitAnimationName = "Sports-Temp-Bounce-Out";
        events.style.webkitAnimationPlayState = "running";
        sports.style.webkitAnimationPlayState = "running";
    


    Et voilà. I made a jsfiddle here so you can play around with it.


    More Sugar