Is there an IE Render Complete event? - javascript

While trying to determine why a page was taking 20s to load, I found some odd behavior in IE8.
The scenario is this.
I make an ajax call, it returns and the callback looked something like this
$("#StoreDetailsContainer").html($(tableHtml));
var StoreDetailsTable = $("#StoreDetailsTable");
StoreDetailsTable.tablesorter({ sortList: [[0, 0]], cssChildRow: "SubTable" });
StoreDetailsTable.filtertable({ cssChildRow: "SubTable" });
However, this bit of code took 20s to complete.
I was messing around, timing things, and popping up alerts between methods, and suddenly, it took only 6s. I played around a little more to find that if I introduced a delay after the .html() call, and before I attempted to manipulate the DOM, the page rendered MUCH faster. It now looks like this
$("#StoreDetailsContainer").html($(tableHtml));
window.setTimeout(function() {
var StoreDetailsTable = $("#StoreDetailsTable");
StoreDetailsTable.tablesorter({ sortList: [[0, 0]], cssChildRow: "SubTable" });
StoreDetailsTable.filtertable({ cssChildRow: "SubTable" });
}, 100);
It also only takes 6s despite having an extra 1/10th of a second added to the process.
My theory is that because the DOM wasn't fully rendered to the screen by IE by the .html() call before attempting to work with it, there is some kind of locking happening.
Is there a way to determine when IE has finished rendering what was added to the DOM by .html() so I don't need to use an arbitrary value in a setTimeout call?

You're almost on the spot with your analysis. Let me attempt to explain why setTimeout is making the difference.
If you look at this great article about DOM rendering, you'll understand that .html() will cause a reflow to happen.
Now with the next two lines, what is happening is that you're tying up the browser's rendering thread. Browsers may choose to wait till script execution completes before attempting a reflow (to avoid multiple reflows or to buffer all changes).
The solution - we need to tell the browser that our html changes are done and you can complete the rendering process. The way to do it - 1) end your script 2) use setTimeout to yield the execution to the browser.
Doing a setTimeout with 0ms delay also works because setTimeout basically relinquishes control of the sript block. One of the reasons why animation related script rely so heavily on setTimeout and setInterval.
Another potential improvement would be to use documentFragments, explained succinctly by John Resig here
I think combining these two should yield more speed but of course, no way to know until profiling is done!

You could add a single pixel image to your callback response, get that image from the DOM after .html(..) and attach to its onload event. I can't imagine it's possible for the image's onload event to fire until the browser has rendered it.
Make sure the image has a unique identifier in the src so that it doesn't get cached...
Odd problem you're having though - I'm sure someone will offer a more graceful solution :)
B

Calling setTimeout delays the given function at least for the specified time but it is never run before the current script execution finished. That said, you could replace your timeout with 0 seconds.
Another approach that might be worth trying is that you access some layout property of the generated content (for example height of StoreDetailsContainer). This way you force IE to finish rendering before returning control to your script since it can only provide the correct value that your script requested after finishing to calculate the layout.
Third guess that might help is that you ensure to parse the HTML out-with the page's layout. This would prevent painting half-done layouts over and over again. To do so, you could detach the StoreDetailsContainer element from the DOM prior your call to html. Now, IE has the change to construct the DOM without affecting the layout. After that you would re-append the StoreDetailsContainer into the DOM. Compared to a normal innerHTML set, this detaching and re-attaching of the container allows you to control when the HTML is parsed to build the DOM tree and when the layout is calculated.

Try this code. The load event is fired after ready event, so it may work.
$(document).ready(function(){
$("#StoreDetailsContainer").html($(tableHtml))
});
$(window).load(function(){
$("#StoreDetailsTable").tablesorter({ sortList: [[0, 0]], cssChildRow: "SubTable" }).filtertable({ cssChildRow: "SubTable" });
});

I think that it could work like this:
$("#StoreDetailsContainer").html($(tableHtml))
.find("#StoreDetailsTable")
.tablesorter({ sortList: [[0, 0]], cssChildRow: "SubTable" })
.filtertable({ cssChildRow: "SubTable" });

Related

How to make my javascript function wait for a html element to render

There is a single Page Application in AngularJS.
It has nested tabs. In the inner tab there is a button on which some event gets fired.I need to trigger the click event of this button present on the inner tab Button gets rendered after both the tabs are rendered. What is the best way to wait until the tabs render themselves and the button is available.
I tried using 'while loop'(i.e keep looping until id for button is undefined) and $timeout(set timeout to 2-3 seconds) service but both have their consequences when there is delay in tab render.
Please suggest if there exists a better approach.
Even though this question is really old, I've found a solution that works for me and I think it might be helpful for some people.
Calling element.getBoundingClientRect() before executing further code worked for me. According to docs this method returns information about the position relative to the viewport (docs):
The Element.getBoundingClientRect() method returns a DOMRect object providing information about the size of an element and its position relative to the viewport.
Assuming that the screen has to render to find information about the position of an element, this function would technically wait for the html element to render or even make the html element render.
Remember, this is only an assumation and I can't guarantee that it works.
Things have changed quite a bit since this question was asked, so there's now a much nicer solution.
requestAnimationFrame(() => {
// this will be called just before the next video frame.
// That will be after any changes to the DOM have been completed.
});
The docs are here...
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
You can do it with jQuery:
$(document).ready(callbackFn);
or native JS:
document.addEventListener('readystatechange', function onReadyStateChange() {
if (document.readyState !== "complete")
return;
callbackFn();
}, false);

Chrome is not executing all lines in callback for jQuery animation

I have some code that animates a <div>. When the animation is complete, several things need to happen (mostly manipulation of CSS on various elements), so I naturally put them callback provided by jQuery's .animate();
It behaves as expected in Firefox. I can't tell whether or not it's an issue in IE because there are still some CSS issues preventing it from displaying properly there - I can't tell if it's the CSS or the same problem I'm having with Chrome. Regardless, for the moment, I'm focusing on Chrome.
One thing to note is that it doesn't happen if I do a console.log right before the line that's not being executed. Same if I insert a breakpoint and then let it continue.
$sliders.animate($thisSlideConfig, 250, function() {
$newPg.removeAttr('style');
$curPg = $newPg;
$curPgInf = plugin.getPgInf($curPg);
plugin.setIndTxt();
load2nav();
plugin.adjustNavState();
doCleanup();
});
The line nor being run is $newPg.removeAttr('style');
It doesn't seem to matter where in the block I put that line or how I select $newPg.
Oh yeah, I'm on Chrome 19.0.1084.52.
Removing the style attribute is unreliable. It may not trigger redrawing of the page (whereas a console log or a breakpoint force it to). Instead, try manually calling:
$newPg.style.XYZ = "";
For each style property you defined, if you can list them. If not, try this:
for( var x in $newPg.style) $newPg.style[x] = "";
These will trigger the correct redraw, and should hopefully stop the problem.

Wait for jquery .html method to finish rendering

I have div with vertical scroll bar. Div is being updated dynamically via ajax and html is inserted using jQuery's .html method.
After div is updated scroll bar returns to top and I am trying to keep it in the previous position.
This is how I'm trying it:
var scrollPos = $('div#some_id').scrollTop(); //remember scroll pos
$.ajax({...
success: function(data) {
$('div#some_id').html(data.html_content); //insert html content
$('div#some_id').scrollTop(scrollPos); //restore scroll pos
}
});
This fails. My best guess is that it is failing due to inserted html not rendered (ie. no scroll).
For example this works.
setTimeout(function(){
$('div#some_id').scrollTop(scrollPos);
}, 200);
But this is dirty hack in my opinion. I have no way of knowing that some browsers won't take more then these 200ms to render inserted content.
Is there a way to wait for browser to finish rendering inserted html before continuing ?
It's still a hack, and there really is no callback available for when the HTML is actually inserted and ready, but you could check if the elements in html_content is inserted every 200ms to make sure they really are ready etc.
Check the last element in the HTML from the ajax call:
var timer = setInterval(function(){
if ($("#lastElementFromAjaxID").length) {
$('div#some_id').scrollTop(scrollPos);
clearInterval(timer);
}
}, 200);
For a more advanced option you could probably do something like this without the interval, and bind it to DOMnodeInserted, and check if the last element is inserted.
I will just like to point out one difference here: One thing, is when the .html() have completed loading, but the browser actually render the content is something different. If the loaded content is somewhat complex, like tables, divs, css styling, images, etc - the rendering will complete somewhat later than all the dom ellements are present on the page. To check if everything is there, does not mean the rendering is complete. I have been looking for an answer to this by myself, as now I use the setTimeout function.
Such callback does not exists because .html() always works synchronously
If you are waiting for images loading, there's one approach https://github.com/desandro/imagesloaded

element width is undefined on document ready

I am trying to obtain an element width using the following code just before the </body>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
var diff = $('div.canvas img.photo').get(1).width;
console.log(diff);
});
</script>
but it logs undefined. However, if I run $('div.canvas img.photo').get(1).width directly in the Chrome/Firebug console, it returns the correct width. The image is not being loaded with Javascript, so should be in place when the document ready fires. What am I doing wrong?
No, it shouldn't. document.ready fires when all HTML has been loaded, but not the images. If you want to wait until all images are loaded, use window.load.
Example:
$(window).load(function(){
var diff = $('div.canvas img.photo').get(1).width;
console.log(diff);
});
Also, as some people have pointed out, you were attempting to get the property ".width" twice.
If at all possible, set a width on the imagetags in CSS. That way, you can safely use document.ready for your code. Using window.load will naturally delay the execution of your script, could be a second, could be ten, depending on the speed of the clients connection, and the amount of content on your page. This could cause unwanted jumps and jitters if you're performing any styling.
img isn't being loaded on DOM ready. docs:
While JavaScript provides the load event for executing code when a page is rendered, this event does not get triggered until all assets such as images have been completely received. In most cases, the script can be run as soon as the DOM hierarchy has been fully constructed. The handler passed to .ready() is guaranteed to be executed after the DOM is ready, so this is usually the best place to attach all other event handlers and run other jQuery code. When using scripts that rely on the value of CSS style properties, it's important to reference external stylesheets or embed style elements before referencing the scripts.
change to this:
$(window).load(function(){
var diff = $('div.canvas img.photo').get(1).width;
console.log(diff.width);
});
Image's width would only be available when its loaded completely.
jQuery supports onload event on every images too.
You can use,
$('div.canvas img.photo').load(function(){
// here the image (or all images which match selector) has been loaded.
}
The problem is that your image is not loaded yet when you try to get its dimentions. To make it work wrap your code into $(window).load. Or another option. If you know the sizes of the image you can provide width and height attributes, then it's going to work even inside DOMContentLoaded. Sometimes it's preferable because onload event takes longer to fire then 'DOMContentLoaded'.
Otherwise you would need to use $(window).load, see #Andreas Carlbom answer.

Mootools problem when zoomed in

I am using Mootools extensively for a site which I am developing. But recently I noticed a problem that the animations slow down alot when I zoom (using the browsers Zoom In) in into the site. What could be a possible reason for this problem ? Or is this problem inherit in Mootools itself. This happens in Chrome 6.0.472 as well as Firefox 3.6.8.
Thanks,
Nitin
many things are wrong here with regards to speed optimisations.
lets take a look at this mouseover code that seems to slow down:
this.childNodes.item(1).style.left="0px";
this.getElements('div').setStyles({'opacity':'1'});
this.getElements('div').set('morph', {duration:'normal',transition: 'sine:out'});
this.getElements('span').set('morph', {duration:'normal',transition: 'sine:out'});
this.getElements('div').morph({'left':'-28px'});
this.getElements('span').morph({'left':'-30px','color':'#FFF'});
obviously this will work as it does but it's so very wrong i don't know where to begin.
the idea is to abstract and setup the repetitive tasks so they are done as a one off.
consider line by line the code above:
this.childNodes.item(1).style.left="0px";
this is wrong for a mootools app anyway, it would need to be this.getFirst().setStyle("left", 0);
the this.getFirst() is a lookup, it should be cached - although that's not a slow one.
then comes the bad part.
you select all child divs 3 times and all spans twice, where NO SELECTION should be applicable. VERY EXPENSIVE
you reset the Fx.morph options every mouseover event where there are no changes (although you seem to have a different duration for mouseenter and mouseleave - this is expensive)
consider this code:
[document.id("menu1"), document.id("menu2")].each(function(el) {
// use element storage to save lookups during events
el.store("first", el.getFirst());
el.store("divs", el.getElements("div"));
el.store("spans", el.getElements("span"));
// store the fx.morph options once and for all, no need to do so
// on every event unless you are changing something
el.retrieve("divs").set("morph", {
duration: 'normal',
transition: 'sine:out',
link: 'cancel'
});
el.retrieve("spans").set("morph", {
duration: 'normal',
transition: 'sine:out',
link: 'cancel'
});
// add the events
el.addEvents({
mouseenter: function(e) {
// retrieve the saved selectors from storage and do effects
this.retrieve("first").setStyle("left", 0);
this.retrieve("divs").morph({
"left": -28
});
this.retrieve("spans").morph({
'left': '-30px',
'color': '#FFF'
});
}
});
});
it will save a lot of processing on the events.
similarly, there are plenty of places where you are not really using the mootools api.
document.getElementById(curr).style.cursor="pointer";
$(this).removeEvents -> no need for $, this is not jquery.
document.getElementById("lightbox").style.visibility="hidden";
m=setTimeout('gallery()',5000); --> use the mootools var timer = (function() { ... }).delay(5000);, don't use strings with setTimeout/interval as it forces eval and reflows but pure anon functions
etc etc.
you really can make a day out of refactoring all this and making it 'nice' but it's going to be worth it.
also, learn about chaining
$("ribbon").set('morph', {duration:'long',transition: 'bounce:out'});
$("ribbon").morph({'top':'-10px'});
$("ribbon").addEvents({
this is calling up a selector 3 times. instead you can:
store it. var ribbon = $("ribbon"); ribbon.set...
chain it. $("ribbon").set("morph", {duration: 500}).morph({top:-10}).addEvents() - mootools element methods tend to return the original element so you can take the response of the last function and apply more to it.
1 is better for readibility, 2 is faster to do.
also. you have way too many global variables which makes your scope chain lookups more expensive, this will affect many call ups and places. try to namespace properly, if you need to access real global vars from functions and closures, use window.varname etc etc.
Another possible improvement here would be the use of event delegation (event bubbling will cause events to fire up the dom tree to the parents, mootools has an api to deal with it so you can add singular events to parent elements and not have to attach nnn events to all children) - look it up.
P.S. please don't take this in the wrong way - it's not meant to rubbish your work and it's just some constructive (i hope) advice that can help you bring it to the next level. good luck :)
I haven't seen any specific code in MooTools or any other library that checks if browser is zooming during animation, so I think that animation slows down, since browser using more CPU for computing zooming process.

Categories