Javascript SetTimeout and Loops [duplicate] - javascript

This question already has answers here:
How do I add a delay in a JavaScript loop?
(32 answers)
Closed 9 years ago.
Experts.
Javascript not producing desired delay effect.
From other questions, on SO I got to know that, problem is with settimeout and the way I am using it.
But still I am not able to comprehend, how Settimeout works.
So I am putting code here.
Need to use Javascript only, because of knowledge purpose.
Actually I am trying to clear my concepts about this, closure in javascript.
Are they kind of twisted things of Javascript?
var objImg = new Object();
var h;
var w;
var no = 100;
while (no != 500) {
setTimeout(function () {
size(no, no);
}, 2000);
/* it's get executed once, instead of repeating with while loop
Does it leave loop in mid? I get image with 500px height and
width, but effect is not acheived.
*/
no = no + 50;
}
function size(h, w) {
var objImg = document.getElementsByName('ford').item(0);
objImg.style.height = h + 'px';
objImg.style.width = w + 'px';
}

You have two problems :
no will have the value of end of loop when the callback is called
you're programming all your timeouts 2000 ms from the same time, the time the loop run.
Here's how you could fix that :
var t = 0
while (no != 500) {
(function(no) {
t += 2000;
setTimeout(function() { size(no,no);} ,t);
})(no);
no = no+50; // could be written no += 50
}
The immediately executed function creates a scope which protects the value of no.
A little explanation about (function(no) { :
The scope of a variable is either
the global scope
a function
The code above could have been written as
var t = 0
while (no != 500) {
(function(no2) {
t += 2000;
setTimeout(function() { size(no2,no2);} ,t);
})(no);
no += 50;
}
Here it's probably more clear that we have two variables :
no, whose value changes with each iteration and is 500 when the timeouts are called
no2, in fact one variable no2 per call of the inner anonymous function
Each time the inner anonymous function is called, it declares a new no2 variable, whose value is no at the time of call (during iteration). This variable no2 is thus protected and is used by the callback given to setTimeout.

Why not just use setInterval() instead?
var objImg = new Object();
var h;
var w;
var no = 100;
var myInterval = window.setInterval(function() {
size(no, no);
no = no + 50;
if (no >= 500) clearInterval(myInterval);
}, 2000);
function size(h, w) {
var objImg = document.getElementsByName('ford').item(0);
objImg.style.height = h + 'px';
objImg.style.width = w + 'px';
}

Your problem is with your size() function syntax & algorithm:
var objImg = new Object();
var h;
var w;
var no = 100;
var int = window.setInterval(function () {
size(no,no);
no += 50;
},2000)
function size(h, w) {
if (h == 500){
window.clearInterval(int);
return;
}
var height = h + 'px';
var width = w + 'px';
document.getElementsByName('ford').item(0).style.height = height;
document.getElementsByName('ford').item(0).style.width = width;
}
http://jsfiddle.net/AQtNY/2/

Related

Why is difference between date() is NaN? [duplicate]

This question already has answers here:
What does var mean in Javascript? [closed]
(2 answers)
Closed 2 years ago.
<script>
var start;
function shapeAppear() {
document.getElementById("shapes").style.display = "block";
var start = new Date().getTime();
}
function delay() {
setTimeout(shapeAppear,Math.random() *2000);
}
delay();
document.getElementById("shapes").onclick = function() {
document.getElementById("shapes").style.display = "none";
var position = Math.floor(Math.random() * 500);
document.getElementById("shapes").style.left = position + "px";
document.getElementById("shapes").style.right = position + "px";
document.getElementById("shapes").style.top = position + "px";
document.getElementById("shapes").style.bottom = position + "px";
var end = new Date().getTime();
var time = end - start;
time /= 1000;
document.getElementById("time").innerHTML = time + "s";
delay();
}
</script>
Here in this code i want the date() function to return a specific integer value.
Because when we subtract the two Date() functions we must get the integer value.
It is a scoping issue. if you use var inside a function, that variable will only exist in the scope of that function.
So what you could do is this:
var start;
function shapeAppear() {
start = new Date().getTime();
}
By removing var in the shapeAppear function, you're updating the var that is created outside the function.
Besides that as Rodney mentioned you call delay before shapeAppear which means that start is not defined when calling delay.
Hope that makes sense.

JS dynamic change divs width in loop

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
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
}
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 :-)
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
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.

How to use requestAnimationFrame inside a Class object

I have a class that takes some coordinate and duration data. I want to use it to animate an svg. In more explicit terms, I want to use that data to change svg attributes over a time frame.
I'm using a step function and requestAnimationFrame outside the class:
function step(timestamp) {
if (!start) start = timestamp
var progress = timestamp - start;
var currentX = parseInt(document.querySelector('#start').getAttribute('cx'));
var moveX = distancePerFrame(circleMove.totalFrames(), circleMove.xLine);
document.querySelector('#start').setAttribute('cx', currentX + moveX);
if (progress < circleMove.duration) {
window.requestAnimationFrame(step);
}
}
var circleMove = new SingleLineAnimation(3000, startXY, endXY)
var start = null
function runProgram() {
window.requestAnimationFrame(step);
}
I can make it a method, replacing the circleLine with this. That works fine for the first run through, but when it calls the this.step callback a second time, well, we're in a callback black hole and the reference to this is broken. Doing the old self = this won't work either, once we jump into the callback this is undefined(I'm not sure why). Here it is as a method:
step(timestamp) {
var self = this;
if (!start) start = timestamp
var progress = timestamp - start;
var currentX = parseInt(document.querySelector('#start').getAttribute('cx'));
var moveX = distancePerFrame(self.totalFrames(), self.xLine);
document.querySelector('#start').setAttribute('cx', currentX + moveX);
if (progress < self.duration) {
window.requestAnimationFrame(self.step);
}
}
Any ideas on how to keep the "wiring" inside the Object?
Here's the code that more or less works with the step function defined outside the class.
class SingleLineAnimation {
constructor(duration, startXY, endXY) {
this.duration = duration;
this.xLine = [ startXY[0], endXY[0] ];
this.yLine = [ startXY[1], endXY[1] ];
}
totalFrames(framerate = 60) { // Default to 60htz ie, 60 frames per second
return Math.floor(this.duration * framerate / 1000);
}
frame(progress) {
return this.totalFrames() - Math.floor((this.duration - progress) / 17 );
}
}
This will also be inserted into the Class, for now it's just a helper function:
function distancePerFrame(totalFrames, startEndPoints) {
return totalFrames > 0 ? Math.floor(Math.abs(startEndPoints[0] - startEndPoints[1]) / totalFrames) : 0;
}
And click a button to...
function runProgram() {
window.requestAnimationFrame(step);
}
You need to bind the requestAnimationFrame callback function to a context. The canonical way of doing this is like this:
window.requestAnimationFrame(this.step.bind(this))
but it's not ideal because you're repeatedly calling .bind and creating a new function reference over and over, once per frame.
If you had a locally scoped variable set to this.step.bind(this) you could pass that and avoid that continual rebinding.
An alternative is this:
function animate() {
var start = performance.now();
el = document.querySelector('#start');
// use var self = this if you need to refer to `this` inside `frame()`
function frame(timestamp) {
var progress = timestamp - start;
var currentX = parseInt(el.getAttribute('cx'));
var moveX = distancePerFrame(circleMove.totalFrames(), circleMove.xLine);
el.setAttribute('cx', currentX + moveX);
if (progress < circleMove.duration) {
window.requestAnimationFrame(frame);
}
}
window.requestAnimationFrame(frame);
}
i.e. you're setting up the initial state, and then doing the animation within a purely locally scoped function that's called pseudo-recursively by requestAnimationFrame.
NB: either version of the code will interact badly if you inadvertently call another function that initiates an animation at the same time.

Timed loop, 10 second between

I currently got this:
var xnumLow = 3000;
var xnumHigh = 4900;
var ynumLow = 9969;
var ynumHigh = 13900;
var ts = Math.round((new Date()).getTime() / 1000);
for (y=ynumLow; y<ynumLow; y++)
{
for(x=xnumLow; x<xnumHigh; x++)
{
$('#box').append(y + " - " + x);
}
}
Now I would like it to append new whole y "row" every 10 seconds, so they all dont append all in once.
The y "row" is the outer for() loop
How can I do this?
I got:
var refreshId = setInterval(function(){ (...) }, 10000);
But I don't know where to merge this with the above code, in order to work correct.
(function () {
var xnumLow = 3000,
xnumHigh = 4900,
ynumLow = 9969,
ynumHigh = 13900,
currentY = ynumLow,
delay = 500,
displayData = function () {
var out = [],
x;
for (x=xnumLow; x<xnumHigh; x++) {
out.push( currentY + "-" + x );
}
console.log(out.join(",")); //do the append here
currentY++;
if (currentY<ynumHigh) {
window.setTimeout(displayData,delay);
}
};
displayData()
})();
setInterval(function () {
// code that appends a box
}, 10000);
https://developer.mozilla.org/en-US/docs/DOM/window.setInterval
var y = ynumLow;
function addRow()
{
for (x = xnumLow; x < xnumHigh; x++) {
$('#box').append(y + " - " + x);
}
if (y++ < ynumHigh)
refreshId = setTimeout(addRow, 10000);
}
addRow();
edited as Pete suggested for clarity
I would do it something like this:
var xnumLow = 3000;
var xnumHigh = 4900;
var ynumLow = 9969;
var ynumHigh = 13900;
var x, y = ynumLow; //don't forget to declare your variables!
var ts = Math.round((new Date()).getTime() / 1000);
(function addYRow() { //Create a function that adds the X elements
for(x=xnumLow; x<xnumHigh; x++)
{
$('#box').append(y + " - " + x);
}
y++; //don't forget to increment y
if(y < ynumHigh) { //only re-call if we aren't done yet
setTimeout(addYRow, 10000); //Recall the function every 10 seconds.
}
}());
Looking at some of the other answers, it's important to realize that you don't want to set up a bunch of things to happen 10 seconds from a given point (which is what happens if you do a loop calling setTimeout(). Instead, I assume you want to add a row, then wait 10 seconds, then add another row. This can only be achieved by adding a row (usiny, in my case, the addYRow() function), then delaying 10 seconds before re-calling the add-a-row function.
Column Delay:
In response to the question about how to do a 500ms delay in the x row, that's a little tricky, but not too bad. You just have to nest things one more time:
var y = ynumLow; //don't forget to declare your variables!
var ts = Math.round((new Date()).getTime() / 1000);
(function addYRow() { //Create a function that adds the X elements
var x = xnumLow;
(function addXCol() { //Create a function that adds each X element
$('#box').append(y + " - " + x);
x++;
if(x < xnumHigh) { //if x is not done, call addXCol 500ms later
setTimeout(addXCol, 500);
} else {
y++;
if(y < ynumHigh) { //If x is done but y isn't, call addYRow 10 seconds later
setTimeout(addYRow, 10000); //Recall the function every 10 seconds.
}
}
}());
}());
Note that if you want to delay the start of the column/row addition (e.g., if you want to put a 500ms delay between when a row is added and when the first column is added, you'll need to adjust the addXCol() expression creation to look like this:
setTimeout(function addXCol() { //Create a function that adds each X element
//...
}, 500);
This will put that initial delay in. Hope that helps.
Something like this?
var refreshId = setInterval(function(){
$('#box').append(++y + " - " + x);
}, 10000);

Incorrect usage of setTimeout?

I've written a script that fires off 2 URLs based on some random number logic and I'm trying to set a delay before either one is fired (of half a second) but I don't think it's working properly. Am I doing this correctly? Code is below:
var clicks = "http://www.urlone.com";
var impressions = "http://www.urltwo.com";
var randomNumber = (Math.random()*100);
function callOut() {
for (var i = 0; i < lengthVal; i++){
if (randomNumber < 75) {
var randomCounter = (Math.random()*100);
if (randomCounter < 50) {
setTimeout("image1.src = clicks;",500);
}
else if (randomCounter > 50) {
setTimeout("image1.src = impressions;",500);
}
}
}
}
setTimeout first parameter should be a function. Not string of code.
code in the alternate syntax, is a string of code you want to execute after delay milliseconds. (Using this syntax is not recommended for the same reasons as using eval())
MDN
setTimeout(function(){...}, 500);
Taken from here: http://www.codescream.com/?p=18 read it it should help :)
If you want to make a delay with setTimeout you should do exactly this:
setTimeout( function () {
doThings()
}, 1000);
and never this:
setTimeout( "doThings()", 1000);
setTimeout("image1.src = clicks;",500);
For this image1 must be declared in a global context, like this:
var image1 = document.getElementById('image1');
But you better use a function here.
function setImageSrcClicks(){
document.getElementById('image1').src = 'http://clicks_url';
}
setTimeout(setImageSrcClicks,500);
Duly noted about using setTimeout with a string. Here is how I ended up doing it. Is this the 'best' way to do this?
var clicks = "http://www.urlone.com";
var impressions = "http://www.urltwo.com";
var conversions = "http://www.urlthree";
var lengthVal = (Math.random() * 20 + 20);
var image1 = new Image();
var image2 = new Image();
var globalCounter = -1;
function callOut() {
var ord = (Math.random() * 9999999999999) + "";
var randomNumber = (Math.random() * 100); // Random value for each call
if (randomNumber < 75) {
var randomCounter = Math.random() * 100;
alert(randomCounter);
if (randomCounter < 50) {
image1.src = clicks + ord + "?";
}
if (randomCounter > 50) {
image2.src = impressions + ord + "?";
}
}
if (globalCounter++ < lengthVal) {
setTimeout(callOut, 1000); // Call itself after another second
}
}

Categories