在未知关键帧上平滑停止 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
之前保存#element
的matrix
位置,在动画暂停后将#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 不确定“慢”按钮点击的预期效果是否也要调整;没有解决css
或js
处的“慢”按钮。【参考方案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 旋转动画的主要内容,如果未能解决你的问题,请参考以下文章