停止 CSS 在未知关键帧上平滑地旋转动画

Stop CSS rotate animation smoothly on unknown keyframe

我有一个使用 CSS 旋转动画摆动的图像。 我想顺利地停止它(并在单击另一个元素时将它放回原来的位置,但不会有跳动停止的感觉)。 似乎预期的行为只发生在动画的第一次迭代上,但不会发生在即将到来的动画上(这是在前 2 秒内单击 "slow" 按钮时)

这是一个示例代码 https://jsfiddle.net/pbarrientos/5v3xwak6/

我已经尝试添加 animation-iteration-count: 1; 和 adding/removing 类.

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

有线索吗?

尝试在 animation-play-state 设置为 paused 之前保存 #elementmatrix 位置,将 #element 的已保存 matrix 位置添加到 css 在动画暂停后应用于 #element ;在 #play click 事件中将 animation-iteration-count 重置为 infinite - 例如 #slow 之前单击的位置,将 animation-iteration-count 设置为 1

$(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/

如果您使用 TweenMax 会很有趣。

jsFiddle.

片段:

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>

目的是为您提供一个替代方案,如果您有兴趣的话。希望对你有帮助。

我会在这里使用手动动画。浏览器负责其 CSS 动画,并且以完美同步的位置和速度进行干预将具有挑战性。

由于动画不是很复杂,我们可以简单地设置我们自己的矩阵,或使用辅助方法,以及使用停止时半径减小的正弦函数。

当按下停止按钮时,我们减小了半径以使其看起来像是在停止。我们可以反其道而行之,重新开始。好处是我们可以随时停下来,自然休息。如果你想偏移角度,你可以在减小半径的同时插值到那个角度。

通过使用 requestAnimationFrame 和变换,我们将获得像 CSS 一样流畅的动画。

主要功能是:

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

然后在停止时,减小半径,最终会变成角度:

radius *= 0.99;      

例子

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>

您可能希望实现一种机制,当半径小于 n 时也能中止循环,然后在需要时启动循环。为此使用一个单独的标志,以便消除启动多个 rAF 的风险。