I'm trying to fix some slow-performing javascript in a sluggish UI, and I've narrowed the main cause down to a jQuery .width()
call used to see the actual pixel size of a width: 100%
element in a responsive layout, in a process that needs to happen often in response to user actions.
I've added timestamp-based measurements and they show that it alone accounts for around 33% of the lag time, which makes the difference between the UI feeling sharp and the UI feeling sluggish. Removing this one line, the UI feels fast - but, it puts things in the wrong place...
It seems well established that .width()
is relatively slow in jQuery > 1.8 largely for two reasons:
It forces reflow while calculating the element size. From an article quoted on When does reflow happen in a DOM environment?:
[ reflow happens when ] you retrieve a measurement that must be calculated, such as accessing offsetWidth, clientHeight, or any computed CSS value (via getComputedStyle() in DOM-compliant browsers or currentStyle in IE)
...in fact, for browsers like IE and Firefox that delay DOM changes until the last moment in order to avoid contradictory changes (e.g. hide and show in the same function call), people sometimes add unnecessary state getters in order to force reflow to occur.
It also needs to check the border-box state of the element. From their blog:
jQuery 1.8 now needs to check the box-sizing property whenever you use .width() so that it can decide whether it needs to subtract out the padding and border width. That can be expensive—up to 100 times more expensive on Chrome!
The purpose of my code's width()
call is to see if, and by how much, a responsive design has been scaled down. It needs to look at a widget's wrapper element, which has width: 100%;
(but might be in a column or other container, depending on what site/page hosts it), and needs to see what % of that wrapper's maximum width it is actually showing at.
Other code based on a different co-ordinate system gives me the location, in pixels, for a label, then I need to use a scaling factor (essentially = $wrapper.width() / maxWidth;
) to scale down the location so that, for example, if the page is viewed in a narrow window/device and the wrapper is 50% of its maximum size, the labels' top and left offsets are 50% of their defaults.
Is there any way I can access this data about how much a %-width element has been scaled down without causing reflow and the other things that make .width()
calls slow?
Things I've tried or ruled out:
.outerWidth()
is exactly as slow as.width()
.get(0).clientWidth
(the pure Javascript / non-Jquery option) is also almost exactly as slow as.width()
(presumably, it's therefore the reflow that is the culprit)- I noticed that on most browsers, if I do any of these followed by any of the above in the other dimension (e.g.
.get(0).clientWidth
followed by.outerHeight()
) calls after the first are very fast (~20 times faster). Presumably since the reflow has just been done and the element properties have just been accessed, they're cached somehow. But the effect doesn't hold for repeated calls to the function, only within one function call. .css("width")
is obviously no use because it'd just give me100%
in all cases- I considered getting the width of the window, but things get complicated depending on the column layouts of the page that is hosting this element. For example, if my wrapper is in a two-column layout and the window is a little wider than the breakpoint that collapses the two columns, the window will be wider than the wrapper element's max size, but the wrapper element won't be at 100% of its maximum width.
[ Update ] I thought I'd figured out a way bypass my need to solve the problem, and position my labels without accessing the scaling of the element: since I already had the maxWidth
variable in pixels and the offsets in pixels, I had enough data to calculate a percentage offset for my labels with leftOffset_px / maxWidth_px
and topOffset_px / maxHeight_px
, then + '%'
and apply that as each label's CSS top
and left
offset. This is ridiculously faster - now less than 1 milisecond, so fast my timestamp-based function can't measure it!
Unfortunately, I also have another function that checks that the labels, which have a fixed width, don't overflow the responsive container, and for that, I do need to take into account either the current pixel width of the container or its scale factor.
.clientWidth
is one of the things I've tried - it causes reflow and is just as slow. I've had an idea for calculating a % offset though without looking at the DOM... – Jalisajalisco%
width without even looking at the DOM. I've updated accordingly. So I don't need the answer any more (but am still interested to know if it is possible, had no idea so many reflows were going on until researching this!) – Jalisajalisco