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

When looping through animations, only the last loop runs

这是 的跟进。

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

下面是我尝试实现循环的代码,但问题是滚动时只有最后一个圆圈是动画的。所有圆圈都显示为 "pre-animation" 状态,但只有最后一个圆圈在您滚动到底部时实际变为动画。

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

//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 只会输出循环的最后一个值,我认为这可能是我的问题的原因。

所以我尝试用 these solutions...

替换 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]);
      }
    }
  }

所以现在我想问题可能不是循环,而是我看不到或想不通的东西。

您的循环 运行 如此之快以至于浏览器引擎无法呈现更改,我建议您使用 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 在每次调用中都被覆盖,这就是为什么只有最后一个圆圈响应。

你们很接近。

修复如下:


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>


注:

因为我没有编辑你的任何圈子animation/circle可见性检查功能,我假设你想要你的animate-的当前状态 when-scrolled-and-in-view 功能方式,就是现在。在此当前状态下,您的脚本 does/has 具有以下副作用

  • 如果您不滚动页面(根本不滚动),您的圈子将不会开始动画,即使当它们可见时也是如此。 解决方案:在创建圆圈时将可见性检查器线封装到一个单独的函数和运行。

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


关于性能和最佳实践:

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

  • 最好将 longer/larger、 整体功能拆分成更小的功能,功能范围更窄,但功能范围也更清晰。

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

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

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

  2. 使用 window.addEventListener 将滚动事件附加到 window,通过使用 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>