1

This is a follow on from another question. I was asked to make a new question instead of discussing this in the comments, so that others could benefit from it's solution.

The problem:

I have some progressbar.js circles that animate when scrolled into view.

They are animating as expected, but I need the circles that are fully visible on page load to animate immediately, instead of waiting for a scroll.

Other circles that are not fully visible on page load should animate once they are scrolled into view, as they do now.

Here is my current code:

//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);
  });
}
#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>

I have also tried this alternate JS without success:

It encapsulates the visibility checker lines to a separate function and runs when creating the circles.

But it has the same problem as the original code, the circles that are visible on page load do not run immediately.

//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');

  function visibilityChecker(bar) {
    jQuery(window).scroll(function() {
      if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
    });
  }
  visibilityChecker(bar);

}
TinyTiger
  • 1,801
  • 7
  • 47
  • 92
  • Please, check my answer too regarding **extracting and separating your code into its own function to reduce code repetition**. [***DRY principle***](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). – user7637745 Jun 27 '18 at 09:16

2 Answers2

2

Call the animation function outside the scroll event

//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');
  if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);//add this
  //Animate the circle when scrolled into view
  $(window).scroll(function () {
    if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
  });
}
#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>
madalinivascu
  • 32,064
  • 4
  • 39
  • 55
1

Quite there.


When checking and updating an element during a scroll event, you're gonna face situations, where the checker/updater code doesn't run, because no scroll event has happened.

To fix this issue in the future, extract the code you are using into a separate function, in this case:

// Checks whether an element is visible and updates it accordingly
function checkVisibility(elem, bar, countvalue) {
    if (isScrolledIntoView(elem)) {
        bar.animate(countvalue);
    } else {
        bar.animate(0);
    }
}

Then you can run the code during initialization and also during a scroll event. More on the DRY principle.


Also

I refactored your code a bit and stored the container elements to reduce calling jQuery(...) as much:

// Select and store the element
var elem = jQuery('#' + divid);

This way, your code won't run jQuery(...) unnecessarily and you can use the elem variable too, when initializing the circle:

var bar = new ProgressBar.Circle(elem.get(0), {

More on the .get() method.


Working example

//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));
}

// Checks whether an element is visible and updates it accordingly
function checkVisibility(elem, bar, countvalue) {
  if (isScrolledIntoView(elem)) {
    bar.animate(countvalue);
  } else {
    bar.animate(0);
  }
}

//Circle design and animation
function bgCircles(divid, countvalue) {
  // Select and store the element
  var elem = jQuery('#' + divid);
  
  // Design the circle using progressbar.js
  var bar = new ProgressBar.Circle(elem.get(0), {
    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');

  // Check element visibility and update it, when needed
  checkVisibility(elem, bar, countvalue);

  // Animate the circle when scrolled into view
  $(window).scroll(function () {
    checkVisibility(elem, bar, countvalue);
  });
}
#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>
user7637745
  • 965
  • 2
  • 14
  • 27
  • Strangely, if you refresh the page while at the bottom of the page and then scroll upwards after the page has reloaded, everything works fine. – TinyTiger Jun 27 '18 at 09:41
  • It works for me (all circles in the category score element animate). If they don't animate to you, please empty your browser's cache. [**Check this answer**](https://stackoverflow.com/questions/50802820/my-css-doesnt-change-why/50803068#50803068), although it's about CSS, you can apply it in your situation too. – user7637745 Jun 27 '18 at 09:42
  • If your circles are animated to their percents too early, check your `isScrolledIntoView(elem)` function (use `console.log(...)` to log out the particular values in the function), whether it gives you the correct results. – user7637745 Jun 27 '18 at 09:52
  • I have cleared caches and change JS file name for cache busting purposes etc. Still same problem. Strange you cannot see it happening on your end. I think the category circles (half way down the page) are animating on page load when they should be only be animating on scroll. I removed the "de-animation" effect to make it more obvious, once the circles have animated they will stay that way now. – TinyTiger Jun 27 '18 at 09:59
  • @TinyTiger what browser do you use? – madalinivascu Jun 27 '18 at 10:02
  • @madalinivascu Tested in chrome and firefox. Both with cleared caches and history. Both same problem. – TinyTiger Jun 27 '18 at 10:03
  • 1
    I'm going to award you the answer because it is the clearest answer and solves what was asked on here, even though both answers do not work on my live dev site for some reason. – TinyTiger Jun 28 '18 at 02:11