【问题标题】:Stop CSS rotate animation smoothly on unknown keyframe在未知关键帧上平滑停止 CSS 旋转动画
【发布时间】: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"
    }

有人知道吗?

【问题讨论】:

    标签: javascript css jquery-animate css-animations


    【解决方案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 项目中的所有动画需求的一站式解决方案。
    猜你喜欢
    • 2015-06-22
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    • 2015-01-05
    • 2015-12-26
    • 2023-02-16
    • 2013-08-12
    • 2019-03-12
    相关资源
    最近更新 更多