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

Posted

技术标签:

【中文标题】在未知关键帧上平滑停止 CSS 旋转动画【英文标题】:Stop CSS rotate animation smoothly on unknown keyframe 【发布时间】:2015-08-23 02:51:25 【问题描述】:

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

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

我已经尝试添加 animation-iteration-count: 1; 以及添加/删除类。

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

有人知道吗?

【问题讨论】:

【参考方案1】:

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

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

当点击停止按钮时,我们会减小半径以使其看起来正在停止。我们可以做相反的事情重新开始。好处是我们可以随时停下来享受自然的休息。如果要偏移角度,可以在减小半径的同时插入到该角度。

通过使用requestAnimationFrame和transforms,我们将获得与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 的风险。

【讨论】:

非常感谢,这对于在停止徽章时获得正确感觉非常有帮助。我必须检查性能如何受到影响。干杯!【参考方案2】:

尝试在将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/

【讨论】:

感谢您的回答。单击慢速按钮时,我仍然会得到跳跃的效果。似乎将 animation-iteration-count 更改为 1 总是会带来这个问题。很好的尝试并为矩阵方法竖起大拇指。 @pato.barrientos 不确定“慢”按钮点击的预期效果是否也要调整;没有解决cssjs 处的“慢”按钮。【参考方案3】:

如果您使用 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>

如果您有兴趣,我们的目的是为您提供替代方案。希望对您有所帮助。

【讨论】:

感谢您的回复。 TweenMax 库看起来很棒,并且使用它来完成所需的行为似乎更简单,但我现在无法将它添加到这个项目中。 我明白了。 GSAP 已存在多年,对我而言,它始终是满足我在 html5 或 Flash 项目中的所有动画需求的一站式解决方案。

以上是关于在未知关键帧上平滑停止 CSS 旋转动画的主要内容,如果未能解决你的问题,请参考以下文章

平滑的 CSS 过渡动画旋转

CSS关键帧动画优雅完成

平滑停止 CSS 关键帧动画

使用变换时,悬停时的 CSS 动画停留在最后一个关键帧:旋转

css3圆环旋转效果动画怎么做

停止无限的 CSS3 动画并平滑恢复到初始状态