Stop CSS to rotate animation smoothly at unknown keyframe

I have an image that is swinging with a CSS rotation animation. I would like to stop it smoothly (and return it to its original position by clicking on another element, but without getting the awkward feeling of stopping). It looks like the expected behavior only happens on the first iteration of the animation, but not on the upcoming ones (this happens when the "slow" button is pressed for the first 2 seconds)

Here is some sample code https://jsfiddle.net/pbarrientos/5v3xwak6/

I've already tried adding animation-iteration-count: 1; and adding / removing classes.

var css = { 
        '-webkit-animation-iteration-count': "1",
        '-moz-animation-iteration-count': "1",
        'animation-iteration-count': "1"
    }

      

Does anyone know?

+3


source to share


3 answers


I would use manual animation here. The browser is responsible for its CSS animations, and it would be difficult to intervene with perfectly synchronized position and speed.

Since the animation is not very complicated, we can just set up our own matrix or use helper methods and also use a sine function where the radius decreases when stopped.

When we hit the stop button, we decrease the radius so that it appears stopped. We can do the opposite to start again. The advantage is that we can stop at any time and have a natural rest. If you want to compensate for an angle, you could interpolate to that angle at the same time as decreasing the radius.

Using requestAnimationFrame

and transforming, we get smooth animations just like with CSS.

Main function:

angle = Math.sin(time) * radius;  // sin=[-1,1] radius => angle

      



Then, when stopped, decrease the radius, which will be equal to the angle:

radius *= 0.99;      

      

Example

var img = $("img"), btn = $("button"),
    angle, maxRadius = 10, radius = maxRadius,
    playing = true, time= 0;

(function loop() {
  angle = Math.sin(time) * radius;            // calc current angle
  setTransform(img, angle);

  if (playing) {
    if (radius < maxRadius) radius *= 1.03;   // increase 3% each frame upto max
  } else {
    radius *= 0.99;                           // reduce 1% each frame
  }
  
  time += 0.1;
  requestAnimationFrame(loop)                 // loop, can be stopped when radius < n
})();

function setTransform(img, angle) {
  img.css("transform", "rotate(" + angle + "deg)");
  img.css("-webkit-transform", "rotate(" + angle + "deg)");
}

btn.on("click", function() {
  playing = !playing;
  if (playing && radius < 0.1) radius = 0.1;  // give some meat in case =0
});
      

img {
  transform-origin: top center; -webkit-transform-origin: top center;
  }
      

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img src="http://i.stack.imgur.com/Vma8v.png"><br>
<button>Start / Stop smoothly</button>
      

Run code


You might want to implement a mechanism to break the loop when the radius is less than n, and then start the loop as needed. To do this, use a separate flag to eliminate the risk of running multiple rAFs.

+2


source


Try to keep matrix

the position #element

before animation-play-state

mounted on paused

adding the stored position matrix

from #element

before css

, applied to the #element

after suspension animation; reset animation-iteration-count

to infinite

in event #play

click

- like when #slow

clicked earlier by setting animation-iteration-count

to1



$(document).ready(function() {
  var el = $("#element");
  
  // return `matrix` position of `el` `#element`
  function tfx(el) {
    return el.css(["-webkit-transform", "-moz-transform"]);
  }

  $("#pause").click(function() {
    // save `matrix` position of `el`
    var pos = tfx(el);
    var css = {
      "-webkit-animation-play-state": "paused",
      "-moz-animation-play-state": "paused",
      "animation-play-state": "paused"
    }
    // extend `css` with `pos`
    var _css = $.extend(pos, css);
    el.css(_css);
  });

  $("#play").click(function() {
    // save `matrix` position of `el` 
    var pos = tfx(el);
    // reset `animation-iteration-count` to `infinite`
    var css = {
      "-webkit-animation-iteration-count": "infinite",
      "-moz-animation-iteration-count": "infinite",
      "animation-iteration-count": "infinite",
      "-webkit-animation-play-state": "running",
      "-moz-animation-play-state": "running",
      "animation-play-state": "running"
    }
    // extend `css` with `pos`
    var _css = $.extend(pos, css);
    el.removeClass("stopit").addClass("swing").css(_css);
  });

  $("#slow").click(function() {
    el.removeClass("swing").addClass("stopit");
    var css = {
      "-webkit-transition": "all 4000ms ease-out",
      "-webkit-animation-iteration-count": "1",
      "-moz-animation-iteration-count": "1",
      "animation-iteration-count": "1"
    }
    el.css(css);
     // `stopit` class added above ?
     // el
     // .one("webkitAnimationEnd oanimationend msAnimationEnd animationend", function (e) {
      // el.addClass("stopit");
    // });
  });
});
      

.swing {
  transform-origin: top center;
  -webkit-transform-origin: top center;
  animation: badge-swing 2s infinite;
  -webkit-animation: badge-swing 2s infinite;
  -webkit-animation-fill-mode: both;
  -webkit-animation-timing-function: ease-in-out;
  -moz-animation: badge-swing 2s infinite;
  -moz-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
}
.stopit {
  -webkit-animation-duration: 2s;
  -webkit-animation-name: stopit;
  -webkit-animation-fill-mode: forwards;
  -moz-animation-duration: 2s;
  -moz-animation-name: stopit;
  -moz-animation-fill-mode: forwards;
  animation-name: stopit;
}
@-webkit-keyframes badge-swing {
  0% {
    -webkit-transform: rotate(-5deg);
    -webkit-animation-timing-function: ease-in;
  }
  25% {
    -webkit-transform: rotate(0deg);
    -webkit-animation-timing-function: ease-out;
  }
  50% {
    -webkit-transform: rotate(5deg);
    -webkit-animation-timing-function: ease-in;
  }
  75% {
    -webkit-transform: rotate(0deg);
    -webkit-animation-timing-function: ease-out;
  }
  100% {
    -webkit-transform: rotate(-5deg);
    -webkit-animation-timing-function: ease-in;
  }
}
@-moz-keyframes badge-swing {
  0% {
    -moz-transform: rotate(-5deg);
    -moz-animation-timing-function: ease-in;
  }
  25% {
    -moz-transform: rotate(0deg);
    -moz-animation-timing-function: ease-out;
  }
  50% {
    -moz-transform: rotate(5deg);
    -moz-animation-timing-function: ease-in;
  }
  75% {
    -moz-transform: rotate(0deg);
    -moz-animation-timing-function: ease-out;
  }
  100% {
    -moz-transform: rotate(-5deg);
    -moz-animation-timing-function: ease-in;
  }
}
@-webkit-keyframes stopit {
  0% {
    -webkit-transform: rotate(-5deg);
    -webkit-animation-timing-function: ease-out;
  }
  100% {
    -webkit-transform: rotate(0deg);
    -webkit-animation-timing-function: ease-out;
  }
}
@-moz-keyframes stopit {
  0% {
    -moz-transform: rotate(-5deg);
    -moz-animation-timing-function: ease-out;
  }
  100% {
    -moz-transform: rotate(0deg);
    -moz-animation-timing-function: ease-out;
  }
}
#pause,
#play,
#slow {
  display: inline-block;
  padding: 5px 30px;
  background: lightgrey;
  border-radius: 5px;
}
      

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<img id="element" src="https://dl.dropboxusercontent.com/u/39131788/palmed.png" class="swing">
<br>
<br>
<div id="pause">pause</div>
<div id="play">play</div>
<div id="slow">slow</div>
      

Run code


jsfiddle https://jsfiddle.net/5v3xwak6/5/

+1


source


It would be great if you could use TweenMax .

jsFiddle .

Excerpt:

var element=document.getElementById('element');
var playButton=document.getElementById('play');
var pauseButton=document.getElementById('pause');
var slowButton=document.getElementById('slow');
var maxDegree=-10;
var minDegree=10;
var duration=.8;
var easeFunc=Power2;
var timeline=new TimelineMax({paused:true,repeat:-1});
TweenMax.set(element,{transformOrigin:'top center'});
timeline.to(element,duration,{rotation:maxDegree,ease:easeFunc.easeOut});
timeline.to(element,duration,{rotation:0,ease:easeFunc.easeIn});
timeline.to(element,duration,{rotation:minDegree,ease:easeFunc.easeOut});
timeline.to(element,duration,{rotation:0,ease:easeFunc.easeIn});

playButton.addEventListener('click',onPlayClick,false);
pauseButton.addEventListener('click',onPauseClick,false);
slowButton.addEventListener('click',onSlowClick,false);

function onPlayClick(){timeline.timeScale(1).play();}
function onPauseClick(){timeline.timeScale(1).pause();}
function onSlowClick(){
    timeline.pause().timeScale(.5);
    if(timeline.progress()<.25){
        timeline.tweenTo(0);
    }else if(timeline.progress()>=.25&&timeline.progress()<.75){
        timeline.tweenTo(timeline.duration()*.5);
    }else{
        timeline.tweenTo(timeline.duration());
    }
}
      

#pause, #play, #slow {
    display: inline-block;
    padding: 5px 30px;
    background: lightgrey;
    border-radius: 5px;
}
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"></script>
<img id="element" src="https://dl.dropboxusercontent.com/u/39131788/palmed.png" class="swing">
<br>
<br>
<div id="pause">pause</div>
<div id="play">play</div>
<div id="slow">slow</div>
      

Run code


The goal is to provide you with an alternative if you are interested. Hope it helps.

+1


source







All Articles