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?
source to share
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>
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.
source to share
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>
jsfiddle https://jsfiddle.net/5v3xwak6/5/
source to share
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>
The goal is to provide you with an alternative if you are interested. Hope it helps.
source to share