You are testing in quirks mode. Test in standards mode.
Measuring the client area of the viewport is very easy in MSHTML. The viewport is actually an element. Which one? The Big One With the Scroll Bar. We need the area inside the border, excluding space taken up by scroll bars. MS invented the clientWidth/Height properties for just this purpose.
In MSHTML, the HTML element (document.documentElement) is used for the viewport in standards mode and the body in quirks (HTML is not rendered). In IE6-8, the document.compatMode property indicates the rendering mode. This property does not exist in IE < 6, but quirks mode can be detected easily enough as we know that the HTML element is not rendered in quirks mode. Inspecting the HTML element's clientWidth/Height properties reveals they are 0 in quirks mode. A document is more likely to legitimately have an 0 pixel height than width, so we will check the clientWidth property.
// This code works for all IE versions and modes
var getRoot, getViewportDimensions;
if (typeof doc.compatMode == 'string') {
getRoot = function(win) {
var doc = win.document, html = doc.documentElement, compatMode = doc.compatMode;
return (html && compatMode.toLowerCase().indexOf('css') != -1) ? html : doc.body;
};
} else {
getRoot = function(win) {
var doc = win.document, html = doc.documentElement;
return (!html || html.clientWidth === 0) ? doc.body : html;
};
}
getViewportDimensions = function(win) {
if (!win) {
win = window;
}
var root = getRoot(win);
return [root.clientWidth, root.clientHeight];
};
The history of this critical implementation is a comedy of errors.
Over the years, the other major browsers have copied IE, but there have been troubles along the way. For one, the browser developers observed that the properties needed to measure the client area of the viewport moved between the HTML and body elements when the rendering mode was switched, but apparently did not correlate this with the MSHTML rendering behavior. In quirks mode, the HTML element is typically rendered, but its rightful (per MS) clientHeight/Width properties are reported by the body element. This makes no logical sense, but doesn't hinder the initial rendition as the document.compatMode property still indicates the proper element in most cases.
Prior to 9.5, Opera used the body exclusively to report what should be the HTML clientHeight/Width, which makes even less sense and requires feature testing to work around, with the body's border(s) figuring in when present. As of 9.5, they have caught up to the other major non-IE browsers.
More often than not, especially in modern browsers released in the last five years, these properties include space occupied by scroll bars. Therefore we refer to these properties only as a last resort.
// Last resort, return innerWidth/Height
// NOTE: May include space occupied by scroll bars
getViewportDimensions = function(win) {
if (!win) {
win = window;
}
return [win.innerWidth, win.innerHeight];
};
Some older KHTML-based browsers (e.g. Safari 2) feature document.clientHeight/Width properties. These properties are arguably a better alternative to measuring elements, but are apparently no longer in production.
// This code will work in Safari 2 and other older KHTML-based browsers
getViewportDimensions = function(win) {
if (!win) {
win = window;
}
var doc = win.document;
return [doc.clientWidth, doc.clientHeight];
};
We need a feature test to find the correct element.
var scrollChecks, html = doc.documentElement;
if (html && typeof doc.createElement != 'undefined') {
// Test to resolve ambiguity between HTML and body
scrollChecks = (function() {
var oldBorder, body = doc.body, result = { compatMode: doc.compatMode };
var clientHeight = html.clientHeight, bodyClientHeight = body.clientHeight;
var elDiv = doc.createElement('div');
elDiv.style.height = '100px';
body.appendChild(elDiv);
result.body = !clientHeight || clientHeight != html.clientHeight;
result.html = bodyClientHeight != body.clientHeight;
body.removeChild(elDiv);
if (result.body || result.html && (result.body != result.html)) {
// found single scroller--check if borders should be included
// skipped if body is the real root (as opposed to just reporting the HTML element's client dimensions)
if (typeof body.clientTop == 'number' && body.clientTop && html.clientWidth) {
oldBorder = body.style.borderWidth;
body.style.borderWidth = '0px';
result.includeBordersInBody = body.clientHeight != bodyClientHeight;
body.style.borderWidth = oldBorder;
}
return result;
}
})();
}
With this and the getRoot function, we can create a simple wrapper, choosing between 4 (5 in the longer version) different algorithms.
var doc = window.document, html = doc.documentElement;
if (typeof doc.clientWidth == 'number') {
// If the document itself implements clientWidth/Height (e.g. Safari 2)
// This one is a rarity. Coincidentally KHTML-based browsers reported to implement
// these properties have trouble with clientHeight/Width as well as innerHeight/Width.
getViewportDimensions = function(win) {
if (!win) {
win = window;
}
var doc = win.document;
return [doc.clientWidth, doc.clientHeight];
};
} else if (html && typeof html.clientWidth == 'number') {
// If the document element implements clientWidth/Height
getViewportDimensions = function(win) {
if (!win) {
win = window;
}
var root = getRoot(win), doc = win.document;
var clientHeight = root.clientHeight, clientWidth = root.clientWidth;
// Replace previous guess at root container
if (scrollChecks) {
root = scrollChecks.body ? doc.body : doc.documentElement;
}
clientHeight = root.clientHeight;
clientWidth = root.clientWidth;
if (scrollChecks && scrollChecks.body && scrollChecks.includeBordersInBody) {
// Add body borders
clientHeight += doc.body.clientTop * 2;
clientWidth += doc.body.clientLeft * 2;
}
return [clientWidth, clientHeight];
};
} else if (typeof window.innerWidth == 'number') {
// Last resort, return innerWidth/Height
// NOTE: May include space occupied by scroll bars
getViewportDimensions = function(win) {
if (!win) {
win = window;
}
return [win.innerWidth, win.innerHeight];
};
}
// Discard unneeded host object references
doc = html = null;
This can be simplified further for contexts where body borders are not used.
See the source for the complete rendition. It is based on a method from My Library.
CLJ FAQ article on the same subject