循环播放动画时,只运行最后一个循环

Posted

技术标签:

【中文标题】循环播放动画时,只运行最后一个循环【英文标题】:When looping through animations, only the last loop runs 【发布时间】:2018-11-26 09:15:26 【问题描述】:

这是my previous question 的后续报道。

我有一个滚动动画的progressbar.js 圆圈。如果只有一个圆圈,它会按预期工作。

现在我想通过循环使用具有不同键值对的对象来创建许多这样的动画圆圈。

例如:

  var divsValues = 
    'total-score-circle': 0.75, 
    'general-score-circle': 0.80, 
    'speed-score-circle': 0.85, 
    'privacy-score-circle': 0.90,
  ;

对于每个键值对,键是一个 div ID,值是告诉动画要走多远的数字。

下面是我尝试实现循环的代码,但问题是只有最后一个圆圈在滚动时动画。所有圆圈都以“动画前”状态显示,但只有最后一个圆圈在您滚动到底部时真正变为动画。

我需要每个圆圈在视口中设置动画。

//Loop through my divs and create animated circle for each one
function makeCircles() 
  var divsValues = 
    'total-score-circle': 0.75,
    'general-score-circle': 0.80,
    'speed-score-circle': 0.85,
    'privacy-score-circle': 0.90,
  ;

  for (var i in divsValues) 
    if (divsValues.hasOwnProperty(i)) 
      bgCircles(i, divsValues[i]);
    
  

makeCircles();

// Check if element is scrolled into view
function isScrolledIntoView(elem) 
  var docViewTop = jQuery(window).scrollTop();
  var docViewBottom = docViewTop + jQuery(window).height();
  var elemTop = jQuery(elem).offset().top;
  var elemBottom = elemTop + jQuery(elem).height();

  return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));


//Circle design and animation
function bgCircles(divid, countvalue) 
  // Design the circle using progressbar.js
  bar = new ProgressBar.Circle(document.getElementById(divid), 
    color: '#ddd',
    // This has to be the same size as the maximum width to
    // prevent clipping
    strokeWidth: 4,
    trailWidth: 4,
    easing: 'easeInOut',
    duration: 1400,
    text: 
      autoStyleContainer: false
    ,
    from: 
      color: '#ddd',
      width: 4
    ,
    to: 
      color: '#888',
      width: 4
    ,
    // Set default step function for all animate calls
    step: function(state, circle) 
      circle.path.setAttribute('stroke', state.color);
      circle.path.setAttribute('stroke-width', state.width);

      var value = Math.round(circle.value() * 100);
      if (value === 0) 
        circle.setText('');
       else 
        circle.setText(value + '%');
      
    
  );
  bar.text.style.fontFamily = '"Montserrat", sans-serif';
  bar.text.style.fontSize = '1.7rem';
  bar.trail.setAttribute('stroke-linecap', 'round');
  bar.path.setAttribute('stroke-linecap', 'round');

  //Animate the circle when scrolled into view
  window.onscroll = function() 
    if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
    else bar.animate(0); // or bar.set(0)
  
#total-score-circle,
#general-score-circle,
#speed-score-circle,
#privacy-score-circle 
  margin: 0.8em auto;
  width: 100px;
  height: 100px;
  position: relative;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/progressbar.js/1.0.1/progressbar.min.js"></script>

<div id="total-score-circle"></div>
<div id="general-score-circle"></div>
<div id="speed-score-circle"></div>
<div id="privacy-score-circle"></div>

在研究这个问题时,我了解到 javascript 只会输出循环的最后一个值,我认为这可能是我的问题的原因。

所以我尝试将 for 循环替换为 these solutions...

解决方案 1: 与之前相同的问题,只有最后一个循环在滚动时出现动画。

  for (var i in divsValues) 
    (function()
      var ii = i;
        if (divsValues.hasOwnProperty(ii)) 
          bgCircles(ii, divsValues[ii]);
        
    )();        
  

解决方案 2: 同样,问题与之前相同,只有最后一个循环在滚动时产生动画。

  for (var i in divsValues) 
    let ii = i;
      if (divsValues.hasOwnProperty(ii)) 
        bgCircles(ii, divsValues[ii]);
      
  

解决方案 3: 同样,问题与之前相同,只有最后一个循环在滚动时出现动画。

  for (var i in divsValues) 
    trythrow i
    catch(ii) 
      if (divsValues.hasOwnProperty(ii)) 
        bgCircles(ii, divsValues[ii]);
      
    
  

所以现在我在想问题可能不在于循环,而是我看不到或弄清楚的问题。

【问题讨论】:

【参考方案1】:

你已经很接近了。

以下是修复:


function bgCircles(...)中,使用var在function's scope中声明bar

var bar = new ProgressBar.Circle(document.getElementById(divid), 

当您设置动画滚动到视图中检查器事件时,您将一个新函数反复分配给window.onscroll。由于您使用的是 jQuery,请考虑 jQuery's .scroll 事件处理程序并像这样使用它:

$(window).scroll(function () 
    if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
    else bar.animate(0); // or bar.set(0)
);

整体解决方案:

//Loop through my divs and create animated circle for each one
function makeCircles() 
  var divsValues = 
    'total-score-circle': 0.75,
    'general-score-circle': 0.80,
    'speed-score-circle': 0.85,
    'privacy-score-circle': 0.90,
  ;

  for (var i in divsValues) 
    if (divsValues.hasOwnProperty(i)) 
      bgCircles(i, divsValues[i]);
    
  

makeCircles();

// Check if element is scrolled into view
function isScrolledIntoView(elem) 
  var docViewTop = jQuery(window).scrollTop();
  var docViewBottom = docViewTop + jQuery(window).height();
  var elemTop = jQuery(elem).offset().top;
  var elemBottom = elemTop + jQuery(elem).height();

  return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));


//Circle design and animation
function bgCircles(divid, countvalue) 
  // Design the circle using progressbar.js
  var bar = new ProgressBar.Circle(document.getElementById(divid), 
    color: '#ddd',
    // This has to be the same size as the maximum width to
    // prevent clipping
    strokeWidth: 4,
    trailWidth: 4,
    easing: 'easeInOut',
    duration: 1400,
    text: 
      autoStyleContainer: false
    ,
    from: 
      color: '#ddd',
      width: 4
    ,
    to: 
      color: '#888',
      width: 4
    ,
    // Set default step function for all animate calls
    step: function(state, circle) 
      circle.path.setAttribute('stroke', state.color);
      circle.path.setAttribute('stroke-width', state.width);

      var value = Math.round(circle.value() * 100);
      if (value === 0) 
        circle.setText('');
       else 
        circle.setText(value + '%');
      
    
  );
  bar.text.style.fontFamily = '"Montserrat", sans-serif';
  bar.text.style.fontSize = '1.7rem';
  bar.trail.setAttribute('stroke-linecap', 'round');
  bar.path.setAttribute('stroke-linecap', 'round');

  //Animate the circle when scrolled into view
  $(window).scroll(function () 
    if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
    else bar.animate(0); // or bar.set(0)
  );
#total-score-circle,
#general-score-circle,
#speed-score-circle,
#privacy-score-circle 
  margin: 0.8em auto;
  width: 100px;
  height: 100px;
  position: relative;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/progressbar.js/1.0.1/progressbar.min.js"></script>

<div id="total-score-circle"></div>
<div id="general-score-circle"></div>
<div id="speed-score-circle"></div>
<div id="privacy-score-circle"></div>

注意:

由于我没有编辑您的任何圆形动画/圆形可见性检查功能,我假设您打算使用 animate-when-scrolled-and-in-view 的当前状态 功能的方式,它是现在。在当前状态下,您的脚本有/有以下副作用

如果您根本不滚动页面,您的圈子将不会开始动画,即使它们可见。 解决方案:将可见性检查线封装到单独的函数中并在创建圆圈时运行。

如果您滚动一个圆圈,其百分比动画将进入其默认状态,即 0%。 解决方案:更改可见性检查器功能,当特定元素由于过度滚动而不可见时,也将该状态返回为可见。这样,您的圈子将保持在 100%,即使您滚动它们。


关于性能和最佳实践:

使用 jQuery 时,请确保尽可能少地调用 jQuery(...) 或其简写形式 $(...)使用变量来存储元素、属性和数据。

最好将更长/更大的单一功能分成更小的功能,功能范围更窄,但功能范围更清晰。

使用事件侦听器时,请确保尽可能少地运行它们。构建您的 html 和 JavaScript 代码,以便以清晰和高效的方式访问和修改您的关键元素、属性和数据

【讨论】:

非常感谢!我试图摆脱你提到的第一个副作用......如果页面加载时可以看到圆圈,我希望它立即动画,而不是等到有滚动。当滚动到视图中时,onload 视口之外的圆圈应该是动画的。我尝试了封装可见性检查器并在创建圆圈时运行该功能的解决方案,但是在页面加载时可见的圆圈仍然需要在动画之前滚动。在这里看小提琴:jsfiddle.net/z51h89wy/17 不客气!我明白了,您需要进行轻微的重构,一切都会好起来的。如果您无法按照您想要的方式解决问题,请创建一个结构良好且措辞清晰的问题。这样,其他人将在未来看到完整的解决方案。 ;)【参考方案2】:

您拥有的循环运行速度如此之快,以至于浏览器引擎将无法呈现更改,我建议您使用setInterval() 方法或连续的setTimeout() 方法,这会给您的代码增加一些延迟,以便浏览器可以呈现您所做的更改。

对于您的特殊情况,我建议:

var i = 0;
var tobecleared = setInterval(timer,1000);

function timer()
    var p = get_ith_key_from_divsvalues(i);//implement this method
    console.log(p);
    bgCircles(p, divsValues[p]);
    i++;
    if(i == Object.keys(divsValues).length)
         clearInterval(tobecleared);

function get_ith_key_from_divsvalues(i)
     var j = -1;
     for(var property in divsValues)
          j++;
          if(j==i)
                return property;
     

注意:window.onscroll 在每次调用中都会被覆盖,这就是为什么只有最后一个圈子响应的原因。

【讨论】:

感谢您的提示。您能否详细说明implement this method 的含义?我才刚开始学习 JS,我不确定我应该用那个函数做什么。 函数 get_ith_key_from_divsvalues(int i) var j = 0; for(divsValues 中的 var 属性) if(j==i) 返回属性; j++; 您是说将 divsValues 对象从函数中拉出并使用低于它的延迟吗?我在这里累了但它不起作用:jsfiddle.net/yvo1rpk9 调试是一件很难的事情我的朋友,我建议打印所有可疑的值,看看它缺少哪里,我也在尝试。 我调试了我的代码,有一些错别字。我评论了您的函数调用并在我的终端中运行代码,它以预期的方式输出。【参考方案3】:

您需要应用两个修复程序。

    目前bar是全局变量,所以它总是一样的,修复它用var声明它。

    使用 window.addEventListener 将滚动事件附加到窗口,通过使用 window.onscroll 设置处理程序,您可以不断覆盖事件处理程序,并且使用 addEventListener 允许您附加多个事件处理程序。

//Loop through my divs and create animated circle for each one
$( document ).ready(function()
function makeCircles() 
  var divsValues = 
    'total-score-circle': 0.75,
    'general-score-circle': 0.80,
    'speed-score-circle': 0.85,
    'privacy-score-circle': 0.90,
  ;

  for (var i in divsValues) 
    if (divsValues.hasOwnProperty(i)) 
      bgCircles(i, divsValues[i]);
    
  

makeCircles();

// Check if element is scrolled into view
function isScrolledIntoView(elem) 
  var docViewTop = jQuery(window).scrollTop();
  var docViewBottom = docViewTop + jQuery(window).height();
  var elemTop = jQuery(elem).offset().top;
  var elemBottom = elemTop + jQuery(elem).height();

  return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));


//Circle design and animation
function bgCircles(divid, countvalue) 
  // Design the circle using progressbar.js
  var bar = new ProgressBar.Circle(document.getElementById(divid), 
    color: '#ddd',
    // This has to be the same size as the maximum width to
    // prevent clipping
    strokeWidth: 4,
    trailWidth: 4,
    easing: 'easeInOut',
    duration: 1400,
    text: 
      autoStyleContainer: false
    ,
    from: 
      color: '#ddd',
      width: 4
    ,
    to: 
      color: '#888',
      width: 4
    ,
    // Set default step function for all animate calls
    step: function(state, circle) 
      circle.path.setAttribute('stroke', state.color);
      circle.path.setAttribute('stroke-width', state.width);

      var value = Math.round(circle.value() * 100);
      if (value === 0) 
        circle.setText('');
       else 
        circle.setText(value + '%');
      
    
  );
  bar.text.style.fontFamily = '"Montserrat", sans-serif';
  bar.text.style.fontSize = '1.7rem';
  bar.trail.setAttribute('stroke-linecap', 'round');
  bar.path.setAttribute('stroke-linecap', 'round');

  //Animate the circle when scrolled into view
  window.addEventListener('scroll', function() 
    if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
    else bar.animate(0); // or bar.set(0)
  )

)
#total-score-circle,
#general-score-circle,
#speed-score-circle,
#privacy-score-circle 
  margin: 0.8em auto;
  width: 100px;
  height: 100px;
  position: relative;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/progressbar.js/1.0.1/progressbar.min.js"></script>

<div id="total-score-circle"></div>
<div id="general-score-circle"></div>
<div id="speed-score-circle"></div>
<div id="privacy-score-circle"></div>

【讨论】:

以上是关于循环播放动画时,只运行最后一个循环的主要内容,如果未能解决你的问题,请参考以下文章

cocos creator typescript键盘控制动画播放,要按下一个不相关的按键才能循环播放

请问,AE中如何让一个合成循环播放呢?

Flash Pro CS6 SWF 动画不使用 Javascript 循环播放

按下按钮时循环播放声音

viewpage 循环滑动播放图片

egret的tween动画循环播放