timing for loop in JS - javascript

I wrote this code in JS:
function startFunction() {
p1 = document.getElementById('p1').innerHTML;
for (var i=1; i<=p1.length; i++) {
alert(p1.slice(0, i));
}
}
I call the function with onload event in html:
<body onload="startFunction()">
And thi is the paragraph with p1 id:
<p id="p1">Hi, I'm</p>
How can I make a delay for the for loop. I want my program to write the p1 text letter by letter.

You can not and should not delay anything inside a loop, because that is how the nonresponsive pages are made: the browser does not react to user actions or do anything visible until the JavaScript code returns.
Instead, you can use some timer, like setInterval():
function startFunction() {
var p1 = document.getElementById('p1');
var txt = p1.innerHTML;
var i=0;
var timer = setInterval(function() {
p1.innerHTML = txt.slice(0,i++);
if(i>txt.length) {
clearInterval(timer);
}
},500);
}
startFunction();
<p id="p1">Hi, I'm</p>

var alertEachLetter =function(p1, i){
setTimeout(function(){
alert(p1.slice(0, i));
},1000);
};
function startFunction() {
p1 = document.getElementById('p1').innerHTML;
for (var i=1; i<=p1.length; i++) {
alertEachLetter(p1, i);
}
}
why create this alertEachLetter function. for that you need to check this link
setTimeout in for-loop does not print consecutive values

You don't need a loop, you need an interval. Javascript's interval feature will call your function at (approximately) the requested interval. So, for example:
function startFunction() {
var p1 = document.getElementById('p1').innerHTML
var count = 1
var finished = p1.length
var iv = setInterval(function() {
alert(p1.slice(0,count++))
if (count > finished) {
clearInterval(iv) // stops the interval from firing once we finish our task
}
}, 1000) // 1000 ms, or every second.
}

Here's a quick example using setTimeout instead of setInterval. There's not much difference except you don't have to clear the timeout - you simply don't run it if it doesn't meet a condition.
// cache the elements
const p1 = document.getElementById('p1');
const out = document.getElementById('out');
// make the text content from p1 iterable and split it into
// the head (first element), and tail (everything else)
const [head, ...tail] = [...p1.textContent];
const loop = function loop(head, tail) {
// update the output text content with the result of head
out.textContent = head;
// if there's anything left of the tail array
if (tail.length) {
// remove the first element of tail and
// add it to head
head += tail.shift();
// call the function again with the new head and tail
setTimeout(loop, 200, head, tail);
}
// pass in the head and tail to the function
}(head, tail);
#p1 { display: none; }
<p id="p1">Content written letter by letter</p>
<p id="out"></p>

Below is an approach I think may help you achieve what youre trying to do. This approach uses setInterval (instead of a loop) to execute a function multiple times. See the comments to understand the code logic:
//Grab our DOM elements
var p1 = document.getElementById('p1').innerHTML;
var copy = document.getElementById('copy');
//Execute a function every 250 milliseconds
var intervalId = setInterval(onInterval, 250);
//nextLetter is a function that will return the character at a particular index in the string. The function will increase the index each time it is called. The function will return null once it exceeds the innerHTML length. c is a "private" variable that can't be modified elsewhere in the program.
var nextLetter = (function(i, limit) {
var c = i;
return function() {
var idx = c++;
if (idx > limit) {
return null;
}
return p1.charAt(idx);
};
})(0, p1.length);
//The function we will execute at each interval
function onInterval() {
var letter = nextLetter();
if (letter) {
copy.innerHTML += letter;
} else {
console.log('End of content reached - removing interval');
clearTimeout(intervalId);
}
}
<p id="p1">Make sure to read the in-code comments</p>
<p id="copy"></p>

Related

How to change interval time under a condition in JavaScript?

I need a function that will print letters one by one but make pauses after ",".
I tried to do it like this but it didn't work :
var span = document.getElementById('text')
function print(string){
var i = 0
var time = 50
var timer = setInterval(function(){
span.innerHTML += string[i++]
if(string[i] == ","){
time = 150
}else{
time = 50
}
},time)
}
I am not sure you can change the time of setTimeinterval once defined. It is better to use a setTimeout. I'm using recursive function to achieve the desired -
function print(string, i){
var time = 50
if(string[i]==",") time = 1000
setTimeout(function(){
span.innerHTML += string[i]
if(i<string.length-1) print(string,++i)
},time)
}
complete example can be found here
First, I don't think setInterval is what you're looking for. Since it seems you just want to print each character with a delay, setTimeout would work best in this situation.
I would break this up so that you can focus on printing a single character at a time, and then you can call that in your main print function for each character in the string.
This is how I would do something like this, using Promises:
const printCharacter = (ch, idx) => new Promise(resolve => {
let time = 50 * idx;
if(ch === ',') {
time = 150 + time * idx;
}
setTimeout(() => {
const span = document.getElementById('text');
span.innerHTML += ch;
resolve();
}, time)
});
const print = async string => {
for(let i = 0; i < string.length; i++) {
await printCharacter(string[i], i);
}
};
There are some bugs that could present itself here, such as the timing for multiple commas present in your base string. This is just my first iteration.
Instead of using an interval, I recommend using a for loop to iterate the string and a Promise to pause as long as desired.
Side note, use textContent instead of innerHTML. The former is like myCar.cangeTires(); myCar.closeDoors(), the latter is like myCar.do('change tires'); myCar.do('close doors');.
let sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
var span = document.getElementById('text');
let print = async string => {
span.textContent = '';
for (let c of string) {
span.textContent += c;
await sleep(c === ',' ? 500 : 50);
}
};
print('my text, and a comma');
<span id="text"></span>

I cannot run setInterval and another function at the same time

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>

Canvas issues with updating

<canvas id="ctx" width="500" height="500" style="border:1px solid #000000;">
<script type="text/javascript">
window.onload = function() {
var ctx = document.getElementById("ctx").getContext("2d");
function createText(words) {
var LENGTH = words.length;
var LOOPS = LENGTH;
var reader = 0;
position = 10;
while( LOOPS > 0) {
letter = words.substr(reader,1);
setTimeout(ctx.fillText(letter,position,10),100);
position += 6;
reader += 1;
LOOPS -= 1;
}
}
createText("Hello, how are you?");
}
</script>
</canvas>
I want it to do kind of like a typing animation where it paused for a fraction of a second before each letter is printed, but instead it loads all at the same time. What am I doing wrong?
So there were a few things making this not work for you, for setTimeout your ctx.fillText was being called right away, as soon as the loop hit it. To stop that you need to wrap it in a function so it will be called during the timeout.
setTimeout(function(){
// your logic here.
}, 100);
However if you do that you will run into the common issue where you will only get the last letter due to the way variable scoping works in JavaScript. To fix that you need to wrap your function in a closure and pass the values to it.
// loop start
a++;
b++;
setTimeout(
(function (a,b) {
return function () {
// some logic that uses a and b
}
})(a, b), 100);
// loop end
The last thing that happens is your timeout is set to 100.. so it will all still happen at once. Meaning every timeout is going to fire after 100ms since the creation loop is so fast. In order to solve this you need to save the delay somewhere and increase that in the loop so they happen after one another. For example the first one will be delayed 100ms, and the next one 200ms, then 300, ect.
// loop start
a++;
b++;
// Increase the time delay each loop iteration
timeDelay += 100;
setTimeout(
(function (a,b) {
return function () {
// some logic that uses a and b
}
})(a, b), timeDelay);
// loop end
Full working code and demo
Live Demo
window.onload = function () {
var ctx = document.getElementById("ctx").getContext("2d");
function createText(words) {
var LENGTH = words.length;
var LOOPS = LENGTH;
var reader = 0;
var timeDelay = 100;
position = 10;
while (LOOPS > 0) {
letter = words.substr(reader, 1);
setTimeout((function (letter, position) {
return function () {
ctx.fillText(letter, position, 10);
}
})(letter, position), timeDelay);
position += 6;
reader += 1;
LOOPS -= 1;
timeDelay += 100;
}
}
createText("Hello, how are you?");
}

Clear setTimeout or reset function

I have a timer function and I want to clear the timeouts or reset the function, cause every time I execute it, a new timeouts are created, so I recieve several counts.
My idea is to reset the count every time I execute the function. I only want a 1 instance of timer and get the correct count. If if execute several times the function I want to restart to 0.
Here is my code:
var timeouts = new Array();
var timer = null;
io.sockets.on('connection', function (client)
{
client.on("start", function (){
console.log('Someone has pressed Start button',new Date().getTime());
//try to kill all timeouts
for (var timeout in timeouts) {
clearTimeout(timeout);
};
if(this.timer == null) {
this.timer = new timer(1000, function (data) {
io.sockets.emit('timeupdate', data);
})
}else {
this.timer = null;
});
});
function timer(delay, callback)
{
// self-reference
var self = this;
if (!(this instanceof timer)) {
return new timer();
}
// attributes
var counter = 0;
var start = new Date().getTime();
/**
* Delayed running of the callback.
*/
function delayed()
{
console.log(counter);
callback(counter);
counter ++;
var diff = (new Date().getTime() - start) - counter * delay;
var timeOut = setTimeout(delayed, delay - diff);
timeouts.push(timeOut);
}
// start timer
delayed();
var timeout = setTimeout(delayed, delay);
timeouts.push(timeout);
}
Thank you in advance.
Using clearTimeout() is the correct way. The problem is your for-loop. This might look like a classic foreach-loop, but it is not. You have to do:
for (var i=0; i< timeouts.length; i++) {
clearTimeout(timeouts[i]);
}
Alternatively, also I don't like this personally:
for (var i in timeouts) {
clearTimeout(timeouts[i]); // note how the array is indexed using var i
}
This is a common JavaScript pitfall - the for (x in y)-loop actually iterates over the array's indices, not the values. It can also iterate over an object's properties. Try it out:
var a = [3, 2, 5, 8];
for (var i in a) {
console.log(i);
console.log(a[i]);
}
var o = { test: 'hello', number: 1234 };
for (var x in o)
console.log(x);

creating a timer using setInterval that can clean up after itself?

I want to use setInterval to animate a couple things. First I'd like to be able to specify a series of page elements, and have them set their background color, which will gradually fade out. Once the color returns to normal the timer is no longer necessary.
So I've got
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(itvlH);
// but itvlH isn't in scope...?
}
},50);
}
Further complicating the situation is I'd want to be able to have multiple instances of this going on. I'm thinking maybe I'll push the live interval handlers into an array and clean them up as they "go dead" but how will I know when they do? Only inside the interval closure do I actually know when it has finished.
What would help is if there was a way to get the handle to the interval from within the closure.
Or I could do something like this?
function intRun() {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// now I can access an array containing all handles to intervals
// but how do I know which one is ME?
clearInterval(itvlH);
}
}
var handlers = [];
function setFadeColor(nodes) {
var x = 256;
handlers.push(setInterval(intRun,50);
}
Your first example will work fine and dandy ^_^
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
clearInterval(itvlH);
// itvlH IS in scope!
}
},50);
}
Did you test it at all?
I've used code like your first block, and it works fine. Also this jsFiddle works as well.
I think you could use a little trick to store the handler. Make an object first. Then set the handler as a property, and later access the object's property. Like so:
function setFadeColor(nodes) {
var x = 256;
var obj = {};
// store the handler as a property of the object which will be captured in the closure scope
obj.itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(obj.itvlH);
// but itvlH isn't in scope...?
}
},50);
}
You can write helper function like so:
function createDisposableTimerInterval(closure, delay) {
var cancelToken = {};
var handler = setInterval(function() {
if (cancelToken.cancelled) {
clearInterval(handler);
} else {
closure(cancelToken);
}
}, delay);
return handler;
}
// Example:
var i = 0;
createDisposableTimerInterval(function(token) {
if (i < 10) {
console.log(i++);
} else {
// Don't need that timer anymore
token.cancelled = true;
}
}, 2000);

Categories