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

11

这是对 我之前问题 的跟进。

我有一个 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,值是一个数字,告诉动画要走多远。

下面是我尝试实现循环的代码,但问题是< strong >只有最后一个圆在滚动时才会动画。所有圆都以其“预动画”状态出现,但只有最后一个圆在滚动到底部时才会真正变成动画。

我需要每个圆在视口中出现时都执行动画。

//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循环...

解决方案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) {
    try{throw i}
    catch(ii) {
      if (divsValues.hasOwnProperty(ii)) {
        bgCircles(ii, divsValues[ii]);
      }
    }
  }

所以现在我认为问题可能不在于循环,而是某些我看不到或者想不出来的东西。

3个回答

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,这就是为什么只有最后一个圆圈会响应的原因。


你是说将divsValues对象从函数中提取出来,然后在下面使用你的延迟吗?我在这里尝试了一下,但它不起作用:https://jsfiddle.net/yvo1rpk9/ - TinyTiger
调试是一件很难的事情,我的朋友,我建议打印所有怀疑的值并查看哪里有缺陷,我也在尝试。 - Anurag Kumar
我调试了我的代码,发现了一些拼写错误。我注释掉了你的函数调用,并在我的终端中运行了代码,输出结果符合预期。 - Anurag Kumar
我在你的jsfiddle代码中插入了我的调试代码并运行它。它会每秒钟输出4个半透明的圆形。 - Anurag Kumar
让我们在聊天中继续这个讨论 - TinyTiger
显示剩余3条评论

2

你离答案很接近。

以下是修复方法:


function bgCircles(...)中,使用var在该函数作用域内声明bar

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

当你设置 滚动到视图中的动画 检查器事件时,你会一遍又一遍地给 window.onscroll 分配一个新函数。由于你正在使用jQuery,请考虑使用jQuery的.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>


注意:

由于我没有编辑您的圆形动画/圆形可见性检查函数,因此我认为您目前的滚动并在视图中时执行动画功能是您想要的。在当前状态下,您的脚本具有以下副作用

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

  • 如果您滚动到圆形上,则其百分比动画将返回到默认状态,即0%。解决方案:更改可见性检查函数,当特定元素由于过度滚动而不可见时,将该状态返回为可见状态。这样,即使您滚动到它们上面,您的圆也将保持为100%。


关于性能和最佳实践:

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

  • 最好将更长/更大的单片函数分成更小的函数,具有更狭窄但更清晰的功能范围。

  • 使用事件侦听器时,请确保尽可能少地运行它们。结构化您的HTML和JavaScript代码以具有明确且高效的方式来访问和修改关键元素、属性和数据


1
非常感谢!我正在尝试消除您提到的第一个副作用...如果圆在页面加载时可见,我希望它立即进行动画处理,而不是等待滚动。而视口外的圆应在滚动到视图中时进行动画处理。我尝试了您的解决方案,将可见性检查器封装起来,并在创建圆时运行该函数,但是在页面加载时可见的圆仍然需要滚动才能进行动画处理。请参见此处的fiddle:http://jsfiddle.net/z51h89wy/17/ - TinyTiger
不客气!我明白了,您需要进行轻微的重构就可以解决问题了。如果您不能用想要的方式解决问题,请创建另一个问题,确保问题结构良好、清晰明了。这样做的话,未来其他人也能看到完整的解决方案。 ;) - user7637745

0

你需要应用两个修复措施。

  1. 目前 bar 是全局变量,因此它始终相同,要修复它,请使用 var 声明。

  2. 使用 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>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接