Reputation: 81
I want to create simple bar chart with animated bars (width from 0 to final size).
I started from css transitions but opera and chrome sometimes have problems.
Now I try to use mechanism what I saw here:
https://www.w3schools.com/w3css/w3css_progressbar.asp
using JS.
I have few "bar-areas":
<div id="chart-bars">
<div class="chart-bar-area">
<div class="chart-bar" data-percent="80" ></div>
</div>
<div class="chart-bar-area">
<div class="chart-bar" data-percent="60" ></div>
</div>
</div>
and JS code which works fine with one bar, but if I try to implement that mechanism for all in loop this script works correctly only for last one bar. Other bars not grooving.
My JS code below:
var foo = document.getElementById('chart-bars');
var width;
var elem;
var id;
for (var i = 0; i < foo.children.length; i++) {
elem = foo.children[i].children[0];
width = 1;
id = setInterval(frame, 10);
function frame() {
if (width >= elem.dataset.percent) {
clearInterval(id);
} else {
width++;
elem.style.width = width + '%';
}
}
}
Can somebody help me ?
Thanks
Upvotes: 2
Views: 1496
Reputation: 718
Congratulations! Today will learn what a "closure" is.
You are experiencing a scoping issue. The reason this only works for the last bar is because the frame
function only sees elem
as it currently exists on scope. That is to say, by the time your setInterval
runs your loop will have ended and elem
will be firmly set to what amounts to foo.children[foo.children.length - 1].children[0]
The way to fix this is by creating a new closure. That is to say, you "close" the variable in a scope.
var foo = document.getElementById('chart-bars');
for (var i = 0; i < foo.children.length; i++) {
(function (elem, width) {
var id = setInterval(frame, 10);
function frame() {
if (width >= elem.dataset.percent) {
clearInterval(id);
} else {
width++;
elem.style.width = width + '%';
}
}
})(foo.children[i].children[0], 1)
}
now this, specifically, is a far from perfect way of accomplishing your goal, but I wrote it this way in order to preserve as much of your code as possible in an attempt to reduce the cognitive load for your specific example.
Essentially what I am doing is wrapping your code in an IIFE (Immediately Invoked Function Expression), which creates a new scope with your elem
and width
variables fixed to that function's scope.
A further step in the right direction would be to pull your frame
function outside of the loop and have it only created once and have it accept as its arguments an elem
, width
, and id
parameter, but I'll leave that as an exercise for the reader :-)
Upvotes: 1
Reputation: 567
Looks like a variable scope issue, could you try something like this :
var foo = document.getElementById('chart-bars');
for (var i = 0; i < foo.children.length; i++) {
var elem = foo.children[i].children[0];
elem.style.width = '1%';
var id = setInterval(function() { frame(elem, id) }, 10);
}
function frame(elem, idInterval) {
var width = parseInt(elem.style.width);
if (width >= elem.dataset.percent) {
clearInterval(idInterval);
} else {
width++;
elem.style.width = width + '%';
}
}
Basically before you were erasing at each loop your previous elem, width, and id variables because they were declared outside the for loop. Hence the weird behavior.
Edit : putting the frame function outside so that it's clearer.
Edit2 : removing width from frame function as it is not needed.
Upvotes: 0
Reputation: 753
This is because value of i
is out of scope of setInterval callback, setInterval takes only last value of iteration as it is a window
method. So, you have to go with the closure or solution like this.
var foo = document.querySelectorAll(".chart-bar");
var width;
var elem;
var id;
for (var i = 0; i < foo.length; i++) {
var myElem = {
x: i
}
width = 1;
id = setInterval(function() {
if (width >= foo[this.x].dataset.percent) {
clearInterval(id);
} else {
width++;
foo[this.x].style.width = width + '%';
}
}.bind(myElem), 10);//Here you are binding that setInterval should run on the myElem object context not window context
}
Upvotes: 1
Reputation: 327
why don't you use the specific jquery lybraries to create the charts like morrischart or chartjs, there are very simple and they work very well there are the documentations http://morrisjs.github.io/morris.js/ https://www.chartjs.org
Upvotes: 0