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 plete.
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?
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 plete.
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?
- 1 IE 8 has a profiler built into its script debugger; why not use it? – Craig Stuntz Commented Aug 4, 2011 at 16:13
-
Isn't this what
$(document).ready()
is for? – Jon Grant Commented Aug 4, 2011 at 16:13 - he calls a jquery function to insert the html... thus this would fire long before he needs it to – Joseph Marikle Commented Aug 4, 2011 at 16:15
- @Craig, I've looked into it, and haven't found anything useful yet. – CaffGeek Commented Aug 4, 2011 at 17:55
- My wild guess is that doing the two operations together is making the reflow bine in a bad way. But hard to be sure from the description. But you should be able to test this. If so, it may be easier to fix than the timing issue. – Craig Stuntz Commented Aug 4, 2011 at 18:09
5 Answers
Reset to default 6You'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 pletes 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 plete 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 bining 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" });