I would like to animate an html page with something like this:
function showElements(a) {
for (i=1; i<=a; i++) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
pause(500);
}
}
function pause(ms) {
ms += new Date().getTime();
while (new Date() < ms){}
}
Unfortunately, the page only renders once javascript completes.
If I add
window.location.reload();
after each pause(500); invocation, this seems to force my javascript to exit. (At least, I do not reach the next line of code in my javascript.)
If I insert
var answer=prompt("hello");
after each pause(500), this does exactly what I want (i.e. update of the page) except for the fact that I don't want an annoying prompt because I don't actually need any user input.
So... is there something I can invoke after my pause that forces a refresh of the page, does not request any input from the user, and allows my script to continue?
While the javascript thread is running, the rendering thread will not update the page. You need to use setTimeout.
Rather than creating a second function, or exposing i to external code, you can implement this using an inner function with a closure on a and i:
function showElements(a) {
var i = 1;
function showNext() {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
i++;
if(i <= a) setTimeout(showNext, 500);
}
showNext();
}
If I add window.location.reload(); after each pause(500) invocation, this seems to force my javascript to exit
window.reload() makes the browser discard the current page and reload it from the server, hence your javascript stopping.
If I insert var answer=prompt("hello"); after each pause(500), this does exactly what I want.
prompt, alert, and confirm are pretty much the only things that can actually pause the javascript thread. In some browsers, even these still block the UI thread.
Your pause() function sleeps on the UI thread and freezes the browser.
This is your problem.
Instead, you need to call setTimeout to call a function later.
Javascript is inherently event-driven/non-blocking (this is one of the great things about javascript/Node.js). Trying to circumvent a built in feature is never a good idea. In order to do what you want, you need to schedule your events. One way to do this is to use setTimeout and simple recursion.
function showElements(a) {
showElement(1,a);
}
function showElement(i, max) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < max) {
setTimeout(function() { showElement(i+1, max) }, 500);
}
}
var i = 1;
function showElements(a) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < a) {
setTimeout(function() { showElements(a) }, 500);
}
i++;
}
showElements(5);
function showElements(a,t) {
for (var i=1; i<=a; i++) {
(function(a,b){setTimeout(function(){
document.getElementById(getImageId(a)).style.visibility = 'visible'},a*b);}
)(i,t)
}
}
The t-argument is the delay, e.g. 500
Demo: http://jsfiddle.net/doktormolle/nLrps/
Related
I thought an interval just delayed the function, but as it turns out it actually loops.
When I include some function that stops the interval after the deletor function ends it doesn't trigger that and I still get Test logged to the console.
document.addEventListener("DOMContentLoaded", function(event) {
let fullURL = window.location.href;
//let fullURL2 = window.location.host + window.location.pathname;
if (fullURL === "https://net.adjara.com/" ||
fullURL === "https://net.adjara.com/Home") {
var timer = setInterval(deletor, 5);
function deletor() {
timer;
var slider = document.querySelector("#slider-con");
var bannerTop = document.querySelector("#MainContent > div:nth-child(2)")
var bannerMiddle = document.querySelector("#MainContent > iframe");
var bannerRandom = document.querySelector("#MainContent > div:nth-child(3)");
if (slider) {
slider.parentNode.removeChild(slider);
}
if (bannerTop) {
bannerTop.parentNode.removeChild(bannerTop);
}
if (bannerMiddle) {
bannerMiddle.parentNode.removeChild(bannerMiddle);
}
if (bannerRandom) {
bannerRandom.parentNode.removeChild(bannerRandom);
}
function stopInterval() {
clearInterval(timer);
}
console.log("Test");
/*if ()
clearInterval(timer);*/
};
} else {
return false;
}
});
What you're looking for is setTimeout. It runs only once.
setTimeout(deletor, 5);
Also, you don't need to write timer variable inside of your closure like you would in Python. Javascript captures everything that's inside of lexical scope.
The code you provided works ¯\_(ツ)_/¯
Maybe the problem is coming from what triggers stopInterval()
But as mentioned in comments / other answers, you might be better off with another method
I wouldn't recommend using setTimeout in your case, because it looks like you are simply waiting for some DOM elements to be loaded. The problem with the timeout is that you can't know for sure how fast is the computer that will run your code. Maybe a bad quality phone with an outdated software that will need way more time to run your code than when you test on your personal computer, and that will not have the elements loaded by the time your function will be executed.
jQuery
For this reason, and since you tagged your question with jQuery I think you could use $(elementYouWaitForTheDOM2beLoaded).ready(function2execute) for each element you are watching instead of a having a loop that waits for the elements to be loaded (documentation for "ready" function)
Vanilla JS
And if you want to do it in pure JS it would be document.querySelector(elementYouWaitForTheDOM2beLoaded).on('load', function2execute))
I have this code that uses an inefficientProcess() that consumes plenty of memory:
My goal is to use some sort of setTimeout(function(){...},0) technique so the browser will not get stuck while executing the code.
How do I change the code so it will work with setTimeout?
function powerOfTwo(num) {
inefficientProcess();
if (num > 0) {
return powerOfTwo(num-1)*2;
} else {
return 1;
}
}
function inefficientProcess() {
var sum;
for (var i=0; i < 500000; i++) {
sum+=10;
}
}
powerOfTwo(1000);
My goal is ofcourse to learn how to avoid browser crush when executing heavy calculations.
Javascript is single-threaded, and all your code is blocking.
There is a new standard in HTML5, WebWorkers API, that will allow you to delegate your task to a different thread. You can then pass a callback function to be executed with the result.
https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers
Simple example:
function powerOfTwo(num, callback) {
var worker = new Worker('inneficient.js');
worker.postMessage('runTask');
worker.onmessage = function(event) {
var num = event.data.result;
var pow;
if (num > 0) {
pow = Multiply(num-1)*2;
} else {
pow = 1;
}
callback(pow);
};
}
powerOfTwo(1000, function(pow){
console.log('the final result is ' + pow);
});
in inneficient.js you have something like:
//inneficient.js
function inefficientProcess() {
var sum;
for (var i=0; i < 500000; i++) {
sum+=10;
}
postMessage({ "result": sum});
}
inefficientProcess();
As was mentioned in Andre's answer, there's a new HTML5 standard that will allow you to set off a task on a different thread. Otherwise, you can call setTimeout with a time of 0 to allow the current execution path to finish (and perhaps some UI changes to render) before the inefficientProcess is called.
But whether you can use HTML5 or not, the powerOfTwo function has to be changed to be asynchronous - whoever calls it needs to provide a callback method that will be called when (a) a thread spun up via WebWorkers returns, or (b) the setTimeout method finishes.
Edited to add example:
function powerOfTwo(num, callback)
{
setTimeout(function ()
{
inefficientProcess();
if (num > 0)
callback(Multiply(num-1)*2);
else
callback(1);
}, 0);
}
function inefficientProcess() { ... }
The HTML element allows you to define when the JavaScript
code in your page should start executing. The “async” and “defer”
attributes were added to WebKit early September. Firefox has been
supporting them quite a while already.
Saw that on this Site
function animateGraph() {
var graph;
for(i=0; i<10; i++)
{
var start = new Date();
while((new Date()) - start <= 500) {/*wait*/}
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
}
}
The loop works. The wait works. But the document.getElement is not showing up until the last item in the array...why?
Using setTimeout will allow the code to run and not lock up the page. This will allow it to run the code and will not effect other elements on the page.
var cnt = 0;
(function animateGraph() {
document.getElementById("timeMark").innerHTML = phoneX[cnt].epoch;
cnt++;
if (cnt<10){
window.setTimeout(animateGraph,500);
}
})();
The while loop, waiting for a datetime, is not a good way to wait - it just blocks execution. It keeps the browser (including UI, and its updating) frozen until the script finishes. After that, the window is repainted according to the DOM.
Use window.setTimeout() instead:
function animateGraph(phoneX) {
var el = document.getElementById("timeMark")
var i = 0;
(function nextStep() {
if (i < phoneX.length )
el.innerHTML = phoneX[i].epoch;
i++;
if (i < phoneX.length )
window.setTimeout(nextStep, 500);
})();
}
Please note that this runs asynchronous, i.e. the function animateGraph will return before all phoneXes are shown.
Use setTimeout instead of a while loop.
https://developer.mozilla.org/en/DOM/window.setTimeout
Also try something like this.
Javascript setTimeout function
The following snippet uses a helper function to create the timers. This helper function accepts a loop counter argument i and calls itself at the end of the timer handler for the next iteration.
function animateGraph() {
var graph;
setTimeMarkDelayed(0);
function setTimeMarkDelayed(i) {
setTimeout(function() {
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
if (i < 10) {
setTimeMarkDelayed(++i);
}
}, 3000);
}
}
You actually need some sort of helper function, otherwise you'll end up overwriting the value of i in your for loop in every iteration and by the time your timers run out, i will already be 9 and all handlers will act on the last element in phoneX. By passing i as an argument to the helper function, the value is stored in the local scope of that function and won't get overwritten.
Or you could use setInterval like Radu suggested, both approaches will work.
I have a while loop:
x = true;
while (x == true) {
document.images['id'].src = arr[i];
i = i+1;
x = confirm('do you want to see more?')
}
This shows me each image and then asks if I want to see more on firefox and ie, but in chrome and safari, it only displays the image after I leave the loop. I know this is efficient, but I'm wondering if there's a way to force execution of the line within the loop as I go along?
Thanks for input!
You can add a sequence of setTimeout instead of a loop to force the javascript user thread to stop and thus let the browser refresh the drawing.
var i = 0; // define outside showNextImage to be properly captured by it.
var showNextImage = function() {
document.images['id'].src = arr[i];
i = i+1;
x = confirm('do you want to see more?');
if (true) setTimeout(showNextImage, 10);
};
Two answers:
Don't use confirm
If you really want to use confirm, yield to the browser after updating the image but before the confirm
1. Don't use confirm
The best way is to not use confirm at all; it's antiquated and as you've found it behaves slightly differently on different browsers in terms of whether changes to the page are shown.
Instead, I'd use any of the 350,124 "dialog" libraries that are out there (jQuery UI has a nice one, but again, there are a lot of them), which work asynchronously and so you definitely see the page changes. Your loop would become an asynchronous function, but those aren't all that tricky once you're used to them and the benefits are enormous in terms of the user experience.
function chooseImage(arr, completionCallback) {
var i = 0, imgElement = document.images['id'];
ask();
function ask() {
imgElement.src = arr[i];
showDialog(gotAnswer); // the nature of "show dialog" will depend on which one you use
}
function gotAnswer() {
if (userSaidYes) { // Again, depends on the library you're using
completionCallback(i); // Tell the calling code which one they picked
}
else {
// Is there another?
++i;
if (i >= arr.length) {
// No, tell the user
/* left as exercise */
// Tell calling code none was chosen
completionCallback(-1); // Using -1 as a flag for none
}
else {
// Yes, ask about it
ask();
}
}
}
}
2. Use confirm but yield
The issue is that confirm brings things to a screeching halt while the browser asks the user a question. Changes you've made to the page may not show up while the confirm window is active (as you've seen).
If you really want to use confirm, you can still do that, just yield back to the browser briefly first so that it has time to show the page changes. Note, though, that this still may not be a guarantee, if the image takes a long time to download.
function chooseImage(arr, completionCallback) {
var i = 0, imgElement = document.images['id'];
showAndHandOff();
function showAndHandOff() {
imgElement.src = arr[i];
setTimeout(ask, 0);
}
function ask() {
if (confirm('do you want to see more?')) {
++i;
if (i >= arr.length) {
alert("Sorry, there aren't any more.");
completionCallback(-1);
}
else {
showAndHandOff();
}
}
else {
completionCallback(i);
}
}
}
For example:
var x = true,
i = 0,
fn = function() {
document.images['id'].src = arr[i];
x = confirm('do you want to see more?');
if ( x ) {
i = i+1;
setTimeout(fn, 0)
}
};
fn();
I want to do this either by pure javascript or any sort of console in a browser or whatever.
Is it possible?
Thanks
Further explanations:
I want to debug a library that does animations. I want to know if there's multiple timers created if there are multiple objects being animated.
Note that setTimeout() does not spawn new threads. Browser side scripting is not only single threaded, but the JavaScript evaluation shares the same single thread with the page rendering (Web Workers apart).
Further reading:
How JavaScript Timers Work by John Resig
You may want to build a timer manager yourself:
var timerManager = (function () {
var timers = [];
return {
addTimer: function (callback, timeout) {
var timer, that = this;
timer = setTimeout(function () {
that.removeTimer(timer);
callback();
}, timeout);
timers.push(timer);
return timer;
},
removeTimer: function (timer) {
clearTimeout(timer);
timers.splice(timers.indexOf(timer), 1);
},
getTimers: function () {
return timers;
}
};
})();
Then use it as follows:
var t1 = timerManager.addTimer(function () {
console.log('Timer t1 triggered after 1 second');
}, 1000);
var t2 = timerManager.addTimer(function () {
console.log('Timer t2 triggered after 5 second');
console.log('Number of Timers at End: ' + timerManager.getTimers().length);
}, 5000);
console.log('Number of Timers at Start: ' + timerManager.getTimers().length);
The above will display the following result in the console:
// Number of Timers at Start: 2
// Timer t1 triggered after 1 second
// Timer t2 triggered after 5 second
// Number of Timers at End: 0
Note that the timerManager implementation above uses the Array.indexOf() method. This has been added in JavaScript 1.6 and therefore not implemented by all browsers. However, you can easily add the method yourself by adding the implementation from this Mozilla Dev Center article.
Finally done, it was interesting for me so I spent some time trying to come up with something, and here it's
It overrides browser's setTimeout and fill active status of current active calls in window._activeSetTimeouts hash, with window._showCurrentSetTimeouts() demo function that displays current setTimeout calls that are waiting.
if(typeof window._setTimeout =='undefined') {
window._setTimeout=window.setTimeout;
window._activeSetTimeouts={};
window._activeSetTimeoutsTotal=0;
window._setTimeoutCounter=0;
window._showCurrentSetTimeouts=function() {
var tgt=document.getElementById('_settimtouts');
if(!tgt) {
tgt=document.createElement('UL');
tgt.style.position='absolute';
tgt.style.border='1px solid #999';
tgt.style.background='#EEE';
tgt.style.width='90%';
tgt.style.height='500px';
tgt.style.overflow='auto';
tgt.id='_settimtouts';
document.body.appendChild(tgt);
}
tgt.innerHTML='';
var counter=0;
for(var i in window._activeSetTimeouts) {
var li=document.createElement('LI');
li.innerHTML='[{status}] {delay} ({calltime})<br /><pre style="width: 100%; height: 5em; overflow: auto; background: {bgcolor}">{cb}</pre>'.f(window._activeSetTimeouts[i]);
li.style.background=(counter++%2)?'#CCC' : '#EEB';
tgt.appendChild(li);
}
}
window.setTimeout=function(cb, delay) {
var id = window._setTimeoutCounter++;
var handleId = window._setTimeout(function() {
window._activeSetTimeouts[id].status='exec';
cb();
delete window._activeSetTimeouts[id];
window._activeSetTimeoutsTotal--;
}, delay);
window._activeSetTimeouts[id]={
calltime:new Date(),
delay:delay,
cb:cb,
status:'wait'
};
window._activeSetTimeoutsTotal++;
return id;
}
//the following function is for easy formatting
String.prototype.f=function(obj) {
var newStr=this+'';
if(arguments.length==1) {
if(typeof(obj)=='string') {
obj={x:obj};
}
for(var i in obj) {
newStr=newStr.replace(new RegExp('{'+i+'}', 'g'), obj[i]+'');
}
newStr+='';
} else {
for(var i=0; i<arguments.length; i++) {
newStr=newStr.replace('{'+(i+1)+'}', arguments[i]);
}
}
return newStr;
}
}
//following line for test
for(var i=0; i<5; i++) setTimeout(window._showCurrentSetTimeouts, 3000*i);
As others have mentioned, setTimeout doesn’t spawn a thread. If you want a list of all the timeout ids (so you can cancel them, for example) then see below:
I don’t think you can get a list of all timeout ids without changing the code when they are called. setTimeout returns an id—and if you ignore it, then it's inaccessible to your JavaScript. (Obviously the interpreter has access to it, but your code doesn't.)
If you could change the code you could do this:
var timeoutId = [];
timeoutId.push(setTimeout(myfunc, 100));
…Making sure that timeoutId is declared in global scope (perhaps by using window.timeoutId = []).
Just off the top of my head, but to reimplement setTimeout you’d have to do something like this:
var oldSetTimeout = setTimeout;
setTimeout = function (func, delay) {
timeoutId.push(oldSetTimeout(func, delay));
}
This isn’t tested, but it gives you a starting point. Good idea, molf!
Edit: aularon's answer gives a much more thorough implementation of the above idea.