How can I optimize this recursive function which calls setTimeout() - javascript

The following function is using 100% of a CPU core on my computer. Is there a way I could rewrite it to be non-recursive? Would that fix it or is it because my CPU sucks? Are others seeing the same performance problems on their computer?
Code:
<html>
<head>
<script type="text/javascript" src="jquery-1.7.1.js"></script>
<script type="text/javascript">
function timeMsg(n,max,delay)
{
writeToLog(n + "th: &#" + n,n);
var temp = n + 1;
if(n < max){
var t=setTimeout("timeMsg(" + temp + "," + max + "," + delay + ")",delay);
}
}
function writeToLog(text,n){
$("#log").html($("#log").html() + text + "<br/>");
//autoscrolling: doesn't work...'
}
</script>
</head>
<body>
<form>
<input type="button" value="Display alert box in 3 seconds" onClick="timeMsg(0,100000,100)" />
</form>
<div id="log"></div>
</body>

Instead of repeatedly calling setTimeout with slightly-different values, you could setup a global variable to track the current iteration, and use one call to setInterval() instead. setInterval is like setTimeout, except it will keep running indefinitely - you don't have to call it over and over again. That might help, a bit.

That should not be using 100% CPU. (But the actual performance is dependent on the size of your document.) However, one obvious improvement is to get rid of the implicit eval in setTimeout:
function timeMsg(n, max, delay) {
writeToLog(n + "th: &#" + n, n);
var temp = n + 1;
if(n < max) {
var t = setTimeout(function() {
timeMsg(temp, max, delay);
}, delay);
}
}
You should also use append in place of the double-html (anti-)pattern you're currently employing:
function writeToLog(text,n){
$("#log").append(text + "<br/>");
}
There's no need to search for #log twice (reading its html both times).

Use setInterval() to start and clearInterval() when you reach your max.
Demo: http://jsfiddle.net/ThinkingStiff/PwASf/
Script:
var timer;
function startMsg( n, max, delay ) {
timer = window.setInterval( function() {
timeMsg( n++, max );
}, delay );;
};
function timeMsg( temp, max ) {
writeToLog(temp + "th: &#" + temp, temp);
if( temp == max ){
window.clearInterval( timer );
}
}
function writeToLog(text,n){
$(' #log' ).append( text + '<br/>' );
}
startMsg( 1, 10, 1000 );
HTML:
<div id="log"></div>

No, even as written, it doesn't use 100% CPU for me.
First thing you should do is change your writeToLog function. It is must faster to append an HTML block than to 1) read the existing HTML, 2) add the text+BR, 3) write the whole thing back in.
function writeToLog(text,n){
$("#log").append(text + "<br/>");
//autoscrolling: doesn't work...'
}
Other than that, if you want to not block other JS from running, then you need to use the setTimeout/setInterval method of implementing this loop. Otherwise a regular for loop is faster.

Related

How can I slow down this js with setTimeout?

I want to slowly rotate the letters in a string using 2 second pauses.
I'm trying to use setTimeout(function, delay) but it doesn't pause it runs the entire script in under a second. How can I do this?
My code is at https://jsfiddle.net/6un4xhuj/
<html>
<head>
<link type="text/css" href="style.css">
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<h1>JS</h1>
<h2 id='for'>For: String Rotations</h2>
<h3 id='result'>Result:</h3>
<h4></h4>
<span id="end_of_results"></span>
<script src="main.js"></script>
</body>
</html>
js:
'use strict';
function display(string) {
$("span#end_of_results").before( $( "<h4>" + string + "</h4>" ) );
}
var string="rotate me";
for (var i = 0; i <= string.length; i++) {
setTimeout(display(string), 2000);
string=string[0,string.length - 1] + string.slice(0,-1);
}
I get this output but all of it immediately, not step by step.
Get rid of the loop and just recurse the same function passing in the string each time. This code repeats without end so you might want to add a condition as per your requirements.
function thing(string) {
string = string[0, string.length - 1] + string.slice(0,-1);
$("h2#result").html( $( "<h3>" + string + "</h3>" ) );
setTimeout(thing, 2000, string);
}
thing(string);
DEMO
setTimeout takes a function in your argument. In your code setTimeout(display(string), 2000);, you pass the result of invoking the function display(string) into setTimeout. To make this more evident, assign it to a variable like this:
var result = display(string); // undefined
setTimeout(result, 2000); // Not right
You'll need to use something like bind, or just create a function that returns a function:
setTimeout(function () {
// Modify your string here
string=string[0,string.length - 1] + string.slice(0,-1);
display(string)
}, 2000);
setTimeout is async, which means the for loop is completing immediately. Also... you are calling display directly instead of passing the function reference. The same algorithm using callbacks would be like this:
jsfiddle: https://jsfiddle.net/w1x0csp9/
'use strict';
var i = 0;
var string="rotate me";
function display() {
if(i > string.length) {
return;
}
$("span#end_of_results").before( $( "<h4>" + string + "</h4>" ) );
string = string[0,string.length - 1] + string.slice(0,-1);
i++;
setTimeout(display, 2000);
}
display();
I've accepted andy's answer, also based on it, I create this version to only scroll once:
<h1>JS</h1>
<h2 id='for'>For: String Rotation</h2>
<h2 id='result'>Result:</h2>
'use strict';
function rotate(string) {
string = string[0, string.length - 1] + string.slice(0,-1);
$("h2#result").html( $( "<h3>" + string + "</h3>" ) ).animate(100);
rotations+=1
if (rotations > string.length) {
return;
}
setTimeout(rotate, 50, string);
}
var string="rotate this string ";
var rotations=0
rotate(string);
fiddle at https://jsfiddle.net/xb865yw6/3/
You are beginning the 2 second wait immediately for all instances of setTimeout. The simplest way to fix your code is to increase the wait each time you go through your loop:
change this:
setTimeout(display(string), 2000);
to this:
setTimeout(display(string), (i+1)*2000);
Alternatively you could make a single HTML element update on a 2 second interval using setInterval() like this:
<div class="rotating-text"> rotate me</div>
<script>
setInterval(function(){
$('.rotating-text').each(function(){
var string = $(this).text();
string = string[0,string.length - 1] + string.slice(0,-1);
$(this).text(string);
});
}, 2000);
</script>
This example applies to everything with a class rotating-text
http://codepen.io/t3hpwninat0r/pen/eZVYqw
You can use .queue() , .delay()
"use strict";
var elem = $("h2#result");
var string = "rotate this string";
elem.append("<br>" + string)
.queue("_fx", $.map(Array(string.length), function(value) {
return function(next) {
string = string[0,string.length - 1] + string.slice(0,-1);
$(this).delay(2000).queue(function() {
$(this).append("<br>" + string).dequeue();
next()
})
}
})).dequeue("_fx")
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<h1>JS</h1>
<h2 id='for'>For: String Rotation</h2>
<h2 id='result'>Result:</h2>

Looping through Video Sources slows Chrome

I have two video sources that are outputted to my page via ajax (the second is hidden). I play the first one and bind an event onto it so that when it has ended the next video is played and shown whilst the first is then paused, rewinded back to 0, and the process continues.
here is my code.
function queueVideos(num, amount) {
if (num < amount) {
document.getElementById('video-element-'+num).addEventListener('ended', playNextClip, false);
function playNextClip() {
var nextVid = num + 1;
$( '#video-element-' + nextVid ).show().get(0).play();
$( '#video-element-' + num ).hide();
document.getElementById( 'video-element-' + num ).pause();
document.getElementById( 'video-element-' + num ).currentTime = 0;
queueVideos(nextVid, amount)
}
}
if (num == amount) {
document.getElementById('video-element-'+num).addEventListener('ended', playFirst, false);
function playFirst() {
$( '#video-element-1' ).show().get(0).play();
$( '#video-element-' + num ).hide();
document.getElementById( 'video-element-' + num ).pause();
document.getElementById( 'video-element-' + num ).currentTime = 0;
queueVideos(1, amount);
}
}
}
The code is called within an ajax function that feeds it the num and amount from data i get elsewhere, for arguments sake let's say i'm running it like so:
queueVideos(1, 2);
My problem is that after around 1 minute the transitions between the clips get laggy, the clips pause unexpectedly, and ultimately the browser (chrome) performs slowly.
How can I fix this?
Thanks!
I found that changing the function playFirst() to not include queueVideos(1, amount); fixed the problem! I didn't realize that i only needed to add the event listener once. Woohoo!

Having trouble getting my HTML onload to call my JS function

I have a function that should be called whenever my html page loads. The <body> tag should call the startAdPage() function. However, nothing at all happens. I am not completely sure why. Any suggestions? Here is the <body> call in my HTML page
<body onload="startAdPage();">
Next, here is my startAdPage() function, as well as the two functions it calls. Those two aren't completely finished yet. One of them is supposed to create a small image gallery slide show. The other is supposed to create a countdown from 10 - 1 before displaying a separate web page. Neither work yet, so any advice on them would also be appreciated, though they aren't my main focus yet.
function startAdPage(){
setInterval(changeAd(), 2000);
setInterval(startCountdown(), 1000);
}
Here is changeAd()
function changeAd() {
for(var i = 1; i < 4; i++){
document.images[0].src = "images/pic" + intNumber + ".jpg";
}
}
Lastly, startCountdown(). I haven't made the webpage that I said this function calls yet
function startCountdown() {
window.alert('Test');
for(var i = 10; i >= 1; i++){
document.getElementById("countdown").value = i;
}
}
Your problem is right here:
document.images[0].src = "images/pic" + intNumber + ".jpg";
You refer to intNumber but you should be using i instead. So change the above to
document.images[0].src = "images/pic" + i + ".jpg";
and your function will be called.
You should consider removing the onload in the body tag and do a
window.onload = startAdPage;
instead - to keep your html and Js logic separate.
Also, your countdown logic is wrong - you want to decrement i, not increment:
var i = 10; i>0; i--

How to make my script loop

I created an image slider that ends on one image, but now I'd like to take it a step further and make it loop.
Here is my code in the head tag
<style>
#picOne, #picTwo, #picThree, #picFour, #picFive{
position:absolute;
display: none;
}
#pics {
width:500px;
height:332px;
}
</style>
<script src="http://code.jquery.com/jquery-1.4.4.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#picOne').fadeIn(1500).delay(3500).fadeOut(1500);
$('#picTwo').delay(5000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picThree').delay(10000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picFour').delay(15000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picFive').delay(20000).fadeIn(1500).delay(3500);
});
</script>
and here is where it is implemented in the body code
<div id="pics">
<center>
<img src="img/dolllay.jpg" width="500" height="332" id="picFive" />
<img src="img/dye.jpg" width="500" height="332" id="picTwo" />
<img src="img/dollsit.jpg" width="500" height="332" id="picThree" />
<img src="img/heirloom.jpg" width="500" height="332" id="picFour" />
<img src="img/heritage.jpg" width="500" height="332" id="picOne" />
</center>
</div>
Could I turn it into a function and then loop it? Can I get any guidance on that? Thank you very much
Everyone's answering the question, but not solving the problem.
Sure, you can just put a loop wrapper around it (preferably one that doesn't terminate), but why not just program it right? Why have all the hardcoded times, and why not make it more robust?
Try rewriting your code like this. It makes it much easier to modify the pictures you loop through:
var pictures = ["picOne", "picTwo", "picThree", "picFour", "picFive"];
var index = 0;
var displayImage = function() {
if (index == pictures.length) { return; }
$("#" + pictures[index++]).fadeIn(1500).delay(3500).fadeOut(1500, displayImage);
};
displayImage();
Then, if you want to loop back, you simply tweak the displayImage function:
var displayImage = function() {
if (index == pictures.length) { index = 0; }
$("#" + pictures[index++]).fadeIn(1500).delay(3500).fadeOut(1500, displayImage);
};
TRY IT at jsfiddle
EDIT
On more careful reading of your question, I see that my original answer didn't do exactly what you needed. You have it set so that every five seconds, one will have faded out and the other one will have faded in. Currently, mine takes 6.5 seconds, since mine is all operating sequentially instead of concurrently. To make it come close to matching yours, just change the 1500s to 750s:
$("#" + pictures[index++]).fadeIn(750).delay(3500).fadeOut(750, displayImage);
This will take the right amount of time. It's slightly different from yours, in that one fades out all the way before the other fades in. The alternative is to actually skip the fadeIn and keep the fadeout. This is a lot closer to the way yours looks.
$("#" + pictures[index++]).show().delay(3500).fadeOut(1500, displayImage);
Or, make a very small fadein, to help reduce the flash of the new image:
$("#" + pictures[index++]).fadeIn(100).delay(3500).fadeOut(1400, displayImage);
Final Edit (really!)
Ok, to get the fadeIn and fadeOut to work reliably at the same time, the solution was to use neither. I went back to using animate, instead. As a result, I had to completely rewrite the displayImage function, but this is exactly what you need:
var displayImage = function () {
if (index == pictures.length) {
index = 0;
}
$("#" + pictures[index]).show().delay(3500).animate({
opacity: 0.2
}, {
step: function (now) {
var idx = (index + 1) % pictures.length;
var val = 1.2 - now;
$("#" + pictures[idx]).show().css("opacity", val);
},
complete: function () {
$("#" + pictures[index++]).hide();
displayImage();
}
});
};
What this does is move the sequence to "show->fadeIn and Out" instead of "fade in -> show -> fade out". To make your transition smooth, I only fade it out to 0.2 instead of 0. The step function is what fades the other one in at the same time. Once the new pic is visible, I completely hide the old pic.
Here's the working fiddle for it.
$(document).ready(function() {
setInterval(example, 10000); // repeat every 10 seconds
});
function example() {
$('#picOne').fadeIn(1500).delay(3500).fadeOut(1500);
$('#picTwo').delay(5000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picThree').delay(10000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picFour').delay(15000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picFive').delay(20000).fadeIn(1500).delay(3500);
}
A better way would be to give each pic the same class such as 'fadeinout'. This will mean you don't have to re-write your code when you add/remove more pics.
eg
<img id="picFive" class="fadeinout" ....
/* not sure if they are even <img>s but whatever they are*/
Then do
$(document).ready(function() {
beginFades();
});
function beginFades() {
$('.fadeinout').each( function(i,el) { // find all elements with fadeinout
//for each one, trigger the start of the fading after i*5000 milliseconds
//i is the index of the element as it was found by jQuery - this will be in
//document order (which actually may not be what you have but I'm guessing
//it is)
setTimeout(function(){
makeImgFadeInOut($(el))
}, i*5000);
});
}
function makeImgFadeInOut(el) {
//trigger a single fadeIn, fadeOut.
//But add a callback function to the end of fadeOut which retriggers the whole
//thing
el.fadeIn(1500).delay(3500).fadeOut(1500, function(){makeImgFadeInOut(el);});
}
WORKING DEMO (WITH DIVS)
You can use setInterval to loop it forever, or setTimeout to loop it for a specific duration.
<script type="text/javascript">
$(document).ready(function() {
setInterval(ImageSlider, 1000);
});
function ImageSlider() {
$('#picOne').fadeIn(1500).delay(3500).fadeOut(1500);
$('#picTwo').delay(5000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picThree').delay(10000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picFour').delay(15000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#picFive').delay(20000).fadeIn(1500).delay(3500);
}
</script>
If you want to have total control upon your elements you can use this:
var elements = [{
el: '#pic1',
delay: 3500,
fadeIn: 1500,
fadeOut: 1500
},
{
el: '#pic2',
delay: 3500,
fadeIn: 1500,
fadeOut: 1500
}
//... other elements
]
var index = null;
(function loop(){
index = index || 0;
index = index % elements.length();
$(elements[index].el).fadeIn(elements[index].fadeIn, function(){
$(this).delay(elements[index].delay)
.fadeOut(elements[index].fadeOut, function(){
index++;
window.setTimeout(loop, 5000);
});
})();
Edit : forgot to execute the first iteration of the loop function and removing the useless call for index inside the loop
The good thing about how this loop works is that it doesn't use the SetInterval function.
and the code inside the loop need to finish what it does inside before iterating again.
(you won't have this hideous bug if you click an other tab and go back to your carousel)
#ElRoconno answer is pretty good too if you require less configuration
Use any of this-
setInterval() - executes a function, over and over again, at specified time intervals
setInterval(function(){alert("Hello")},3000);
setTimeout() - executes a function, once, after waiting a specified number of milliseconds.
setTimeout(function(){alert("Hello")},3000);
What is the difference between both setInterval and setTimeout
for you may be the setTimeout will not work as it will run only once after a delay and setInterval will go on to make continuous repetitive call until the window.clearInterval(intervalVariable) is been called
I have created an example on jsfiddler here. Basically you don't have to do this one at a time. Just get the whole collection of images as an array and loop over them. Hope this helps
$(document).ready(function () {
var arr = $('.pics')
arr.hide();
$(arr[0]).fadeIn(1500).delay(3500).fadeOut(1500);
var index = 1;
var maxIndex = arr.length - 1;
setInterval(function () {
/*arr.hide();
var pic = $(arr[index]);
pic.show();
*/
var pic = $(arr[index]);
pic.fadeIn(1500).delay(3500).fadeOut(1500);
index++;
if (index >= maxIndex) {
index = 0;
}
}, 6500);
});
There's really no need for setInterval here since you can use the callback built-into .fadeOut(), nor having to enumerate an array of images. You can do something as simple as:
var idx = 0;
fade();
function fade() {
if (idx >= $('img').length) idx = 0;
$('img').eq(idx).fadeIn(1500).delay(3500).fadeOut(1500, fade);
idx++;
}
jsFiddle example

Write sequence of random numbers in javascript to paragraph

Really a newbie question but I can't seem to find the answer. I need to have this html file show a bunch of random numbers, separated by 1 second intervals. For some reason (maybe obvious) it is only showing me the last one unless I have 1 alert after each random number generated. How can I correct this?
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var randomnumber
var message
function placePossibleWinner()
{
randomnumber=Math.floor(Math.random()*11);
message="Teste ";
message=message.concat(randomnumber.toString());
document.getElementById("WINNER").innerHTML=message;
//alert(".")
}
</script>
</head>
<body>
<script type="text/javascript">
function runDraw()
{
var i=1
alert("start")
while (i<20)
{
setTimeout("placePossibleWinner()",1000)
i++
}
}
</script>
<h1>H Draw</h1>
<p id="WINNER">Draw</p>
<p></p>
<button onclick="runDraw()">Get me winner!</button>
</body>
</html>
Thanks in advance for any answers/comments.
The problem is all your setTimeouts are being triggered at the same time. Adding alerts pauses the JavaScript execution, so you see each number. Without that, after 1 second, all 19 setTimeouts run (one after another) and you just see one number (the screen is updated so fast, you just see one).
Try using setInterval instead.
function runDraw() {
var i = 1;
var interval = setInterval(function(){
if(i < 20){
placePossibleWinner();
i++;
}
else{
clearInterval(interval);
}
}, 1000);
}​
This will run the function once every second, until i is 20, then it will clear the interval.
I believe you want setInterval instead. using setTimeout in a loop will just queue up 20 calls immediately and they will all fire at once 1 second later. Also, you are setting the innerHTML of the p which will overwrite any previous text.
function placePossibleWinner() {
// add a var here, implicit global
var randomnumber=Math.floor(Math.random()*11);
// add a var here, implicit global
message="Teste " + randomnumber + '\n'; // new line
document.getElementById("WINNER").innerHTML += message; // concat, don't assign
}
function runDraw() {
var counter = 1;
var intervalID = setInterval(function () {
if (counter < 20) {
placePossibleWinner();
counter++;
} else {
clearInterval(intervalID);
}
}, 1000);
}
You are resetting your message in your functions and you are calling placePossibleWinner() the wrong way... you want to use setInterval. Below is a modification of your html/javascript
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var randomnumber;
var message = "Teste ";
var timesCalled = 0;
var funtionPointer;
function placePossibleWinner()
{
timesCalled++;
randomnumber=Math.floor(Math.random()*11);
message=message.concat(randomnumber.toString());
document.getElementById("WINNER").innerHTML=message;
if (timesCalled > 20)
{
clearInterval(functionPointer);
}
}
</script>
</head>
<body>
<script type="text/javascript">
function runDraw()
{
var i=1
alert("start")
functionPointer = setInterval(placePossibleWinner,1000)
}
</script>
<h1>H Draw</h1>
<p id="WINNER">Draw</p>
<p></p>
<button onclick="runDraw()">Get me winner!</button>
</body>
</html>
To start with,
setTimeout("placePossibleWinner()",1000)
should be
setTimeout(placePossibleWinner,1000)
The parameter to setTimeput should be a reference to a function. See JavaScript,setTimeout

Categories