Question

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

Was it helpful?

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

  • In your example, the circles bounce out alternating in bounce. You can easily get this back to work with the second solution by using setTimeout for all sports circle animations. I did not want to include it here because it would unnecessarily complicate the example code.

  • I know the provided examples are not really DRY, you could for example make all the stuff for events and sports work with half the lines of code (with meta properties), but in terms of readability, I think this example serves well.

  • To have this example working in all browsers with support for css3 animations, you need to normalize the transition properties. To do this in javascript, have a look here The Example works for animations and other properties as well, just replace 'transition' with the property you want

  • For a further read on modifying css3 animations on the fly, I found this post very useful, have a look at it as well.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top