jQuery functions provide a convenient shorthand for retrieving information from the DOM. There is no need to think about browser-specific quirks – mostly. Here is a case where it doesn't work out.
Retrieving the size of a document should be a straightforward affair: a simple $( document ).width()
or $( document ).height()
call, and you are done. Yet in IE, these calls often return a size just a little bit too large. Sometimes they are off by as little as 4px, sometimes the difference accounts for the width of the scroll bar, or the scrollbar plus 4px. This bug has been around for a long time. As of jQuery 1.6.1, it is still with us, and it looks like it's going to stay that way.
The actual size of the mismatch depends on the document layout and the version of IE. All IE browsers up to and including IE9 are affected, with the curious exception of IE7. The exact workings of this bug tell a little about the document properties used by jQuery and what they actually contain.
jQuery determines the document width and height by querying these properties and using the largest value:
d.dE.clientWidth
, d.dE.clientHeight
d.dE.scrollWidth
, d.dE.scrollHeight
body.scrollWidth
, body.scrollHeight
d.dE.offsetWidth
, d.dE.offsetHeight
body.offsetWidth
, body.offsetHeight
The ones mentioned first, the d.dE.client*
properties, contain the size of the viewport. The others reflect the content of the document. The bug occurs because the offset properties, d.dE.offset*
, will occasionally return a value which is larger than both the viewport and the content area.
The d.dE.offset*
property is based on the viewport size. In most versions of IE, it also includes elements of the browser chrome, most notably the scrollbar.
This is not an issue as long as there aren’t any scrollbars, of course. Both client*
and offset*
will return the size of the viewport, as they should. Equally, there won't be a problem if the document is significantly larger than the viewport, in width as well as height. Yes, scrollbars will interfere with d.dE.offset*
. But that doesn’t matter because the document covers a much larger area, pushed out by the actual content of the page.
But what if the page is spilling out of the viewport at the bottom, while still wrapping its content nicely inside the browser window horizontally? The vertical scrollbar is visible, adding to the width reported by d.dE.offsetWidth
. The content doesn't exend underneath it. Suddenly d.dE.offsetWidth
has become the largest value - and jQuery happily returns it as the document width, which now includes the scrollbar.
This happens in IE6, IE8 and IE9. In IE6, this situation is actually permanent. Even when the complete page is fitting nicely into the browser window, IE6 will display the vertical scrollbar element (though not the actual scrollbar slider itself - after all, there is nothing to scroll). To add to the confusion, IE6 and IE8 also include a small border around the viewport in the figure returned by the offset*
property. It accounts for 2px on each side – thus adding a total of 4px in each dimension. For this reason, in IE8, d.dE.offset*
and jQuery will be off by 4px even if scrollbars are absent.
It may be easier to see it in action, though. Here is a demo page to play around with. Just take any of the affected IE versions and watch the numbers change as you resize the window. Another, more basic illustration of the bug can be tweaked in jsFiddle.
As mentioned before, IE7 is not part of this particular mess. It appears that d.dE.offset*
is just a synonym for d.dE.client*
in IE7, so d.dE.offset*
doesn’t introduce any new, weird numbers here.
In a nutshell: The d.dE.offset*
property includes elements of the browser chrome in most versions of IE and is responsible for jQuery misreporting the document size. In addition, the implementation of this property has changed in every new version of IE. Don’t rely on it, ever.
The solution, then, is both simple and obvious. d.dE.offset*
should not be queried in IE.
I am not sure about other browsers, though. While I'm not aware that using this property would be necessary, I didn’t really look into it, and there problably is a good reason for d.dE.offset*
showing up in the jQuery source in the first place.
Anyway, a few lines wrapped in a jQuery extension can handle all that. They fix the issue in IE and leave the code for all other browsers intact. Use the extension by calling $(document).trueWidth()
and $(document).trueHeight()
.
So here is the code:
( function ( $ ) {
var getPropIE = function ( name ) {
return Math.max(
document.documentElement["client" + name],
document.documentElement["scroll" + name],
document.body["scroll" + name]
);
}
$.fn.trueWidth = function() {
return ( ( $.browser.msie && this.get()[0].nodeType === 9 ) ? getPropIE( 'Width' ) : this.width() );
};
$.fn.trueHeight = function() {
return ( ( $.browser.msie && this.get()[0].nodeType === 9 ) ? getPropIE( 'Height' ) : this.height() );
};
} )( jQuery );
As you can see, the code is relying on browser detection. This technique is frowned upon by the majority of web developers, and with good reason. Yet here we seem to have one of the few cases where the far superior alternative, feature detection, doesn't lead anywhere:
Relying on $.browser.msie isn’t the most attractive thing to do, but another option isn't immediately obvious - that is, to me. Should you have an idea, I'd appreciate if you leave a comment. Anyway, sniffing out IE is still a huge improvement over the alternative, which is leaving the bug unfixed.
But perhaps you aren't interested in that kind of debate and just want to download the extension. When it is loaded, just call $(document).trueWidth()
and $(document).trueHeight()
instead of the native jQuery methods.
To find out if, at some point, the issue will be fixed in jQuery itself, have a look at the somewhat unpromising ticket in the jQuery bug tracker. Don't hold your breath.
WOW! I "love" it (read the opposite pls) when you guys write great tutorials for gurus who know anyways how to solve these issues, but leave the rest of us without a clue as of how to implement all that ...
"When it is loaded, just call $(document).trueWidth() and $(document).trueHeight() instead of the native jQuery methods."
WOW, great ... and what do I do with that stuff now? So difficult to think of the less fortunate ones who do not happen to be programmers?
What about the exact code for ".. just call $(document).trueWidth() and $(document).trueHeight() instead of the native jQuery methods." !??!
That would be reaaaaly great!
Thank you in the name of all non-programmer souls out there!
Thanks for elevating me to the level of a "guru" ;-)
You are right, though, in that this post is targeted at people who know how to write scripts in Javascript/jQuery. It solves a problem you are only going to have if you are a programmer or web developer. Otherwise you won't even encounter the issue.
Anyway, if you are just having trouble with the phrase you mentioned, "native jQuery methods" are the ones built into jQuery -
width()
andheight()
in this case. Whenever you would normally call$( document ).width()
in your code, use$( document ).trueWidth()
instead - that's all you need to change.Hello. I English know bad, but I still decided to thank YOU for this perfect solution for fixing this problem. After I read it, I came up with the brilliant idea of how exactly to define it is IE or another browser. I think it's reliable way to pinpoint it IE or not:
It is only my idea. If this is to add to your plugin, finally function should look like this:
I'm very waiting for a reply from you, becouse to write this message it took me a long time (in the majority I used a translator). Thank you!!
sorry, but you site, is filtering more symbols((( Add Please the ability to format the text in the comments See the plugin here https://jsfiddle.net/rzSYb/
Thank you very much for your suggestion, and for taking the time to struggle with the translation. Sorry for the garbled code in your comment. Some stuff doesn't make it past the filter in the comments section, so if anyone is interested, follow the link to jsFiddle.
In a nutshell, this is what you are suggesting:
And that is browser sniffing.
Browser sniffing doesn't necessarily have to happen by examining the UA string. The method doesn't really matter that much. The issue here is that the underlying logic is at fault. We see that we have a browser of type A, so we infer - but don't test directly - that it gets feature B wrong.
This approach is bad because it is indirect, based on an assumption, and might break with the next version of browser A. What we want to do is check, on the fly, if feature B is broken in the current environment. In case it is, we use a polyfill. Who cares about the browser type.
But feature detection is hard to pull off in this case, or maybe even impossible, for the reasons I have mentioned in the post. So it has to be browser sniffing for now. That might just as well be handled by the jQuery
$.browser
utility. Conditional comments will be dropped in IE10 anyway, and$.browser
doesn't add a global variable (as does your flag).But again, thanks for your suggestion, and for trying to solve this problem!
Thanks so much for this. Too bad the folks at jquery aren't fixing it, for whatever ideological reason (I stopped reading after the standard "this obvious bug is not a bug" reply.)
Hello! Good work. I have a question: How to calculate this "4px" "offset" ?
Seems,
-document.documentElement.getBoundingClientRect().left * 2
work (IE 8) ?
Yes, it works that way in IE8, but frankly, I would stay clear of that one.
getBoundingClientRect()
, called on the document element, reportsIn other words, don't expect
getBoundingClientRect()
to be particularly helpful in this context.@michael But i support only IE8, so it seems, document.documentElement.getBoundingClientRect().left works well for me it returns n px depending on browser zoom level, it returns 0 in fullscreen mode
Oh, ok, that's a special case. Perhaps it will work for you then.
By the way, good point you make about the value being dependent on the zoom level. Again, it is rather unintuitive, the value growing when the zoom level is reduced (while the actual border size, in pixels, stays exactly the same).
But I'm sure you figured that out way before me :)
I tring to support ie8 In my scenario document.documentElement.clientHeight document.body.offsetHeight document.documentElement.scrollHeight != document.documentElement.clientHeight
to detect the actual height of document is greater then view port height working fine in Mozilla and chrome Not working in IE8 all the value are same not able to get scroll height.
I'm not sure I've understood your question entirely, but I'd suggest you have a look at the jQuery source. Most of the browser inconsistencies are handled there, and you can take you cue from the way it's done.
(That doesn't apply to document height, of course, because it's broken in jQuery, but that's what this post is all about.)
If looking at the jQuery source doesn't get you anywhere, Stackoverflow is probably your best bet ;)
$.browser is deprecated now in jQuery, so
will no longer work
True indeed.
I can totally see why the jQuery team is going that route, but in the context of this bug, it sucks big time. As I have said before, browser sniffing is the only way to make it work (until proven otherwise).
So for now, I'd recommend reinstating jQuery.browser by including the jQuery.migrate plugin in your project. I might add the code for sniffing out IE to my extension later on, but I think that for now, jQuery.migrate probably is the best solution.
Comments are disabled.
Comments have primarily been disabled because of a flood of comment spam. Turning them off has also been an easy way to comply with EU privacy and data protection regulations. User nicknames have been replaced by anonymous placeholders. All data relating to the original commenters has been deleted.