Cannot force DOM redraw using window.getComputedStyle or element.offsetHeight? - javascript

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>

Related

Javascript while loop changing scroll position of div crashes site

I have a div displaying some horizontally scrollable images with white-space:nowrap; overflow-x:scroll and i'm trying to make the function below work:
var mouseIsInDiv = false;
function autoScroll() {
var i = 1;
while (mouseIsInDiv = false) {
setTimeout(function(){
document.getElementById("theDiv").scrollLeft = i;
i++;
},50);
}
}
It is supposed to loop through (while the mouse is not within this scrollable div) incrementing the scroll position by 1px every 50 miliseconds. In other words it's supposed to scroll through the images automatically when this function is called. I'm not getting any syntactic errors but whenever i press a button that calls this function on a webpage, the browser crashes completely - I'm using the latest versions of Chrome, Safari and Firefox. Any ideas would be really helpful, I've been tearing my hair out over this!
Your loop creates many timeouts that happens in the same time (after 50 milisecs) you need to set the timeout recursivly, inside the set timeout function, and ask if mouseISInDiv inside the set timeout function as well.
The current code state, the loop will run many many times in a small amount of time, and page will crush(it's liek infinite) and after 50 millisecs there will be many set timeouts that ran.
I had a fun time working on this one, so I'll post my response despite the correct answer already having been accepted.
Basically, you need to restructure everything so that the whole scheme is asynchronous. That means event listeners respond to mouse movement, and there are no while loops.
Thus, I present this fiddle. Here is the javascript:
var mouseIsInDiv = false;
var theDiv = document.getElementById("theDiv");
theDiv.onmouseover = function() { mouseIsInDiv = true; };
theDiv.onmouseout = function() {
mouseIsInDiv = false;
scrollLeft1();
};
function scrollLeft1() {
if (mouseIsInDiv == false && theDiv.scrollLeft < theDiv.clientWidth) {
theDiv.scrollLeft += 1;
setTimeout(scrollLeft1, 50);
}
}
scrollLeft1();
As you can see, the function calls itself recursively and asynchronously, and the whole thing can be restarted after manually resetting the scroll. You could also add an event listener for the scroll completion.

Problem with setInterval

I have a problem with setInterval on a 'click' event. Im already spawning a hidden div. But I want spawn and fade in synced and only run the fade in ones.
var anchor = document.getElementById('anchor');
var hiddenElement = document.getElementById('hiddenElement');
var bkgFade = document.getElementById('bkgFade');
anchor.addEventListener('click', spawnImage, false);
hiddenElement.addEventListener('click', despawnImage, false);
function spawnImage() {
setInterval(function() {
document.getElementById('hiddenElement').style.display = 'block';
document.getElementById('bkgFade').style.visibility = 'visible';
hiddenElement.style.opacity += 1.0;
} 1000);
}
function despawnImage() {
document.getElementById('hiddenElement').style.display = 'none';
document.getElementById('bkgFade').style.visibility = 'hidden';
}
This piece of code makes no sense to me:
function spawnImage() {
setInterval(function() {
document.getElementById('hiddenElement').style.display = 'block';
document.getElementById('bkgFade').style.visibility = 'visible';
F hiddenElement.style.opacity += 1;
});
clearInterval();
}
There are multiple things wrong with it.
You aren't setting a time for setInterval().
You aren't saving the return value from setInterval().
You aren't passing anything to clearInterval(). It should be passed the return value from setInterval(). It won't do anything the way you have it.
This line isn't legal javascript: F hiddenElement.style.opacity += 1;
I can't understand why you would setInterval() and then immediately clearInterval()
Adding 1 to the opacity in a timer interval isn't going to accomplish anything. That's just going to make it visible in one giant step.
Manipulating opacity directly won't work in some versions of IE.
You're using document.getElementById('hiddenElement') in one place and hiddenElement in another place. If hiddenElement is valid, use that both places. If not, use document.getElementById('hiddenElement') both places.
If you describe in more detail what you're really trying to accomplish with this code, folks might be able to help you come up with correct code.
I would highly recommend that animation be done with a library like jQuery or YUI as it's way, way easier to use and make it work cross-browser.

When using setInterval, if I switch tabs in Chrome and go back, the slider goes crazy catching up

I have a jQuery slider on my site and the code going to the next slide is in a function called nextImage. I used setInterval to run my function on a timer, and it does exactly what I want: it runs my slides on a timer. BUT, if I go to the site in Chrome, switch to another tab and return, the slider runs through the slides continuously until it 'catches up'. Does anyone know of a way to fix this. The following is my code.
setInterval(function() {
nextImage();
}, 8000);
How to detect when a tab is focused or not in Chrome with Javascript?
window.addEventListener('focus', function() {
document.title = 'focused';
},false);
window.addEventListener('blur', function() {
document.title = 'not focused';
},false);
To apply to your situation:
var autopager;
function startAutopager() {
autopager = window.setInterval(nextImage, 8000);
}
function stopAutopager() {
window.clearInterval(autopager);
}
window.addEventListener('focus', startAutopager);
window.addEventListener('blur', stopAutopager);
Note that in the latest version of Chromium, there is either a bug or a 'feature' which is making this less reliable, requiring that the user has clicked at least once anywhere in the window. See linked question above for details.
I post an answer here: How can I make setInterval also work when a tab is inactive in Chrome?
Just do this:
setInterval(function() {
$("#your-image-container").stop(true,true);
nextImage();
}, 1000);
inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) - will stop all buffered events and execute immadietly only last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
A few ideas comes to mind:
Idea #1
You can make it so that a short burst is idempotent. For example, you could say:
function now() {
return (new Date()).getTime();
}
var autopagerInterval = 8000;
function startAutopager() {
var startImage = getCurrentImageNumber();
var startTime = now();
var autopager = setInterval(
function() {
var timeSinceStart = now() - startTime();
var targetImage = getCurrentImageNumber + Math.ceil(timeSinceStart/autopagerInterval);
if (getCurrentImageNumber() != targetImage)
setImageNumber(targetImage); // trigger animation, etc.
},
autopagerInterval
);
return autopager;
}
This way even if the function runs 1000 times, it will still run in only a few milliseconds and animate only once.
note: If the user leaves the page and comes back, it will have scrolled. This is probably not what the original poster wants, but I leave this solution up since it is sometimes what you want.
Idea #2
Another way to add idempotence (while still keeping your nextImage() function and not having it scroll to the bottom of the page) would be to have the function set a mutex lock which disappears after a second (cleared by another timeout). Thus even if the setInterval function was called 1000 times, only the first instance would run and the others would do nothing.
var locked = false;
var autopager = window.setInterval(function(){
if (!locked) {
locked = true;
window.setTimeout(function(){
locked=false;
}, 1000);
nextImage();
}
}, 8000);
edit: this may not work, see below
Idea #3
I tried the following test:
function f() {
console.log((new Date()) + window.focus());
window.setTimeout(f, 1000);
}
f();
It seems to indicate that the function is being called every second. This is odd... but I think this means that the callbacks are being called, but that the page renderer refuses to update the page in any graphical way while the tab is unfocused, delaying all operations until the user returns, but operations keep piling up.
Also the window.focus() function doesn't say if the window has focus; it GIVES focus to the window, and is thus irrelevant.
What we want is probably this: How to detect when a tab is focused or not in Chrome with Javascript? -- you can unset your interval when the window loses focus (blur), and reset it when it gains focus.
I don't know exactly what is going on in your function nextImage(), but I had a similar issue. I was using animate() with setInterval() on a jQuery image slider that I created, and I was experiencing the same thing as you when I switched to a different tab and back again. In my case the animate() function was being queued, so once the window regained focus the slider would go crazy. To fix this I just stopped the animate() function from queuing.
There are a couple ways you can do this. the easiest is with .stop(), but this issue and ways to fix it are documented in the jQuery docs. Check this page near the bottom under the heading additional notes: http://api.jquery.com/animate/
I had faced similar issue, somehow this code below works fine for me.
var t1= window.setInterval('autoScroll()', 8000);
window.addEventListener('focus', function() {
focused = true;
window.clearInterval(t1);
t1 = window.setInterval('autoScroll()', 8000);
},false);
window.addEventListener('blur', function() {
focused = false;
window.clearInterval(t1);
},false)
function autoScroll()
{
if ( running == true){
if ( focused = true){
forwardSlide();
}
}
else {
running = true;
}
}
If you are using Soh Tanaka's image slider then just add this...to solve your Google Chrome issue:
$(".image_reel").stop(true, true).fadeOut(300).animate({ left: -image_reelPosition}, 500 ).fadeIn(300);
Take note of the .stop() function. Ignore the fading in and out stuff, that's what I used on my version
Thanks
Seconding the comment by jgerstle to use page visibility events instead, see https://www.w3.org/TR/page-visibility/#example-1-visibility-aware-video-playback for more around subscribing to 'visibilitychange' for hidden/visible states.
This seems to be more useful than focus/blur these days as it covers visible-but-not-selected windows if concerned also about multi-window operating systems.

DOM style change waiting for pause

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.

JavaScript Execution Order Problem

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.

Categories

Resources