I want to create a 'random selector' behaviour where the function iterates through an array for a period of time (example: 3 seconds, 5 seconds) while displaying all of array elements fast throughout the iteration until the iteration ends. Just imagine seeing all the elements displayed in a label one after another until it finally stops at an element.
My code so far:
var places = ["Curry Leaf", "Subway", "Burger King"];
function execute_randomizer() {
var place_label = document.getElementById("place_label");
for (var i = 0; i < 100; i++) {
var selected_place = places[Math.floor(Math.random() * places.length)];
setTimeout(function () {
place_label.innerText = selected_place;
}, 400);
}
}
This runs through the iteration and displays an element when the loop is done but it doesn't show each iteration's element. How can I amend this?
EDIT
Even if there's 3 elements, the animations must re-iterate through the array until the duration is completed
Your for finishes iterating before the setTimeout runs, then the function passed to setTimeout runs 100 times using the last value of selected_place.
More about this behavior in setTimeout in for-loop does not print consecutive values
Another problem that I noticed is that your setTimeout will trigger after 400ms, since the for loop will finish in about 1 ms, the function passed to setTimeout will trigger 100 times one after another with no delay in between, for this, I replaced the delay argument of setTimeout from 400 to 400 * i.
var places = ["Curry Leaf", "Subway", "Burger King"];
function execute_randomizer() {
var place_label = document.getElementById("place_label");
for (var i = 0; i < 100; i++) {
var selected_place = places[Math.floor(Math.random() * places.length)];
(function(selected_place){
setTimeout(function () {
place_label.innerText = selected_place;
}, 400 * i);
})(selected_place);
}
}
execute_randomizer();
<span id="place_label"></span>
You could use a closure over the value and a different time for each timeout.
var places = ["Curry Leaf", "Subway", "Burger King"];
function execute_randomizer() {
var place_label = document.getElementById("place_label");
for (var i = 0; i < 10; i++) {
var selected_place = places[Math.floor(Math.random() * places.length)];
setTimeout(function (value) {
return function () {
place_label.innerText = value;
};
}(selected_place), i * 100);
}
}
execute_randomizer();
<div id="place_label"></div>
For a first run through, you could show each element and then take a random element at last value.
function execute_randomizer() {
function cb (value) {
return function () {
place_label.innerText = value;
};
}
var place_label = document.getElementById("place_label");
place_label.innerText = '';
for (var i = 0; i < places.length; i++) {
setTimeout(cb(places[i]), 200 + i * 200);
}
setTimeout(cb(places[Math.floor(Math.random() * places.length)]), 200 + places.length * 200);
}
var places = ["Curry Leaf", "Subway", "Burger King"];
execute_randomizer();
<div id="place_label"></div>
You should change your loop because right now you go in loop 100 times in maybe one millisecond and order 100 of change text but once again in the same time.
So better is wait for time out 400 ms and then make next iteration.
Please remember time out is async.
I think it might be better if you would put the whole function in a timeout. (I'm not sure and didn't test, but that's what I would try).
What I mean is that you just build a function that creates your randomizer and fills in your field. After that you put a timeout to call the same function again + keep a counter that you pass as parameter.
An example of what I mean below: (didn't test it)
var places = ["Curry Leaf", "Subway", "Burger King"];
execute_randomizer(0); /* Start for the first time */
function execute_randomizer(times) {
if(times == 100)
return; /* stop execution */
var place_label = document.getElementById("place_label");
var selected_place = places[Math.floor(Math.random() * places.length)];
place_label.innerText = selected_place;
setTimeout(function () {
execute_randomizer(times+1)
}, 400);
}
Related
Im trying create some type of number generator on webpage. I want to show like five numbers before the generated number show. For better imagine, you can look to google generator. When you click generate, it shows like 3-4 numbers before generated number. I use setInterval or setTimeout but i dont know how it works. My js code:
var button = document.querySelector("button");
button.addEventListener("click",function() {
for (var i = 0; i < 8; i++) {
setInterval(textC,5);
}
});
function textC(){
number.textContent = Math.floor(Math.random() * 1000) + 1;
}
Thanks for every help!
The issue with setInterval() is that it will continue forever unless cleared, causing you to keep generating random numbers. Instead you can use setTimeout(), but set the timeout to change based on the value of i in the for loop. That way, each interval will occur 50 m/s after the other.
See example below:
const button = document.querySelector("button");
const number = document.querySelector("#number");
button.addEventListener("click", function() {
for (let i = 0; i < 5; i++) {
setTimeout(textC, 50 * i);
}
});
function textC() {
number.textContent = Math.floor(Math.random() * 1000) + 1;
}
<p id="number"></p>
<button>Generate</button>
Don't use a loop (why not?). Just nest setTimeout and call it until a predefined threshold is reached. It gives you maximum control.
var button = document.querySelector("button");
var number = document.querySelector("#number");
const nRuns = 12;
const timeout = 100;
let iterator = 0;
button.addEventListener( "click", textC);
function textC(){
number.textContent = `${Math.floor(Math.random() * 1000) + 1}\n`;
iterator += 1;
if (iterator < nRuns) {
setTimeout(textC, timeout)
} else{
iterator = 0;
// you control the loop, so it's time for some extra text after it
number.textContent += ` ... and that concludes this series of random numbers`;
}
}
<p id="number"></p>
<button>Generate</button>
I have four different button effects where each effect are declared in a variable.
Therefore, I bring all of these four variables and place them within an array called arr in which is used in the clickByItself() function using Math.floor(Math.random()) methods.
Without the for loop, the code clicks by itself randomly in one of the four buttons every time I reload the page.
function clickByItself() {
let random = Math.floor(Math.random() * arr.length);
$(arr[random]).click();
}
However, using the for loop I am not being able to make these clicks one-by-one within the maximum of 10 times.
var blueButtonEffect = code effect here;
var redButtonEffect = code effect here;
var greenButtonEffect = code effect here;
var yellowButtonEffect = code effect here;
var arr = [blueButtonEffect, redButtonEffect, greenButtonEffect, yellowButtonEffect];
//will click on buttons randomly
function clickByItself() {
let random = Math.floor(Math.random() * arr.length)
var i;
for (i = 0; i < 10; i++) {
$(arr[random]).click();
setTimeout(clickByItself(), 1000);
}
}
The final output with the current code above is the four buttons being clicked at the same time, not one-by-one.
So, how can I have this function to press a random button by 10 times one-by-one with one second of interval from each click?
To fix your code you need:
A base case for your recursion
Pass a function reference to setTimeout. Currently, you are executing clickByItself and passing its return value (which is undefined) to setTimeout.
Do not use setTimeout in a loop without increasing the time by a factor of i, as the for loop will queue all the function calls at the same time
Alternatively, you can use a "times" argument to avoid looping
You could try something like
function clickByItself(times = 0) {
let random = Math.floor(Math.random() * arr.length)
$(arr[random]).click();
if (++times < 10) {
setTimeout(function(){clickByItself(times);}, 1000);
}
}
An example with console logs
https://jsfiddle.net/pfsrLwh3/
The problem is that the for loop calls the setTimeout 10 times very quickly. If you want to wait until the previous function call finishes prior to calling the next, then you should use recursion or just use a setInterval.
Recursion:
function clickByItself(numIterations) {
let random = Math.floor(Math.random() * arr.length)
let i;
$(arr[random]).click();
if( numIterations < 10 ){
setTimeout(() => {
clickByItself(numIterations += 1)
}, 1000)
}
}
clickByItself(0);
With setInterval
let numIterations = 0;
function clickByItself() {
let random = Math.floor(Math.random() * arr.length);
let i;
$(arr[random]).click();
numIterations += 1;
if( numIterations > 10) {
clearInterval(loop)
}
}
let loop = setInterval(test2, 1000);
Are you saying this is working for only 4 times but I think your above code will run in an infinite loop as you are calling clickByItself() again in the for loop.
If you want press a random button by 10 times one-by-one with one second of interval from each click then replace the for loop with
for (i = 0; i < 10; i++) {
setTimeout($(arr[random]).click(), 1000);
}
I'm trying to create a sort of ecosystem where objects spawn over time. However, when I try using setInterval to increase the amount it doesn't work. It works when I call the function on its own, but not when I use setInterval.
var plantSpawn = 5;
function createPlants() {
setInterval(reproducePlants, 5000);
for(var i=0; i<plantSpawn; i++){
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'green';
myPlants[i] = plant;
}
}
function reproducePlants() {
plantSpawn += 5;
}
My goal for this is for every 5 seconds, 5 new plants appear. However, when I use the reproducePlants function with setInterval it does not work.
Note: I am calling createPlants() later in my code which makes the first 5 plants show up, but the next 5 won't show up. I am just showing the code that I'm trying to fix
The creation code must be moved inside the function that is repeatedly called.
NOTE: This is not an efficient way if you are going to call reproducePlants infinitely many times, since the myPlants array is reconstructed every time.
// Init with 0, because we increment it inside reproduce Plants
var plantSpawn = 0;
var myPlants = [];
function createPlants() {
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants() {
const canvas = document.getElementsByTagName('canvas')[0];
plantSpawn += 5;
for(var i = 0; i < plantSpawn; i++) {
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'green';
myPlants[i] = plant;
}
}
You don't necessarily need to call the createPlants function from the reproducePlants function. You could add a call after both the functions. If I understand what you are trying to achieve that should do it.
You need to move the code that creates the plants (the for() chunck) inside the function that is called every 5 seconds (reproduce plants). So each time the function is called it will create the plants.
If you are trying to add only the 5 new plants every 5 seconds to your plants array you shouldn't recreate the array each time. It's better to keep track of the last index you have added and then continue right after that.
I created a variable called lastCreatedIndex so you can understand better what is going on. So the first time the code will run plants[i] from 0 to 4, the second 5 to 9...
var myPlants = [];
var lastCreatedIndex;
var plantSpawn;
function createPlants() {
plantSpawn = 5; //Initialize with 5 plants
lastCreatedIndex = 0; // Starts from index 0
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants() {
for(var i = 0 + lastCreatedIndex; i < plantSpawn; i++) {
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'green';
myPlants[i] = plant;
console.log(i); // Output the number of the plant that has been added
}
lastCreatedIndex = i; //Update the last index value
plantSpawn += 5;
}
I have two functions that are supposed to run when I click on a button. The first function is a setInterval function that increments the variable num by 0.01 and displays it in a div id called result. The second function generates an object literal into a json string and displays it in a div id called output. When I click on the button, only the JSON string is outputted, but it seems as though the setInterval doesn't run and I don't know why. Here is the link for example
HTML
<button>
click
</button>
<div id="result">
</div>
<div id="output">
</div>
Javascript
var timer,
result = document.getElementById("result"),
num,
link ={
name:"link",
weapon:"master sword",
mute:true,
race:"hyrulian",
age:16
};
/* ---------- GENERATE JSON OBJECT --*/
function generate(){
var data = {};
for(var prop in link){
data[prop] = link[prop];
}
data = JSON.stringify(data);
document.getElementById("output").innerHTML = data;
}
/* ---------- CLICK BUTTON --*/
document.querySelector("button").onclick = function(){
num = 0;
function callBack(){
num += 0.01;
result.innerHTML = num.toFixed(2);
}
timer = setInterval(callBack,10);
generate();
clearInterval(timer);
}
Updated My Answer: You have to wrap your callBack(); function in the setInterval with an anonymous function. This is because setInterval expects a reference to a Function that should be executed every few milliseconds (the interval you specify). You should use it like this setInterval( function(){ callBack(); }, 10);. This should give you the desired result.
Same code, I just updated the setInterval( function(){ callBack(); }, 10); line:
var timer,
result = document.getElementById("result"),
num,
link ={
name:"link",
weapon:"master sword",
mute:true,
race:"hyrulian",
age:16
};
/* ---------- GENERATE JSON OBJECT --*/
function generate(){
var data = {};
for(var prop in link){
data[prop] = link[prop];
}
data = JSON.stringify(data);
document.getElementById("output").innerHTML = data;
}
/* ---------- CLICK BUTTON --*/
document.querySelector("button").onclick = function(){
num = 0;
function callBack(){
num += 0.01;
result.innerHTML = num.toFixed(2);
}
timer = setInterval( function(){ callBack(); },10); // I modified this line
// timer = setInterval(callBack,10);
generate();
clearInterval(timer);
// setTimeout( function(){ clearInterval(timer); }, 1000); // This can be used to test that the timer has indeed started, you can wait 1 second before you clear the timer
}
It's because you setInterval(callBack,10);, but then you clear the interval using clearInterval(timer);.
remove the clearInterval(timer); and you'll get the timer working.
JavaScript is single threaded. In order for the callBack function which you have set up to run in an interval of 10ms with setInterval(callBack,10) to run, your JavaScript code has to stop running (i.e. complete). Until your code relinquishes the processor, no call back functions (of any type) will execute.
Thus, in your code:
timer = setInterval(callBack,10); //Set up the interval timer
generate(); //generate(): Even if this takes 10 minutes to complete
// the callBack function will not be executed from the interval timer.
clearInterval(timer); //Clear the interval timer. While the interval may have expired
// (depending on how long the generate() function took), the
// the callBack function will have never run because the processor
// has been busy continuing to run the code that came after calling
// setInterval. Clearing the interval timer here results in
// callBack never being executed.
Thus, if you want callBack to execute even once, you need to not call clearInterval(timer) prior to the code you are executing at that time completing.
If you wanted callBack to only be executed once, then you could have used setTimeout().
Timing how long something takes:
If you are attempting to time how long something takes:
Get the current time prior to what you are wanting to time
Use window.performance.now() which is "measured in milliseconds, accurate to one thousandth of a millisecond".
Do the thing you want to time
Get the time after you are done (Use window.performance.now() again).
Compute and display the difference between the two times.
So, modifying your code:
var timer,
result = document.getElementById("result"),
link = {
name: "link",
weapon: "master sword",
mute: true,
race: "hyrulian",
age: 16
};
/* ---------- GENERATE JSON OBJECT --*/
function generate() {
var data = {};
for (var prop in link) {
data[prop] = link[prop];
}
data = JSON.stringify(data);
document.getElementById("output").innerHTML = data;
}
/* ---------- CLICK BUTTON --*/
document.querySelector("button").onclick = function() {
function showResult(time, precision, units) {
result.innerHTML = time.toFixed(precision) + ' ' + units;
}
var startTime = window.performance.now();
generate();
var endTime = window.performance.now();
//These times are floating point numbers in milliseconds
var elapsedMs = endTime - startTime;
showResult(elapsedMs,3,'milliseconds');
//If you want to show the time in microseconds:
//var elapsedUs = elapsedMs * 1000;
//showResult(elapsedUs,0,'microseconds');
}
<button>
click
</button>
<div id="result">
</div>
<div id="output">
</div>
However, if your real goal is to determine how long, on average, it takes to run generate() then you need time how long it takes to run generate a large number of times, not just once.
var result = document.getElementById("result");
var link = {
name: "link",
weapon: "master sword",
mute: true,
race: "hyrulian",
age: 16
};
/* ---------- GENERATE JSON OBJECT --*/
function generate() {
var data = {};
for (var prop in link) {
data[prop] = link[prop];
}
data = JSON.stringify(data);
document.getElementById("output").innerHTML = data;
}
/* ---------- CLICK BUTTON --*/
document.querySelector("button").onclick = function(event) {
//Should not do long things in an event handler.
//Thus, instead of actually performing the tests here, we set
// a timeout which will execute after the event has processed.
this.style.display = 'none'; //Only get to click once
setTimeout(performTimedTests,0,generate,result);
}
/*
* Perform multiple runs of an increasing number of iterations of a specified function.
*
* functionToTest = The function which is being timed
* resultsElement = An element in the DOM, usually a <div>, which will
* have its innerHTML replaced with a table of time results.
* start = The number of iterations to start with. (default = 1)
* end = The number of iterations to not exceed. (default = 1000000)
* multiplier = The amount multiply the current number of iterations to find
* the number of iterations for the next run. (default = 10)
*/
function performTimedTests(functionToTest,resultsElement,start,end,multiplier){
start=start?start:1;
end=end?end:1000000;
multiplier=multiplier?multiplier:10;
var textColors =['red','orange','yellow','green','blue','purple']
function timeAfunction(functionToTest, iterations){
var i, startTime, endTime;
startTime = window.performance.now();
for(i=0;i<iterations;i++){
functionToTest();
}
endTime = window.performance.now();
return (endTime - startTime);
}
function addResultToTable(table, elapsedTime, iterations, precision1, units1
, precision2, units2, scale2, name) {
var perCall = (elapsedTime/iterations)*scale2;
var newRow = table.insertRow(-1);
newRow.insertCell(-1).textContent = iterations;
newRow.insertCell(-1).textContent = elapsedTime.toFixed(precision1) + units1;
newRow.insertCell(-1).textContent = perCall.toFixed(precision2) + units2;
newRow.insertCell(-1).textContent = name;
}
function performNextTest(functionToTest,resultsElement,iterations,maxIterations
,multiplier,isFirstIteration){
var elapsedTime = timeAfunction(functionToTest, iterations);
var processing;
if(isFirstIteration) {
result.innerHTML = '<div></div>'
+'<table><tr><th>Iterations</th><th>Total time</th>'
+'<th>Time per<br/>Iteration</th>'
+'<th>Function<br>Tested</th></tr></table>';
processing = resultsElement.querySelector('div');
processing.textContent = 'Processing';
processing.style.color = textColors[0];
}
processing = resultsElement.querySelector('div');
var table = resultsElement.querySelector('table');
addResultToTable(table,elapsedTime,iterations,3,'ms',3,'us',1000
,functionToTest.name);
processing.textContent += '.';
//Cycle colors of 'Processing..' text
var colorIndex=textColors.indexOf(processing.style.color);
colorIndex++;
colorIndex = colorIndex>=textColors.length?0:colorIndex;
processing.style.color = textColors[colorIndex];
iterations *= multiplier;
if(iterations<=maxIterations) {
//Let the browser redraw
setTimeout(performNextTest,0,functionToTest,resultsElement,iterations
,maxIterations,multiplier,false);
}else{
processing.textContent = '';
processing.style.display = 'none';
}
}
performNextTest(functionToTest,resultsElement,start,end,multiplier,true);
}
<button>Start</button>
<div id="result"></div>
<div id="output"></div>
I wrote a simple enough randomize function that loops through the elements in an array and displays them one after the other.
See it here.
function changeSubTitle() {
var whatAmI = ["Webdesigner", "Drummer", "Techie", "Linguistics student", "Photographer", "Geek", "Coder", "Belgian", "Batman", "Musician", "StackExchanger", "AI student"];
setTimeout(function () {
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).text(whatAmI[Math.floor(Math.random() * whatAmI.length)]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
}
However, obviously it is very well possible that the same element is displayed twice, one immediately after the other. This happens because I randomize the array each time I call the function. How would I prevent an element to be displayed two times right after each other?
I suppose the most straightforward way to do this is to get the randomize function out the loop, run the loop and each time an element is called, remove the index from the array and refill the array when it's empty. A lot of questions on SO consider this problem, but not as specific as mine: I'm not sure how to do this in the loop to display each element.
Stop upvoting please :-D
The following answer is better: https://stackoverflow.com/a/32395535/1636522. The extended discussion between me and Tim enlighten on why you should avoid using my solutions. My answer is only interesting from this point of view, hence, it does not deserve any upvote :-D
You could save the last integer and "while" until the next one is different:
var i, j, n = 10;
setInterval(function () {
while ((j = Math.floor(Math.random() * n)) === i);
document.write(j + ' ');
i = j;
}, 1000);
Or even simpler, just add 1... :-D Modulo n to prevent index overflow:
var i, j, n = 10;
setInterval(function () {
j = Math.floor(Math.random() * n);
if (j === i) j = (j + 1) % n;
document.write(j + ' ');
i = j;
}, 1000);
First solution applied to your code:
var whatAmI = ["Webdesigner", "Drummer", "Techie", "Linguistics student", "Photographer", "Geek", "Coder", "Belgian", "Batman", "Musician", "StackExchanger", "AI student"];
var j;
var i = 1; // since you start with "Drummer"
var n = whatAmI.length;
function changeSubTitle() {
setTimeout(function () {
while ((j = Math.floor(Math.random() * n)) === i);
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).text(whatAmI[j]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
i = j;
}
changeSubTitle();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header class="page-header">
<h1>Bananas</h1>
<h2>Drummer</h2>
</header>
To give an even chance of all elements except the previous one being used do the following:
var i, j, n = 10;
setInterval(function () {
// Subtract 1 from n since we are actually selecting from a smaller set
j = Math.floor(Math.random() * (n-1));
// if the number we just generated is equal to or greater than the previous
// number then add one to move up past it
if (j >= i) j += 1;
document.write(j + ' ');
i = j;
}, 1000);
The comments in the code should explain how this works. The key thing to remember is that you are actually selecting from 9 possible values, not from 10.
You should initialize i to be a random element in the array before starting.
For a simple walk through on a 3 element array with the second element selected:
i=1, n=3
The random result gives us either 0 or 1.
If it is 0 then j >= i returns false and we select element zero
If it is 1 then j >= i returns true and we select the third element.
You can do the same walk through with i being 0 and and i being 2 to see that it never overruns the buffer and always has an equal chance to select all other elements.
You can then extend that same logic to an array of any size. It works exactly the same way.
var whatAmI = ["Webdesigner", "Drummer", "Techie", "Linguistics student", "Photographer", "Geek", "Coder", "Belgian", "Batman", "Musician", "StackExchanger", "AI student"];
var prev = 1; //1 because "drummer" is the first array element displayed in the HTML
function getIndex(){
var next = Math.floor(Math.random() * whatAmI.length);
if(next==prev)
return getIndex();
else {
prev = next;
return next;
}
}
function changeSubTitle() {
setTimeout(function () {
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).text(whatAmI[getIndex()]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
}
changeSubTitle();
Try this method.