【问题标题】:CSS change animation duration without jumpingCSS改变动画持续时间而不跳跃
【发布时间】:2018-05-14 16:05:27
【问题描述】:

我有一个车轮旋转的简单动画。我正在尝试使用滑块(输入范围)控制纺车的速度。我已经设法做到了,但是每次我更改动画时,动画都会重新启动(它会跳跃)。寻找一种解决方案来创建平滑的速度增加。随着用户增加滑块的值,滚轮以增加的速度旋转。

在下面的代码中,#loading 是纺车。

$(document).on('input', '#slider', function() {
  var speed = $(this).val();
  $('#speed').html(speed);
  $("#loading").css("animation-duration", 50 / speed + "s");
});
#loading {
  position: absolute;
  width:100px; height:100px; background-color:black;
  left: 0;
  right: 0;
  margin: auto;
  transform-origin: 50% 50%;
  animation: rotateRight infinite linear;
  animation-duration: 0;
}

@keyframes rotateRight {
  100% {
    transform: rotate(360deg);
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div id="loading"></div>
<input type="range" min="0" max="100" value="0" class="slider" id="slider">
<p>Speed: <span id="speed"></span></p>

【问题讨论】:

  • 我只看到滑块和速度指示器。
  • @NanduKalidindi 你现在应该可以看到了。
  • 考虑到input 事件到change 所以它会等到用户决定他想要的速度。
  • 你应该考虑使用像greensock这样的框架。
  • 以下解决方案是否解决了您的问题?

标签: javascript jquery html css animation


【解决方案1】:

经典问题
(现在还有跳跃……)

带有 jQ​​uery 的版本

var lasttime = 0,  lastduration = 0,  angle = 0;
$(document).on('input', '#slider', function(event) {
  var speed = $(this).val();
  $('#speed').html(speed);
  
  var el = $("#loading");
  var duration = (speed > 0) ? 50 / speed : 0;
  var currenttime = event.originalEvent.timeStamp / 1000;
  var difftime = currenttime - lasttime;
  el.removeClass("enable_rotate").show();
  
  if (!lastduration && duration)
      el.css("transform", "");
  else
      angle += (difftime % lastduration) / lastduration;
      
  if (duration){     
    el.css("animation-duration", duration + "s")
    .css("animation-delay", -duration * angle + "s")    
    .addClass("enable_rotate");
    }
  else
    el.css("transform", "rotate(" + 360 * angle + "deg)");
  
  angle -= angle | 0; //use fractional part only
  lasttime = currenttime;
  lastduration = duration;
});
.anime_object {
  width:100px; height:100px; background-color:black;
  position: absolute;
  left: 0;
  right: 0;
  margin: auto;
}

.enable_rotate {
  animation: rotateRight infinite linear;
}

@keyframes rotateRight {
  100% {
    transform: rotate(360deg);
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<img id="loading" class="anime_object">
<input type="range" min="0" max="100" value="0" id="slider">
<p>Speed: <span id="speed"></span></p>

工作草案

保存当前动画的变量
http://www.w3.org/TR/css-animations-1/#interface-animationevent-attributes

  1. SO
  2. css-tricks.com

【讨论】:

    【解决方案2】:

    如果不使用不使用 CSS 动画的不同方法,我不完全确定这是否可行。问题是,每当您更改速度时,动画都不会正常化。它总是从动画的 0% 动画到动画的 100%。每次调整animation-duration 时,都会使用动画中的当前位置重新插值。

    换句话说,如果您在t=12 处从animation-duration: 25 更改为50,那么动画完成一半(180 度);现在它只完成了四分之一(90度)。你无法控制t,那是浏览器的。如果可以,您可能希望将 t 设置为保持在插值中的位置,在此示例中为 t=25,以便您保持与您之前相同的动画完成百分比,但您拉伸剩余的时间。

    我稍微修改了你的脚本,试图更好地展示我所描述的内容。它会在速度 0 和 5 之间每秒增加 0.25 的速度。您可以看到问题是由浏览器控制的 t 造成的。

    您可以重写它以便使用 JavaScript 自己控制 t,但我认为您必须放弃 CSS 动画。

    要更深入地讨论这个浏览器控制的t 变量,请查看这篇关于 CSS-Tricks 的文章:Myth Busting: CSS Animations vs. JavaScript

    某些浏览器允许您暂停/恢复 CSS 关键帧动画,但 就是这样。你不能寻找到一个特定的位置 动画,也不能平滑地中途反转或改变 时间尺度或在某些位置添加回调或将它们绑定到丰富的 一组播放事件。 JavaScript 提供了很好的控制,如 下面的演示。

    那是您的问题,您希望能够更改动画的持续时间,但同时还要寻找动画中的正确位置。

    $(function() {
      var speed = parseInt($("#slider").val(), 10);
      $("#speed").html(speed);
      $("#loading").css("animation-duration", 50 / speed + "s");
    
      var forward = true;
      setInterval(function() {
        speed += (forward ? 0.25 : -0.25);
        if (speed >= 5) {
          forward = false;
        } else if (speed <= 0) {
          forward = true;
        }
        $("#loading").css("animation-duration", 50 / speed + "s");
        $("#slider").val(speed);
        $("#speed").html(speed);
      }, 1000);
    });
    #loading {
      position: absolute;
      left: 0;
      right: 0;
      margin: auto;
      transform-origin: 50% 50%;
      animation: rotateRight infinite linear;
      animation-duration: 0;
    }
    
    @keyframes rotateRight {
      100% {
        transform: rotate(360deg);
      }
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <img id="loading" src="//placehold.it/100">
    <input type="range" min="0" max="100" value="0" step=".25" class="slider" id="slider" value="0">
    <p>Speed: <span id="speed"></span></p>

    【讨论】:

      【解决方案3】:

      TL;DR

      不,你不能(根据我的测试)

      • 首先,让我们从 CSS 中删除动画声明并将其移至 Javascript,这样动画循环就不会开始(即使您无法直观地看到它正在运行)。

      您是否注意到,即使您将滑块从初始位置移动,框似乎也是从随机位置开始的?那是因为动画循环实际上一直在运行。

      • 现在,您实际上可以在任何给定时间通过动画获得当前变换值应用于您的盒子,使用getComputedStyle(loadingElement).getPropertyValue('transform'); 这将返回一个矩阵,它不会像那样给您太多,但我们可以计算角度来自该矩阵的旋转:

      (使用一些数学解释 here

      Math.round(Math.atan2(b, a) * (180/Math.PI));
      

      现在我们有了这个值,我们必须将它归一化为角度只有正值,然后我们可以应用这个角度作为transform: rotate(Xdeg)的基础值

      到目前为止一切都很好,你可以在代码 sn-p 中看到它的工作原理,然而即使你这样做了,并且增加/减少速度值,动画循环已经以设定的时间尺度运行,您无法重置此循环。

      到目前为止,我的回答是让其他对动画循环有更深入了解的人可以从中构建,并且可能会想出一个有效的代码。

      如果你还在读这篇文章,你可能会想,那就用loadingElement.style.removeProperty('animation') 删除动画并再次分配它,尝试它不起作用。以及如何使用 setInterval(...,0) 再次启动动画,使其在下一个循环中运行,也不行。

      $(document).on('input', '#slider', function() {
      	var speed = $(this).val();
      	var loadingElement = document.querySelector("#loading")
      	$('#speed').html(speed);
      	//get the current status of the animation applied to the element (this is a matrix)
      	var currentCss = getComputedStyle(loadingElement).getPropertyValue('transform'); 
      
      	if (currentCss !== 'none'){
      		//parse each value we need from the matrix (there is a total of 6)
      		var values = currentCss.split('(')[1];
      		values = values.split(')')[0];
      		values = values.split(',');
      
      		var a = values[0];
      		var b = values[1];
      		var c = values[2];
      		var d = values[3];
      
      		//here we make the actual calculation
      		var angle = Math.round(Math.atan2(b, a) * (180/Math.PI)); 
      
      		//normalize to positive values
      		angle = angle < 0 ? angle + 360 : angle;
      
      		loadingElement.style.removeProperty('animation'); //remove the property for testing purposes
      		$("#loading").css('transform', 'rotate(' + angle + 'deg)');
      	}
      	else{ //else for testing purposes, this will change the speed of the animation only on the first slider input change
      		$("#loading").css('animation', 'rotateRight infinite linear'); //see how the animation now actually starts from the initial location
      		$("#loading").css("animation-duration", 50 / speed + "s");
      	}
      
      });
      #loading {
        position: absolute;
        left: 0;
        right: 0;
        margin: auto;
        transform-origin: 50% 50%;
        /*I removed the initialization of the animation here*/
      }
      
      @keyframes rotateRight {
        100% {
          transform: rotate(360deg);
        }
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
      <img id="loading" src="https://placehold.it/100">
      <input type="range" min="0" max="100" value="0" class="slider" id="slider">
      <p>Speed: <span id="speed"></span></p>

      【讨论】:

        【解决方案4】:

        是的,我们可以!

        这是我们将采取的方法:

        1. 输入变化时,获取新的速度值
        2. 获取元素的当前transform值,返回为matrix()
        3. matrix() 转换为以度为单位的 rotate
        4. 删除现有动画
        5. 根据当前速度和旋转值创建新动画
        6. 将新动画应用到元素

        我们需要克服的主要问题是根据当前旋转值创建一个新的动画关键帧——我们需要创建一个新动画,其起始值等于当前旋转值,结束值等于当前旋转值值 + 360。

        也就是说,如果我们的元素旋转90deg并且速度发生变化,我们需要创建一个新的@keyframes

        @keyframes updatedKeyframes {
          0% {
            transform: rotate(90deg);
          },
          100% {
            transform: rotate(450deg); // 90 + 360 
          }
        }
        

        为了创建动态动画,我使用jQuery.keyframes 插件来创建动态@keyframes

        虽然此解决方案有效,但基于 jQuery.keyframes 插件的工作原理,我不认为它具有超级性能。对于每个新的动画关键帧,插件都会附加一个内联 &lt;style&gt;。这可能导致定义数十、数百甚至数千个keyframes。在下面的示例中,我使用speed 变量来创建@keyframe 名称,因此它将创建多达100 个唯一的@keyframes 样式。我们可以在此处进行一些优化,但这超出了此解决方案的范围。

        下面是工作示例:

        $(document).on('input', '#slider', function() {
          var speed = $(this).val();
          $('#speed').html(speed);
          var transform = $("#loading").css('transform');
        
          var angle = getRotationDegrees(transform);
          
          $("#loading").css({
            "animation": "none"
          });
          
          $.keyframe.define([{
            name: `rotateRight_${speed}`,
            "0%": {
              "transform": "rotate(" + angle + "deg)"
            },
            "100%": {
              'transform': "rotate(" + (angle + 360) + "deg)"
            }
          }]);
          
          if (speed === "0") {
            $("#loading").css("transform", "rotate(" + angle + "deg)");
          } else {
            $("#loading").playKeyframe({
              name: `rotateRight_${speed}`,
              duration: 50 / speed + "s",
              timingFunction: "linear",
              iterationCount: "infinite"
            });
          }
        
        });
        
        function getRotationDegrees(matrix) {
          if (matrix !== 'none') {
            var values = matrix.split('(')[1].split(')')[0].split(',');
            var a = values[0];
            var b = values[1];
            var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
          } else {
            var angle = 0;
          }
          return angle;
        }
        #loading {
          position: absolute;
          left: 0;
          right: 0;
          margin: auto;
          transform-origin: 50% 50%;
        }
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://rawgit.com/jQueryKeyframes/jQuery.Keyframes/master/jquery.keyframes.js"></script>
        <img id="loading" src="http://via.placeholder.com/100x100">
        <input type="range" min="0" max="100" value="0" class="slider" id="slider">
        <p>Speed: <span id="speed"></span></p>

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-15
          • 1970-01-01
          • 2012-01-25
          相关资源
          最近更新 更多