clientWidth Performance in IE8 - javascript

I have some legacy javascript that freezes the tfoot/thead of a table and lets the body scroll, it works fine except in IE8 its very slow.
I traced the problem to reading the clientWidth property of a cell in the tfoot/thead... in ie6/7 and FireFox 1.5-3 it takes around 3ms to read the clientWidth property... in IE8 it takes over 200ms and longer when the number of cells in the table is increased.
Is this a known bug ? is there any work around or solution ?

I've solved this problem if you are still interested. The solution is quite complex. Basically, you need to attach a simple HTC to the element and cache its clientWidth/Height.
The simple HTC looks like this:
<component lightweight="true">
<script>
window.clientWidth2[uniqueID]=clientWidth;
window.clientHeight2[uniqueID]=clientHeight;
</script>
</component>
You need to attach the HTC using CSS:
.my-table td {behavior: url(simple.htc);}
Remember that you only need to attach the behavior for IE8!
You then use some JavaScript to create getters for the cached values:
var WIDTH = "clientWidth",
HEIGHT = "clientHeight";
if (8 == document.documentMode) {
window.clientWidth2 = {};
Object.defineProperty(Element.prototype, "clientWidth2", {
get: function() {
return window.clientWidth2[this.uniqueID] || this.clientWidth;
}
});
window.clientHeight2 = {};
Object.defineProperty(Element.prototype, "clientHeight2", {
get: function() {
return window.clientHeight2[this.uniqueID] || this.clientHeight;
}
});
WIDTH = "clientWidth2";
HEIGHT = "clientHeight2";
}
Notice that I created the constants WIDTH/HEIGHT. You should use these to get the width/height of your elements:
var width = element[WIDTH];
It's complicated but it works. I had the same problem as you, accessing clientWidth was incredibly slow. This solves the problem very well. It is still not as fast IE7 but it is back to being usable again.

I was unable to find any documentation that this is a known bug. To improve performance, why not cache the clientWidth property and update the cache periodically? I.E if you code was:
var someValue = someElement.clientWidth + somethingElse;
Change that to:
// Note the following 3 lines use prototype
// To do this without prototype, create the function,
// create a closure out of it, and have the function
// repeatedly call itself using setTimeout() with a timeout of 1000
// milliseconds (or more/less depending on performance you need)
var updateCache = function() {
this. clientWidthCache = $('someElement').clientWidth;
};
new PeriodicalExecuter(updateCache.bind(this),1);
var someValue = this.clientWidthCache + somethingElse

Your problem may be related to something else (and not only the clientwidth call): are your updating/resizing anyhting in your DOM while calling this function?
Your browser could be busy doing reflow on IE8, thus making clientwidth slower?

IE 8 has the ability to switch between IE versions and also there is a compatibility mode.
Have you tried switching to Compatibility Mode? Does that make any difference?

I though I had noticed a slow performance also when reading the width properties. And there may very well be.
However, I discovered that the main impact to performance in our app was that the function which was attached to the window's on resize event was itself somehow causing another resize which caused a cascading effect, though not an infinite loop. I realized this when i saw the call count for the function was orders of magnitude larger in IE8 than in IE7 (love the IE Developer Tool). I think the reason is that some activities on elements, like setting element widths perhaps, now cause a reflow in IE8 that did not do so in IE7.
I fixed it by setting the window's resize event to: resize="return myfunction();" instead of just resize="myfunction();" and making sure myfunction returned false;
I realize the original question is several months old but I figured I'd post my findings in case someone else can benefit.

Related

Google DevTool Timeline: Forced reflow is likely performance bottleneck

I added parallax effect to my page. And now I have problems with performance and FPS and many questions :-)
I use transform3d and requestAnimationFrame to realize it (like this recomended http://www.html5rocks.com/en/tutorials/speed/animations/).
My code looks like this:
window.addEventListener('scroll', function() {
latestKnownScrollY = window.scrollY;
});
function updateParallax() {
var y = latestKnownScrollY * 0.4;
element.style.transform = 'translate3d(0, ' + y + 'px, 0)';
requestAnimationFrame(updateParallax);
}
updateParallax();
Sometimes I have warnings like on the screenshot:
Forced reflow is likely performance bottleneck
Call stack points to latestKnownScrollY = window.scrollY.
But why this warning appears only occasionally? I use window.scrollY each scroll event.
Each time you read window.scrollY, you're causing a reflow. It just means that the browser is calculating the styles and layout to give you the value.
It says it's likely a performance issue because it takes time and it is synchronous. If you read, set, read, set, read, set properties, or if you have this kind of thing inside a loop, it will cause a bottleneck until it can redraw the whole page all the times you triggered the reflow. The solution is usually first to read everything you need, then set everything you need to change.
But in your case, it shouldn't be a problem. It says it takes just 0.2 ms and it's doing it just once. Do you notice any performance issue? Like a lag when you scroll?

Different values when debugging than in console?

I'm running an Angular application that is returning two distinct values simultaneously. I'm curious if anyone has seen this:
function updateValues() {
var activeNavButton = pageNavButtons.eq(values.currentPage);
pageNavButtons.removeClass("active");
activeNavButton.addClass("active");
pageNavButtons.each(function () {
var forceRender = $(this).get(0).offsetLeft;
});
var w = 0;
$(".pages button").each(function () {
w = w + $(this).outerWidth(true)
});
var b=0;
completeHandler();
}
This is straightforward as can be. Switch which item is "active", and then force a render refresh. You'll notice none of this code is really doing anything, but thats okay. I left out some of the less important, unrelated stuff.
Yep, I'm frustrated enough that I'm trying to force the render refresh in multiple ways at once.
In the chrome debugger, if you break on this line:
var b = 0
the following occurs:
w = 790 //Watcher
However, if you open the console while the script is still at that break point and literally copy and paste the preceding 4 lines:
var w = 0;
$(".pages button").each(function () {
w = w + $(this).outerWidth(true)
});
It returns 800 for the value of w.
An important thing to note: the .active class gives the selected element a "bold" font, thus changing the element width. I'm positive this is related to the problem but I can't for the life of me figure out what the issue really is.
As you can see, I'm accessing offsetWidth to try to force the browser to update the elements but its not working.
Any ideas? This is driving me absolutely insane.
Okay. This may seem dumb but on large code bases it might not be terribly surprising:
Turns out that the button's base CSS class (a ways up in the hierarchy) had a transition: all 150ms on it.
This caused a delay, which caused widths to return incorrectly as you might expect because, and this is the important part, font weight is included in transition all.
Because of this, the font weight would change 150ms later, and those extra couple of pixels of width (in certain cases) would register "late". No wonder my timers seemed to arbitrarily succeed: I was guessing.
Lesson learned: Don't use transition: all unless you have a good reason. Target the things you want to actually transition.
Hope this helps somebody else! Cheers.

Recalculate Style: why so stuttering?

Let's say we have a code that injects series of similar elements into the DOM. Something like this:
var COUNT = 10000,
elements = Object.keys(Array(COUNT).join('|').split('|'));
var d = document,
root = d.getElementById('root');
function inject() {
var count = COUNT,
ul = d.createElement('ul'),
liTmpl = d.createElement('li'),
liEl = null;
console.time('Processing elements');
while (count--) {
liEl = liTmpl.cloneNode(false);
liEl.textContent = elements[count];
ul.appendChild(liEl);
}
console.timeEnd('Processing elements');
console.time('Appending into DOM');
root.appendChild(ul);
console.timeEnd('Appending into DOM');
};
d.getElementById('inject').addEventListener('click', inject);
Demo.
When this snippet is run in Firefox (25.0), the time between calling 'inject' and actually seeing its results more-o-less corresponds to what is logged by time/timeEnd. For 1000 elements, about 4 ms; for 10000, about 40 and so on. Quite normal, ain't it?
It's very not so, however, with Chrome (30.0 and Canary 32.0 are tested). While the reported time for processing and appending is actually less than Firefox's, rendering of these elements takes a LOT more.
Puzzled, I've checked the Chrome's profiler for different scenarios - and it turned out the bottleneck is in Recalculate Style action. It takes 2-3 seconds for 10000 nodes, 8 seconds for 20000 nodes, and whopping 17 seconds for 30000 nodes.
Now the real question is: has anyone been in the same situation, are there any workarounds?
One possible way we've thought about is limiting the visibility of these nodes in a sort of lazy load ('a sort of', because it's more about 'lazy showing': the elements will already be in place, only their visibility will be limited). It's confirmed that 'Recalculate Style' is triggered only when element is about to become visible (which makes sense, actually).
Looks like the trouble is with the li elements that have display:list-item
If instead of ul/li you use div elements it works pretty fast in chrome..
Also creating a css rule of li{display:block;} fixes the delay.
And manually adding the list-item shows the delay even if the elements are already rendered in the DOM (they have to be re-rendered ofcourse)
See demo at http://jsfiddle.net/6D7sM/1/
(so it seems that chrome is slow in rendering display:list-item elements)
There is also a relevant bug submission to chrome http://code.google.com/p/chromium/issues/detail?id=71305 which has been merged into http://code.google.com/p/chromium/issues/detail?id=%2094248 (looks like in earlier versions it was crashing chrome, but it has been fixed. the crashing, not the speed)

IE Standard vs Quirks Handling of offsetLeft

We are modifying an older pre-existing web app and as part of that have begun viewing it using IE10. This app has a third party menu control (menu9_com.js?) and among the numerous issues we are noticing, is the positioning of this menu on IE7+ in Standards mode. In FF, Chrome, or any version of IE with Quirks - it is positioned correctly. In Standards mode, however, it is shoved far off to the right.
I've identified the function below as a possible source for the issue. Running in any mode, the value of StartLeft begins about the same. In the working modes it finishes at a value which - by definition - works. In the broken modes, it is much much higher.
Though it's not fully clear, I believe the function is walking up the DOM from a given target location and adding values on to calculate a "total" offset for the menu element it is adding. And I think the issue comes down to the different ways that offsetLeft (and maybe offsetParent?) are handled. So I'm trying to find the best way to get consistent behavior from this function but just not familiar enough with the intention of the function, nor with the behavior of offsetLeft etc in the various modes.
Here's the function:
function ClcTrgt() {
var TLoc=Nav4?FLoc.document.layers[TargetLoc]:DomYesFLoc.document.getElementById(TargetLoc):FLoc.document.all[TargetLoc];
if (DomYes) {
while (TLoc) {
StartTop += TLoc.offsetTop;
StartLeft += TLoc.offsetLeft;
TLoc = TLoc.offsetParent;
}
}
else {
StartTop+=Nav4?TLoc.pageY:TLoc.offsetTop;
StartLeft+=Nav4?TLoc.pageX:TLoc.offsetLeft;
}
}
Any suggestions? For example, I'd convert this function to use jQuery, if I knew how.
UPDATE:
I've posted the script on pastebin.
My current direction, in the absence of an actual fix to the script (which may not be worth the work), is adding this function to run after the script itself. I added some markup to facilitate it, and it just takes the menu, and puts it where it should be (which it right-aligned with another element I've identified). This is for from optimal, but it works.
function fixMenu9() {
var pTD = $('#pgMenuDivTD');
var pMN = $('#pgMenuDiv');
var additionalOffset = ExpYes ? 3 : 0;
var leftVal = (parseInt(pTD.offset().left) + parseInt(pTD.css('width'))) - (parseInt(pMN.css('width')) + additionalOffset);
$('#pgMenuDiv').css('left', leftVal);
}

Improving Efficiency in jQuery function

The while statement in this function runs too slow (prevents page load for 4-5 seconds) in IE/firefox, but fast in safari...
It's measuring pixel width of text on a page and truncating until text reaches ideal width:
function constrain(text, ideal_width){
$('.temp_item').html(text);
var item_width = $('span.temp_item').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
var original = text.length;
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
$('.temp_item').html(smaller_text);
item_width = $('span.temp_item').width();
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
Any way to improve performance? How would I convert this to a bubble-sort function?
Thanks!
move the calls to $() outside of the loop, and store its result in a temporary variable. Running that function is going to be the slowest thing in your code, aside from the call to .html().
They work very very hard on making the selector engines in libraries fast, but it's still dog slow compared to normal javascript operations (like looking up a variable in the local scope) because it has to interact with the dom. Especially if you're using a class selector like that, jquery has to loop through basically every element in the document looking at each class attribute and running a regex on it. Every go round the loop! Get as much of that stuff out of your tight loops as you can. Webkit runs it fast because it has .getElementsByClassName while the other browsers don't. (yet).
Instead of removing one character at time until you find the ideal width, you could use a binary search.
I see that the problem is that you are constantly modifying the DOM in the loop, by setting the html of the temp_item, and then re reading the width.
I don't know the context of your problem, but trying to adjust the layout by measuring the rendered elements is not a good practice from my point of view.
Maybe you could approach the problem from a different angle. Truncating to a fixed width is common.
Other possibility (hack?) if dont have choices, could be to use the overflow css property of the container element and put the … in other element next to the text. Though i recommend you to rethink the need of solving the problem the way you are intending.
Hugo
Other than the suggestion by Breton, another possibility to speed up your algorithm would be to use a binary search on the text length. Currently you are decrementing the length by one character at a time - this is O(N) in the length of the string. Instead, use a search which will be O(log(N)).
Roughly speaking, something like this:
function constrain(text, ideal_width){
...
var temp_item = $('.temp_item');
var span_temp_item = $('span.temp_item');
var text_len_lower = 0;
var text_len_higher = smaller_text.length;
while (true) {
if (item_width > ideal)
{
// make smaller to the mean of "lower" and this
text_len_higher = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_lower)/2));
}
else
{
if (smaller_text.length>=text_len_higher) break;
// make larger to the mean of "higher" and this
text_len_lower = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_higher)/2));
}
temp_item.html(smaller_text);
item_width = span_temp_item.width();
}
...
}
One thing to note is that each time you add something to the DOM, or change the html in a node, the page has to redraw itself, which is an expensive operation. Moving any HTML updates outside of a loop might help speed things up quite a bit.
As other have mentioned, you could move the calls to $() to outside the loop. You can create a reference to the element, then just call the methods on it within the loop as 1800 INFORMATION mentioned.
If you use Firefox with the Firebug plugin, there's a great way of profiling the code to see what's taking the longest time. Just click profile under the first tab, do your action, then click profile again. It'll show a table with the time it took for each part of your code. Chances are you'll see a lot of things in the list that are in your js framework library; but you can isolate that as well with a little trial and error.

Categories