When this function is called, the style change on the "gif" element does not show up until "lotsOfProcessing()" finishes. However, when I uncomment the alert("test"), the style change is shown before the alert pops up.
What I am trying to do is have an animated gif displayed while lotsOfProcessing is running. This seemed pretty straight forward solution but it is clearly not working. Any suggestions / solutions?
function nameOfFuntion()
{
document.getElementById("gif").style.display = "inline";
//alert("test");
lotsOfProcessing();
}
JavaScript code executes on the same thread as the browser's rendering. Everything that needs to be drawn waits for JavaScript execution to complete - including the next frame of any GIF animation.
The only solution is to break your long processing code down into smaller parts and delay each part using timers.
For example:
function nameOfFuntion() {
document.getElementById("gif").style.display = "inline";
//alert("test");
lotsOfProcessing();
}
function lotsOfProcessing() {
var i = 0;
window.setTimeout(function () {
partOfIntenseProcessing();
if (i < 1000000)
i++, window.setTimeout(arguments.callee, 10);
}, 10);
}
This will delay how long it will take for your processing to complete, but between timer execution the GIF can continue to animate.
You can also take a look at Web Workers, which allow you to run JavaScript operations in a background thread. However, they are not widely implemented yet (read: not available in Internet Explorer).
Perform your heavy processing in a delayed function with window.setTimeout():
function nameOfFunction()
{
document.getElementById("gif").style.display = "inline";
window.setTimeout(lotsOfProcessing, 10);
}
That's strange indeed. Seems like lotsOfProcessing gets javascript's single thread before the dom has time to refresh, but it's the first time I hear of something like that.
You might try this (not that is not an ideal solution):
function nameOfFuntion()
{
document.getElementById("gif").style.display = "inline";
setTimeout(lotsOfProcessing, 100);
}
This is a vaguely educated guess but it may be worth trying to put document.getElementById("gif").style.display = "inline"; into a function eg.
function nameOfFuntion()
{
showGif();
//alert("test");
lotsOfProcessing();
}
function showGif() {
document.getElementById("gif").style.display = "inline";
}
My thinking is that perhaps the lotsOfProcessing() is getting hoisted to the top of nameOfFunction() because it's a function and therefore getting processed first.
Related
I have a function that takes a little while to run, so I'm trying to set the display value of a div (that basically says "please wait") to "block" at the start of the function and then to "none" when the function is complete to alert the user that the function is running.
The display setting is being set properly and I can see the changes in the console log; however, I cannot get it to redraw until after the function is complete. After hours of scouring forum answers to similar questions, it seems like my best option is to call window.getComputedStyle or element.offsetHeight, but these don't seem to be working.
Here is my code:
function renumber(){
notif = document.getElementByID("notification"); //this is my div
notif.style.display = "block";
window.getComputedStyle(notif, null).height; //I've tried this as well as...
notif.offsetHeight; //trying this seperately
/////////big loop that takes a long time to run/////////
notif.style.display = "none";
}
I have also tried setting the CSS instead, and then using window.getComputedStyle and element.offsetHeight:
doc = document.styleSheets[6].cssRules[0].style;
disp = doc.setProperty('display','block');
window.getComputedStyle(notif, null).height; //I've tried this as well as...
notif.offsetHeight; //trying this seperately
And, I have even tried abandoning my notification div and using setTimeout() and alert(), but even that doesn't display until the function is complete (it doesn't matter how much time I put in):
function renumber(){
setTimeout(function() { alert("my message"); }, 50);
////big loop that takes a long time to run////
}
So with all of that, my question is why is window.getComputedStyle and element.offsetHeight not redrawing and making my div display the way the values are set within the function? Everything I've seen online sounds like simply calling one of those should force a redraw, so I'm confused why I'm not seeing a redraw. Is there a better way I should be doing this?
Would tell problem is JS is single threaded, you block UI by your slow operation.
Do not know if there is a process events signal to UI common in frameworks like Qt, etc., but setTimeout can help here (may not work perfect here in snippet, slow part takes around 6s in my Chrome):
function renumber(){
notif = document.getElementById("notification"); //this is my div
notif.style.display = "block";
setTimeout(long, 0, notif);
}
renumber();
function long(notif) {
console.log("Long starts", new Date());
for(var a=0;a<1000000000;a++) if(a % 10) a++; else a += 0.1;
console.log("Long end", new Date());
notif.style.display = "none";
}
<div id="notification" style="display: none;">
Wait please !
</div>
I have a block of code that executes when a button is clicked. The code uses a loop that sometimes takes a while to complete. When the user clicks the button, I want the cursor to change a "wait" cursor before the loop begins. Once the loop is finished, the cursor should return to normal.
What is actually happening (in Chrome for Windows at least) is that the style doesn't get updated until after the loop. It seems to be a quirk of how buttons work. I really don't know. I'm out of guesses!
A sample fiddle: http://jsfiddle.net/ra51npjr/1/ (it just uses console.log to execute "something"... you might need to change how many times the loop runs depending on how zippy or slow your machine is).
Sample HTML:
<div class="fakebody">
<button id="foo">Foo</button>
</div>
Sample CSS:
.fakeBody {
height: 1000px;
width: 100%;
}
.wait {
cursor: wait !important;
}
Sample JavaScript:
$('#foo').on('click', function (e) {
$('.fakebody').addClass('wait');
for (i = 0; i < 10000; i++) {
console.log(i);
}
$('.fakebody').removeClass('wait');
});
--
Here are my ASSUMPTIONS on how the script should work:
The click happens, which fires up the code. Indeed, if I log "started!" inside the code block, it will correctly log that it has started
The cursor should be a wait cursor so long as it is hovering anywhere over "fakebody".
The for loop is just a simple way to kill a few seconds to see the effect. Feel free to substitute any other loop that takes a while to complete
At the end of the loop, the cursor is no longer a wait cursor
What is actually happening:
The loop executes
At the end of the loop, the cursor turns to a "wait" cursor and then instantly back to a regular cursor. The change doesn't happen until the loop is complete
Does anybody know a technique or workaround to get the cursor to change before the loop starts instead of only after it is finished? Is this known behaviour that I need to educate myself about (and if so, do you know where I should start looking?)
This is a common issue in JavaScript. This question may provide some deeper insight, but essentially the point is that synchronous JavaScript execution must finish before the browser can perform other actions (like updating the view).
Because .addClass, the for loop, and .removeClass all occur synchronously, the browser doesn't get a chance to redraw anything. A technique that is often used in these cases is to setTimeout with a timeout of 0, which essentially just "yields" control back to the browser.
$('.fakebody').addClass('wait');
setTimeout(function() {
for (i = 0; i < 10000; i++) {
console.log(i);
}
$('.fakebody').removeClass('wait');
}, 0);
If this is a common pattern, you could potentially extract it out to a function (which would also help improve readability) that wraps the async setTimeout. Here's a simple example:
/**
* Wraps a long-running JavaScript process in a setTimeout
* which yields to allow the browser to process events, e.g. redraw
*/
function yieldLongRunning(preFn, fn, postFn, ctx) {
if (arguments.length <= 2) {
ctx = fn; fn = preFn;
preFn = postFn = function() {};
}
preFn.call(ctx);
setTimeout(function() {
fn.call(ctx);
postFn.call(ctx);
}, 0);
}
And use it like so:
yieldLongRunning(function() {
$('.fakebody').addClass('wait');
},
function() {
for (i = 0; i < 10000; i++) {
console.log(i);
}
},
function() {
$('.fakebody').removeClass('wait');
});
As a side point, note that setTimeout(..., 0) simply queues the function in the browser's event loop, alongside other queued JavaScript functions, as well as other types of events (like redraws). Thus, no setTimeout call is guaranteed to run precisely at the given time - the timeout argument is simply a lower-bound (and, in fact, there is a minimum timeout of 4ms specified by HTML5 spec, which browsers use to prevent infinite timeout loops; you can still use 0, though, and the browser will add it to the event queue after the minimum delay).
I think you should try to force a redraw by hiding + showing the parent element.
Try this:
document.getElementById('fakebody').style.display = 'none';
document.getElementById('fakebody').style.display = 'block';
Before and after the loop (i.e. when you want the child element "foo" to refresh.
EDIT: Since you're using jquery you could do this:
$('#fakebody').hide().show(0);
Demo - Use queue & dequeue to construct an order of what should happen when in jQuery.
$('#foo').on('click', function (e) {
$('.fakebody').addClass('wait').queue(function(n) {
for (i = 0; i < 10000; i++) { console.log(i); }
}).removeClass('wait').dequeue();
});
I have a very heavy graphical issue to perform, and I need to be able to show an onscreen progress bar and also prevent the browser from getting "freeze".
I understand that tight looping is blocking the UI, and JavaScript is single threaded, so I using setTimeout in order to perform some graphical testing as follow:
function FG_ShowHM(y) {
for(var x=0 ; x<100 ; x++) {
if(FG_TreeH[y*100+x]=="") {
FG_hmctx.fillStyle = "rgba(255,255,255,1)";
}
else {
var col=DegToCol(FG_min,FG_max,FG_TreeH[y*100+x]);
FG_hmctx.fillStyle = "rgba("+col.r+","+col.g+",0,1)";
}
FG_hmctx.fillRect(x*3,y*3,3,3);
}
ProgBT+=0.5;
y++;
if(y<100) {
window.setTimeout(FG_ShowHM(y),100); // move on
}
else {
XPW();
}
}
And a call to that function from within another function:
window.setTimeout(FG_ShowHM(0));
NOTE: PW() is just a shortcut to jQuery functions that creating the "please wait evement, and XPW is just a shortcut to remove the "please wait" window.
For some reason the UI is still stack without possibility to show any progress, and more than that, after few seconds the browser get completely "freeze"...
I have tried many many ways to solve this issue, but without success.... I would like to know what is the best way to show up progress in such a long operation, or at least prevent the browser from getting "freeze".
Thanks in advance.
The problem is the way you use window.setTimeout. As a first argument it expects a function and a number as a 2nd. When you do window.setTimeout(FG_ShowHM(y),100); you actually don't pass a function as a parameter but execute it and the result of the execution pass to window.setTimeout. As a result - you have an infinite recursion.
To fix it - correct the way of calling window.setTimeout to
window.setTimeout(function() { FG_ShowHM(y) }, 100);
Note: there are a lot of places in your code of such window.setTimeout usage. So be attentive.
Read more about window.setTimeout here.
I am trying to make a simple hidden object game using javascript. When the user finds and clicks an image, I want 3 things to happen in the following order; a sound plays, the image size increases, and the image goes invisible. The problem I am running into is getting the 3 events to happen sequentially, not concurrent. Right now, seems that all three events happen all at the same time.
I've tried using setTimeout(), and while that does create a delay, it still runs all functions at the same time, even if each function is nested in setTimeout.
Example: (all this does is waits 1.5 sec then plays the sound and makes the image invisible):
function FindIt(image, id){
var t = setTimeout('sound()',10);
var b = setTimeout('bigger(' + image + ')',30);
var h = setTimeout('hide(' + image + ')',1500);
}
Below are the functions I am currently using and the actual results are: click the image, nothing happens for 2 seconds, then the sound plays and the image goes invisible.
function FindIt(image, id){
sound();
bigger(image);
hide(image);
}
function sound(){
document.getElementById("sound_element").innerHTML= "<embed src='chime.wav' hidden=true autostart=true loop=false>";
}
function bigger(image){
var img = document.getElementById(image);
img.style.width = 112;
img.style.height = 112;
}
function hide(id){
var ms = 2000;
ms += new Date().getTime();
while (new Date() < ms){} //Create a 2 second delay
var img = document.getElementById(id);
img.style.visibility='hidden';
}
Any guidance would be greatly appreciated!
To trigger things sequentially, you need to execute the second item some amount of time after the first one completes, execute the third item some amount of time after the second one completes, etc...
Only your sound() function actually takes some time, so I'd suggest the following:
function FindIt(image, id){
sound();
// set timer to start next action a certain time after the sound starts
setTimeout(function() {
bigger(image);
// set timer to start next action a certain time after making the image bigger
setTimeout (function() {
hide(image);
}, 1000); // set this time for how long you want to wait after bigger, before hide
}, 1000); // set the time here for how long you want to wait after starting the sound before making it bigger
}
FYI, the animation capabilities in libraries like jQuery or YUI make this sort of thing a lot easier.
Also, please don't use this kind of construct in your JS:
while (new Date() < ms){}
That locks up the browser for that delay and is very unfriendly to the viewer. Use setTimeout to create a delay.
For reference, using the animation libraries in jQuery, the jQuery code to handle a click on the object and then animate it over a 2 second period to a larger size, delay for 1 second, then slideup to disappear is as follows:
$("#rect").click(function() {
$(this).animate({height: 200, width: 400}, 2000).delay(1000).slideUp();
});
jQuery manages an animation queue and handles setting all the timers and doing all the sequencing and animation for you. It's a lot, lot easier to program and gives a very nice result.
You can see it work and play with it here: http://jsfiddle.net/kC4Mz/.
why don't use "event" approach. like onTaskDone();
function task1(arg, onTask1Done){
console.log(arg);
if(onTask1Done)onTask1Done();
}
task1("working", function(){console.log("task2");});
The Frame.js library is designed to elegantly handle situations like this:
function FindIt(image, id){
Frame(10, function(next) { sound(); next(); });
Frame(30, function(next) { bigger(image); next(); });
Frame(1500, function(next) { hide(image); next(); });
Frame.start();
}
Frame.js offers many advantages over using standard timeouts, especially if you are doing a lot of this kind of thing, which for a game, you likely are.
https://github.com/bishopZ/Frame.js
The following works correctly:
alert("start");
loadData(); // takes a while
alert("finished");
loadData() is a method that inserts large amounts of data into a table in the DOM and takes a few seconds.
This, however, does not work as expected:
document.getElementById("mydiv").style.display = "block";
loadData(); // takes a while
document.getElementById("mydiv").style.display = "none";
The data is loaded into the table without displaying mydiv until the loading is completed and then mydiv is quickly displayed and gone.
But this works like it's supposed to:
document.getElementById("mydiv").style.display = "block";
alert("start");
loadData(); // takes a while
alert("finish");
document.getElementById("mydiv").style.display = "none";
mydiv is displayed, the alert dialog is shown, the data is loaded into the table, and then mydiv disappears, as expected.
Anyone know why the second code section above does not work properly?
I dont know the exact reason, but i can think of is that LoadData is a heavy function so browser is busy evaluating that, so it holds the rendering. When u give alert in between it provides sufficient time to render div and then evaluate LoadData.
Work Around:
function LoadData()
{
//Show Div here
//window.setTimeout(newFunc,100);
}
function newFunc()
{
//Do data operations here
//Hide Div
}
Think of javascript as inserting some interpreted code into a larger program. The larger program looks a little like this:
void eventLoop () {
struct event event;
while(event = getEvent()){
runJavascript(event);
runBrowserCode();
if(viewHasChanged) {
redrawViewRegions();
}
}
}
This isn't really accurate at all, but it gives you a little bit of an idea. While javascript code is running, the browser is not doing anything else: including updating the view. it hasn't got up to that bit. The browser gui does not run in a seperate asynchronous thread, in parallel to the javascript as you seem to imagine. The browser has to wait for javascript to finish before it can do anything.
You can see the gui update when you use alerts, because, well- To display an alert you have to update the gui! so it's kind of a special case.