4

I'm entering the world of CSS3 animations and transitions so please forgive my ignorance. Here's the simplified version of what I'm trying to do:

  • I have a ball that "pulsates" infinitely via CSS3 keyframes
  • I want the ball to grow bigger and stay like that when I hover over it
  • I want the ball to become small again when I move the mouse away from it and keep pulsating (all the transitions need to be smooth, of course).

Here's my stab at it using a mix of CSS3 animations and transitions (testing this on Chrome so far, hence webkit prefixes):

@-webkit-keyframes pulsate {
    from {
        -webkit-transform: scale(0.7);
    }    
    to {
        -webkit-transform: scale(0.8);
    }
}

.ball {
    background: blue;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    transition: all 1s;

    -webkit-transform: scale(0.7);
    -webkit-transform-origin: center center; 

    -webkit-animation-duration: 800ms;
    -webkit-animation-name: pulsate;
    -webkit-animation-iteration-count: infinite;
    -webkit-animation-direction: alternate;
    -webkit-animation-timing-function: ease-in-out;
}

.ball:hover {
    -webkit-transform: scale(2);
    -webkit-animation-play-state: paused; /* transition works but gets reset at the end*/
    /*-webkit-animation: 0;*/ /* transition works only one time, and no smooth transition on mouse out */
}

jsFiddle Demo

The result is pretty close but as soon as the ball finishes expanding on hover, it suddenly becomes small again (don't understand why). I also tried disabling the animation via -webkit-animation: 0; instead of pausing it but it doesn't work well either.

I tried a different approach that uses keyframes only (no transitions) by attempting to call a different keyframe set on hover:

@-webkit-keyframes pulsate {
    from {
        -webkit-transform: scale(0.7);
    }    
    to {
        -webkit-transform: scale(0.8);
    }
}

@-webkit-keyframes expand {
    to {
        -webkit-transform: scale(2);
    }
}

@-webkit-keyframes shrink {
    to {
        -webkit-transform: scale(0.7);
    }
}

.ball {
    background: blue;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    transition: all 2s;

    -webkit-transform: scale(0.7);
    -webkit-transform-origin: center center; 

    -webkit-animation-duration: 800ms, 800ms;
    -webkit-animation-name: shrink, pulsate;
    -webkit-animation-iteration-count: 1, infinite;
    -webkit-animation-direction: normal, alternate;
    -webkit-animation-timing-function: ease-in-out, ease-in-out;
}

.ball:hover {
    -webkit-animation-name: expand;
    -webkit-animation-iteration-count: 1;
    -webkit-animation-direction: normal;
    -webkit-animation-fill-mode: forwards;    
}

jsFiddle Demo

The ball stays big as long as the mouse is over it but there's still no smooth transition when the mouse moves away from the ball. I expect it to play the shrink animation instead but it doesn't.

Am I missing something or this is impossible to implement with just pure CSS at the moment?

// Related thread but didn't work for me: Stop animation and start transition on hover

Community
  • 1
  • 1
Dmitry Pashkevich
  • 13,431
  • 9
  • 55
  • 73
  • 1
    in your first fiddle - if you add a slight delay on the transition it will always transition on hover (not only on the first time): **transition: all 1s 0.01;** - http://jsfiddle.net/danield770/ntTUt/1/ – Danield Aug 20 '13 at 11:54
  • Interesting! Can you explain why it works? I still need the transition to start from arbitrary state while it's pulsating, not from scale(0.7) as it happens now, and transition back smoothly on mouseout – Dmitry Pashkevich Aug 20 '13 at 12:55

2 Answers2

3

You need to add an animation delay to allow the transition to complete because it reverts back to scale(.7) at the start of the animation. Updated jsFiddle

-webkit-animation-delay:1s;

EDIT

I realized that the answer I posted here was not fully correct. True, the delay animated the transition from big back to small, but if you hover over the pulsing ball when its expanded it jumps back to it's 0 value of .7 before animating to the large scale.

Updated Demo

I came up with a fix that just uses some javascript to fix it based on this article. You do have to change the CSS a little, but it's not very noticeable in the outcome. Here is the updated code

/* CSS */
body {margin: 100px;}

@-webkit-keyframes pulsate {
    0% {
        -webkit-transform: scale(0.7);
    }    
    50% {
        -webkit-transform: scale(0.8);
    }
    100% {
        -webkit-transform: scale(0.7);
    }
}

.ball {
    background: blue;
    width: 100px;
    height: 100px;
    border-radius: 50%;    
    -webkit-transform: scale(0.7);
    -webkit-transform-origin: center center; 
    transition: all 1s;
}
.ball.animated {
  -webkit-animation-duration: 1600ms; 
  -webkit-animation-name: pulsate;
  -webkit-animation-iteration-count: infinite;
  -webkit-animation-direction: alternate;
  -webkit-animation-timing-function: ease-in-out;
}

/* Javascript */
var ball = document.getElementsByClassName('ball')[0],
    pfx = ["webkit", "moz", "MS", "o", ""],
    hovered = false;

function AnimationListener() {
    if(hovered)
    { 
      ball.classList.remove('animated');     
      ball.style.webkitTransform = 'scale(2)';
      ball.style.transform = 'scale(2)';
    }
}

function TransitionListener() {
  if(!hovered)
  {
    ball.classList.add('animated');
  }
}

function PrefixedEvent(element, type, callback) {
    for (var p = 0; p < pfx.length; p++) {
        if (!pfx[p]) type = type.toLowerCase();
        element.addEventListener(pfx[p]+type, callback, false);
    }
}

PrefixedEvent(ball, "AnimationIteration", AnimationListener);

ball.onmouseover = function() {
  hovered = true;
}
ball.onmouseout = function() {
  hovered = false;
  PrefixedEvent(ball, "TransitionEnd", TransitionListener);
  ball.style.webkitTransform = 'scale(.7)';
  ball.style.transform = 'scale(.7)';  
}
Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
  • Great, I didn't realize that, thanks! Only one minor issue left - is it possible to make the ball start expanding from it's current state when the mouse is over it (not from initial state)? Setting `-webkit-animation-fill-mode: forwards;` doesn't help cause I disable the animation on hover – Dmitry Pashkevich Aug 20 '13 at 14:31
  • 1
    Theoretically you can keep the animation running a little longer (during the transition), but since they affect the same variable (scale) you can't. The problem then goes into a different issue, getting the current % of the keyframe and approximating the result. It is possible, but you have to use javascript to do so. I asked a question and posted a solution attempting to do exactly this (with a slightly different animation) [**here on SO**](http://stackoverflow.com/questions/18006099/get-current-keyframes-percentage) – Zach Saucier Aug 20 '13 at 15:10
  • So in other words there's no way of switching from one keyframe sequence to another in CSS3 while preserving state – Dmitry Pashkevich Aug 20 '13 at 16:28
  • Not without javascript or without changing different values, no – Zach Saucier Aug 20 '13 at 17:18
  • Anything else I can answer? – Zach Saucier Aug 21 '13 at 20:18
  • I updated my answer with a full solution but it involves some javacript. I hope it serves your needs better! – Zach Saucier Sep 05 '13 at 00:59
  • Thanks, @Zeaklous! I tried your jsFiddle and it doesn't quite work (for some reason the ball doesn't always expand on hover and I couldn't figure out the behavior pattern) but I get the idea! For now I'm leaving the solution without js as the "jump" is quite minor but thanks for doing further investigation! – Dmitry Pashkevich Sep 05 '13 at 08:54
1

Just update this CSS rule, I have added From & To - in Expand & Shrink:

@-webkit-keyframes expand {
    from {
        -webkit-transform: scale(1);
    }    
    to {
        -webkit-transform: scale(2);
    }
}

@-webkit-keyframes shrink {
    from {
        -webkit-transform: scale(2);
    }    
    to {
        -webkit-transform: scale(1);
    }
}
Sunil Kumar
  • 1,718
  • 16
  • 33
  • Thanks, this looks better. However, I don't understand why do I need to explicitly specify `from`. The spec says that if `from` is omitted, the browser uses currently computed styles as starting point. This is desired behavior because I can move my mouse away before the ball fully expands and I don't want to see a jump there. – Dmitry Pashkevich Aug 20 '13 at 11:52