Javascript using a For Loop with setTimeout (or another delay) - javascript

I apparently don't fully understand how the setTimeout function works in Javascript:
function move() {
var a;
for(a = 0; a < 101; a++){
setTimeout(function(){
block.style.marginTop = (750 - a) + 'px');
}, 1000);
}
for(a = 0; a < 101; a++){
setTimeout(function(){
block.style.marginTop = (650 + a) + 'px');
}, 1000);
}
}
I have tried writing this out in many different ways, but the For Loops always execute instantly every single time. How can I make a For Loop wait for 1 second between each value of 'a'? So, when a = 0, the code executes and then waits for 1 second before running when a = 1, etc. until the first For Loop is finished, then the second For Loop executes in the same way.
Also, is there a more efficient way of doing this than using setTimeout? Like a way of just writing
sleep(1000);
or something like that. This whole setTimeout feature seems very overly complicated if it is the only way of producing delays in javascript. I tried this once but it didn't work at all in any way
await sleep(1000);
Any help with Timeouts and delays in Javascript, especially within a loop, would be greatly appreciated!

You may find value in the answer I posted here. That will explain setTimeout in loops a little more.
Separately, you may want to explain what you are trying to accomplish. It looks like you are either
trying to move an element one pixel per second
trying to move an element ~100 pixels after one second
For the first option I would use CSS Transitions instead. You'll have a lot more flexibility over how the element moves and you only need to dictate the direction and distance.
For the second option, you could toss the loop and keep the stuff inside, setting the new marginTop to the full value after some timeout.

setTimeout(function(){
block.style.marginTop = (750 - a) + 'px');
}, 1000);
this part of your code use 'a' variable after 1000 miliseconds. in this time 'a' is 100, because your loop not stoped for run setTimeout function, and it happened because javascript is asyncronous.
one solution for solving this problem in js is using recursive functions. if is not necessary to use for loop, you can use this code:
var a = 0;
function my_loop(a) {
if (a < 101) {
setTimeout(function() {
block.style.marginTop = (750 - a) + 'px');
my_loop(a);
}, 100);
a++;
}
}
my_loop(a);
but if you want do your question's job, i seriously recommened you to use CSS.

As #squint mentioned, you can use setInterval for your task.
Here's an example:
// create an element
const width = 10;
const el = document.createElement('div');
el.setAttribute('id', 'main');
document.body.appendChild(el);
el.style.width = width + 'px';
// question-relevant code starts here
const a = [...Array(101).keys()]; // fancy way to create [0, 1, 2, 3, ...]
const it = a[Symbol.iterator](); // for convenience
const int = setInterval(() => {
const { value, done } = it.next(); // next iteration
if (done) { clearInterval(int); return }; // finished?
el.style.width = width + value + 'px'; // adjust width
}, 10);
#main {
height: 100px;
width: 10px;
background: green;
}

Related

How to rotate from different titles

No, I am not talking about <h1> or something like that. I am talking about the <title> element. I created a function, that delays every .5 seconds or so, and changes titles.
The script I have developed reads the array, which includes the titles, and rotates from it. The first time I have ran it, I did not use setTimeout, and it ran instantly, I couldnt read anything.
But when I tried using the setTimeout function, it started to return undefined. What is the problem with this code?
for(var i = 0; i < titles.length ; i++){
setTimeout(function () {document.title = titles[i]}, 500)
}
You're going about this the wrong way (IMO). Using a loop you'll fire off all your timeouts at once. You'd be better off using setInterval
let i = 0, titles = ['title1', 'title2', 'title3'];
let int = setInterval(() => {
document.title = titles[i];
console.log('doc.title: ', document.title);
if (i++ >= titles.length - 1) clearInterval(int);
}, 500)

Creating a for loop that loops over and over =

So I have a weird problem (as I can do this using dummy code, but cannot make it work in my actual code) -
The concept is simple - I need a for loop that upon hitting its max "I" number reverts "I" to 0 again and creates a loop over and over -
DUMMY CODE:
for(i=0;i<10;i++){
console.log(i);
if(i === 10){
i = 0
}
}
Now for the longer code (sorry)
function reviewF(){
// add ID to each of the objects
reviews.forEach((e, i)=>{
e.id = i
})
// get the elements to be populated on page
var name = document.querySelector('p.name');
var date = document.querySelector('p.date');
var rating = document.querySelector('.rating_stars');
var review = document.querySelector('p.review_content_text');
// reverse the array - so the newest reviews are shown first (this is due to how the reviews where downloaded)
var reviewBack = reviews.slice(0).reverse();
// start the loop - go over each array - take its details and apply it to the elements
/**
* THIS IS WHAT I WOULD LIKE TO LOOP OVER FOREVER
*
* **/
for (let i = 0; i < reviewBack.length; i++) {
(function(index) {
setTimeout(function() {
// document.getElementById('reviews').classList.remove('slideOut')
name.classList.remove('slideOut')
date.classList.remove('slideOut')
rating.classList.remove('slideOut')
review.classList.remove('slideOut')
name.classList.add('slideIn')
date.classList.add('slideIn')
rating.classList.add('slideIn')
review.classList.add('slideIn')
name.innerHTML = reviewBack[i].aditional_info_name;
date.innerHTML = reviewBack[i].Date;
rating.innerHTML = '';
review.innerHTML = reviewBack[i].aditional_info_short_testimonial;
if(reviewBack[i].aditional_info_short_testimonial === 'none'){
reviewBack.innerHTML='';
}
var numberOfStars = reviewBack[i].aditional_info_rating;
for(i=0;i<numberOfStars;i++){
var star = document.createElement('p');
star.className="stars";
rating.appendChild(star);
}
setTimeout(function(){
// document.getElementById('reviews').classList.add('slideOut')
name.classList.add('slideOut')
date.classList.add('slideOut')
rating.classList.add('slideOut')
review.classList.add('slideOut')
},9600)
}, i * 10000)
})(i);
// should create a infinite loop
}
console.log('Loop A')
}
// both functions are running as they should but the time out function for the delay of the transition is not?
reviewF();
EDITS >>>>>>>>
Ok so I have found a hack and slash way to fix the issue - but its not dry code and not good code but it works.....
this might make the desiered effect easier to understand
reviewF(); // <<< this is the init function
// this init2 function for the reviews waits until the reviews have run then
// calls it again
setTimeout(function(){
reviewF();
}, reviews.length*1000)
// this version of the innit doubles the number of reviews and calls it after that amount of time
setTimeout(function(){
reviewF();
}, (reviews.length*2)*1000)
From trying a bunch of different methods to solve this issue something I noticed was when I placed a console.log('Finished') at the end of the function and called it twice in a row (trying to stack the functions running..... yes I know a horrid and blunt way to try and solve the issue but I had gotten to that point) - it called by console.log's while the function was still running (i.e. the set time out section had not finished) - could this have something to do with it.
My apologies for the rough code.
Any help here would be really great as my own attempts to solve this have fallen short and I believe I might have missed something in how the code runs?
Warm regards,
W
Why not simply nest this for loop inside a do/while?
var looping = True
do {
for(i=0;i<10;i++){
console.log(i);
}
if (someEndCondition) {
looping = False;
}
}
while (looping);
I would think that resetting your loop would be as simple as setting "i = 0" like in the dummy code. So try putting the following into your code at the end of the for loop:
if(i === 10){
i = 0;
}

Adding delay to function during drawing objects

i placed setInterval into the code but this obviously delaying all the lasers by 1 second.
I want it to be working in the following sequence:
- at start laser1 and laser2 are fired.
- 1 second break and fire another set of lasers etc.
Also if someone could teach me how to move all block of code by four spaces on forum, that would be amazing, as none of the ways i found in google solve this ridiculous problem.
Code shortcut:
let laser1;
let lasers1 = [];
let laser2;
let lasers2 = [];
function createLaser() {
laser1 = new Laser(bossOne.x, bossOne.y + bossOne.sizeY, 10, 50, 5);
lasers1.push(laser1);
laser2 = new Laser(bossOne.x + bossOne.sizeX - 10, bossOne.y +
bossOne.sizeY, 10, 50, 5);
lasers2.push(laser2);
}
function draw() {
requestAnimationFrame(draw);
setInterval(createLaser, 1000);
for (i = 0; i < lasers1.length; i++) {
lasers1[i].show();
lasers1[i].move();
}
for (i = 0; i < lasers2.length; i++) {
lasers2[i].show();
lasers2[i].move();
}
}
requestAnimationFrame(draw);
Remote host for full code if needed:
https://stacho163.000webhostapp.com/
lasers are red
I think i should work with booleans, but can't handle to set it there.
Got a similar topic with key activation, but i lost contact with the one who proposed a solution on the basis of booleans (as it didn't work well) so i took the easiest part first without involving the keys.
Any tips are appreciated :)
Try changing setInterval to setTimeout, as it's already looping recursively - just add a delay to it. Also move the requestAnimationFrame call to the bottom of draw, not the top:
function draw()
setTimeout(createLasers, 1000);
//Loops
requestAnimationFrame(draw);
}

setTimeout() not working for changing intervals

I have a class test whose background-color I want to flip between lime and green faster and faster.
For that, I'm using a for loop variable and passing it to a function containing a setTimeout(), but it's not working.
(This is not a duplicate question. The said "original" is about a simple setTimeout() whereas this question is about a setTimeout() within a for loop. I understand that the answers on that question might indirectly answer mine, but the questions themselves aren't the same)
$(document).ready(function() {
for (var i = 0; i < 20; i++) {
delay(i);
$(".test").css('background-color', 'lime');
}
});
function delay(i) {
setTimeout(function() {
$(".test").css('background-color', 'green');
}, 1000 - 50 * i);
}
.test {
width: 300px;
height: 300px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test"></div>
Try this way:
for(var i=0;i<20;i++)
{
delay(i);
}
function delay(i) {
setTimeout(function() {
if (i%2 == 0) {
$(".test").css('background-color', 'green');
} else {
$(".test").css('background-color', 'lime');
}
}, 1000 - 50 * i);
}
The problem is the loop executes faster than the timeout. setTimeout function basically says execute the given function after a certain time. The for loop you created there will continue without waiting for the code inside the setTimeout function to be executed, In other words your code producing 20 functions that will be executed in the future.
There are many way to produce the functionality you need.
To keep it simple and solve it you should create two functions instead:
$(document).ready(function() {
for (var i = 0; i < 20; i++) {
delay_lime(i);
delay_green(i+1);
}
});
function delay_green(i) {
setTimeout(function() {
$(".test").css('background-color', 'green');
}, 1000 - 50 * i);
}
function delay_lime(i) {
setTimeout(function() {
$(".test").css('background-color', 'lime');
}, 1000 - 50 * i);
}
try this: here is a example example
$(document).ready(function() {
delay();
var start = 0;
delay(start);
function delay(start) {
setTimeout(function() {
if(start == 0 ){
$(".test").css('background-color', 'green');
start = 1;
}else{
$(".test").css('background-color', 'red');
start = 0;
}
delay(start);
}, 100);
}
});
If you want to use a for loop, you should turn its containing function into an async function and await promises that resolve at the desired time:
const delay = (i) => new Promise(resolve => {
setTimeout(resolve, 1000 - 50 * i);
});
function changeToGreen() {
$(".test").css('background-color', 'green');
}
function changeToLime() {
$(".test").css('background-color', 'lime');
}
(async () => {
for (var i = 0; i < 20; i++) {
await delay(i);
changeToLime();
await delay(i);
changeToGreen();
}
})();
Your loop doesn't wait for any of the timeouts to occur, it runs through and queues up the events which will fire at the relevant intervals.
However, whilst doing so it sets the background color to lime a number of times.
After the loop has finished, the queued intervals start firing, and they set the background color to green a number of times.
But the colours do not alternate as the code execution is not in the order you expect.
Also, the multiple calls to setInterval queue the events to be fired after the specified delay. The code does not wait for the allotted time and then fire the next one. So your could of 1000 - 50 * i actually queues the latest event first, and so on until it queues the event that will actually fire first. Does that make sense? It will be more intuitive for you to set these in the order that they will fire. You could achieve the reducing delay by incrementing the timeout by a variable which reduces, e.g.
time = 1000;
delay = 1000;
setTimeout (blah, time);
time += delay;
delay -= 50;
setTimeout (blah, time);
// etc.
You could achieve an alternating effect by setting alternate intervals to be green and lime. For that a simple toggle variable would help.
color = 1;
color = 1 - color; // toggles between 0 and 1
useColor = ["lime", "green"][color];
I shan't rewrite your entire program for you, but I can assist more if you have specific questions. The best way to learn is to do.
There is a slight misunderstanding about the way timeouts work in the example code. Timeouts are asynchronous, meaning that they execute out of the normal order of execution. As a result, the lime green is shown immediately, and then at various times later the background is repeatedly changed to green; although, the only time the change is noticed is the first time as changing from green to green has no effect.
setTimeout creates a task, JavaScript in a browser is single threaded and will execute tasks through a task scheduler.
Using 1000 - 50 * i from 0 to 19 in the approach shown in the question will result in timeouts being scheduled for execution. First at 1000, then at 950, etc. However, they are all scheduled at the exact same time. So there is no difference scheduling them in forward or reverse order as the only relevant metric used is the time. Essentially the result is that every 50 milliseconds, the background color is set to green in this example.
Unfortunately, tasks that get executed in the browser are not executed exactly on time, and using this will aim at 50 milliseconds per call, but due to Operating System scheduling and depending on the system in use the result could be wildly different.
This could have been done with an interval just as easily, where the interval used was 50 milliseconds (although it would still suffer from the aforementioned OS issue). That said, there is no acceleration being used there. A better approach here, since we are dealing with animation (the colors flashing) would be to instead use requestAnimationFrame MDN.
requestAnimationFrame will attempt to run your code at 60 frames per second, or roughly 16.6 milliseconds per frame (1000 milliseconds / 60 frames).
Given that the goal was acceleration, a rate could be put in place to ramp the flashing.
// Cache the constructed jQuery object for element with class "test"
var testCache = $('.test');
// Create a set of colors to use in the flashing
var colors = ['lime','green'];
// Use a variable for a switch between the two colors
var colorSwitch = 0;
// Keep track of how many times the color has flashed
var i = 0;
// Used for tracking the start of an animation sequence
var start;
// In order to facilitate acceleration, use a function for
// determining the time between flashes,
// used an offset x^2 line at (20,16) with a 2x width
// y = 1/2(x-19)^2 - 19x + 16
var ft = t => 0.5*(t-19)*(t-19) - (t-19) + 16;
// This function will be called every 16.6 milliseconds
// by requestAnimationFrame, the timestamp is automatically injected
(function flashAccel(timestamp){
// Loop control to ensure only 20 flashes occur
if(i >= 20) return;
// Track the start of the timing for the animation sequence
start = start || timestamp;
// This is the milliseconds since the last sequence was updated
var elapsed = timestamp - start;
// Check to see if enough time has elapsed based on the acceleration
// function's value and the current value, if it has then update the view
if( elapsed > ft(i) ){
// Swaps between 0 and 1
colorSwitch = 1 - colorSwitch;
// Selects 0 or 1 indexed color
var color = colors[colorSwitch];
testCache.css('background-color',color);
// Update metrics
i++;
start = timestamp;
}
// Request the function to be called again in roughly 16.6 milliseconds
window.requestAnimationFrame(flashAccel);
})()
.test {
width: 300px;
height: 300px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test"></div>

Javascript easing not animating

Trying to implement simple easing in javascript (without jQuery) but unfortunately there doesn't seem to be any animation when the button is clicked (see below).
My goal is to get the hidden list item (the last item) visible by tweening the first item's margin left property. I know it isn't a CSS issue because manually modifying the style moves the list, but I'm not sure what the issue is. My guess is with how I'm calling the ease function but changing the params still wasn't working for me.
The easing part is below, entire code is here: Fiddle
JS:
var start = document.getElementById('start'),
list = document.getElementById('my-list'),
imgs = list.getElementsByTagName('img'),
last_img = imgs[imgs.length -1 ];
ease = function(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
shift_imgs = function(el) {
var orig_value = parseFloat( el.style.marginLeft ),
end_value = -37,
change = Math.abs( end_value - orig_value ),
duration = 1, // 1 second
time = 0;
for ( var i = 0; i < change; i++ ) {
setTimeout(function() {
el.style.marginLeft = ( parseFloat( el.style.marginLeft ) + 1 ) + 'px';
}, time);
time = ease(time, orig_value, change, duration);
}
};
start.onclick = function() {
shift_imgs(last_img);
}
Your orig_value is NaN as parseFloat(el.style.marginLeft) returns nothing, even if you set an initial value in the css. i.e: margin-left: 15px; still will return nothing.
You can use window.getComputedStyle(...).getPropertyValue, similar to this:
window.getComputedStyle(el, null).getPropertyValue("margin-left");
This will give you the actual current value along with the px, i.e: 0px.
(It always return the value in px even if set in CSS as em or pt)
So you need to remove the px and the get the float value.
You can wrap this into a little helper similar to this:
getElementMarginLeftAsFloat = function (el) {
var pxValue = window.getComputedStyle(el, null).getPropertyValue("margin-left");
var valueOnly = pxValue.substring(0, pxValue.length - 2);
return parseFloat(valueOnly);
}
Another issue is that the moving of the actual element occurs within setTimeout executed inside a loop. The loop which calls setTimeout, causes each setTimeout to be queued nearly simultaneously, hence they all execute close to the same time, causing the element to just jump.
You can use a recursive sub-method inside your method which uses setTimeout to call itself until it is done. That way each setTimeout is triggered only after the specified interval, causing them to be executed close enough apart to the specified interval, similar to this:
shift_imgs = function (el) {
var orig_value = getElementMarginLeftAsFloat(el),
end_value = -37,
change = Math.abs(end_value - orig_value),
duration = 1, // 1 second
time = 0;
function doShift() {
currentValue = getElementMarginLeftAsFloat(el);
if(currentValue+1 > change){
return;
};
el.style.marginLeft = (currentValue + 1) + 'px';
time = ease(time, orig_value, change, duration);
setTimeout(doShift, time);
}
doShift();
};
By having the setTimeout function call itself, it releases the resources, ensuring the drawing of the element can occur between each "iteration".
I updated your code to use this approach and it seems to work now.
DEMO - animating movement using computed style
You can most likely do this many other ways and also prettify this code for sure but but this should get you started either way.

Categories