Window sizes and scrolling
Say you want to build a “back to top” button, a sticky header that appears after some scrolling, or a modal that locks the page in place. Every one of those needs a number: how tall is the viewport, how far has the user scrolled, how big is the whole document. This chapter is about getting those numbers reliably.
Most of the answers come from the root element, document.documentElement, which is the DOM object for the <html> tag. It wraps every visible thing on the page, so it’s the natural place to ask about page-wide geometry. A few of these properties behave oddly at the top level, though, and there are safer aliases the browser gives you for exactly that reason. We’ll take them one at a time.
Width and height of the window
To measure the visible area of the page — the part you can actually see, minus browser chrome — read clientWidth and clientHeight on document.documentElement:
alert( document.documentElement.clientWidth ); // width of the visible content area
alert( document.documentElement.clientHeight ); // height of the visible content area
That visible rectangle is often called the viewport. Try this button, which reports your current viewport height:
Width and height of the whole document
The viewport is only what’s on screen. Often you want the full size of the document, including the parts scrolled out of view — for a progress bar, a virtualized list, or a “you’ve reached the end” check.
In theory, since documentElement encloses everything, document.documentElement.scrollHeight should give you the total content height. In practice, at the top level, it doesn’t behave consistently. On WebKit- and Blink-based browsers (Chrome, Safari, Opera), when the page is short enough that nothing scrolls, documentElement.scrollHeight can come out smaller than documentElement.clientHeight. Which makes no sense as a “total height,” but there it is.
The reliable move is to compute the largest value across both body and documentElement, over all three height-flavored properties, and take the maximum:
let scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
alert('Full document height, including the scrolled-out part: ' + scrollHeight);
Why six values instead of one clean property? There’s no elegant reason. Different browsers, across many years, disagreed about where the “true” height lives, so taking the max is the defensive answer that works everywhere.
Reading the current scroll position
Any scrollable DOM element tracks how far it’s been scrolled in scrollLeft and scrollTop.
For the page itself, document.documentElement.scrollLeft / scrollTop works in most browsers. The exception is older WebKit-based ones like Safari, where the page scroll historically lived on document.body instead (this was WebKit bug 5991). Guessing which element to read is a bad time.
You don’t have to. The browser gives you two dedicated properties on window that always report the page scroll correctly:
alert('Scrolled down from the top: ' + window.pageYOffset + 'px');
alert('Scrolled right from the left: ' + window.pageXOffset + 'px');
Both are read-only — you can’t scroll the page by assigning to them. To change the scroll, use the methods below.
Scrolling the page: scrollTo, scrollBy, scrollIntoView
A regular element scrolls when you assign to its scrollTop or scrollLeft. The page works the same way through document.documentElement.scrollTop / scrollLeft (again, document.body on Safari). The same cross-browser wrinkle as before.
There’s a cleaner, universal path. Two window methods that behave identically everywhere:
-
window.scrollBy(x, y)scrolls relative to where you are now.scrollBy(0, 10)nudges the page 10px further down; call it again and you’ve gone 20px. Positiveymoves content up (you go down the page).Try it — each click scrolls down another 10px:
-
window.scrollTo(pageX, pageY)scrolls to an absolute position. After the call, the top-left corner of the viewport sits at document coordinates(pageX, pageY). It’s the method-form of settingscrollTop / scrollLeftdirectly.So
scrollTo(0, 0)jumps back to the very top:
The very same scrollTo and scrollBy also exist on any scrollable element, and the position math is identical: scrollTop counts from the top, and the largest scrollable distance is scrollHeight - clientHeight. Drive this panel by scrolling it directly or with the buttons — the readout and progress bar are computed live from those three numbers:
scrollIntoView
One more method, handy when you want to bring a specific element into view rather than compute coordinates yourself: elem.scrollIntoView(alignToTop).
Calling it scrolls the page just enough to reveal elem. The single boolean argument controls the alignment:
elem.scrollIntoView(true)— the default — scrolls so the element’s top edge lines up with the top of the viewport.elem.scrollIntoView(false)scrolls so the element’s bottom edge lines up with the bottom of the viewport.
This button scrolls itself to the top of the window:
And this one scrolls itself to the bottom:
The two buttons below both reveal the same off-screen card — but one pins it to the top of the scroll box and the other to the bottom, so you can feel the difference the boolean makes:
Preventing scroll
Sometimes you want the page to hold still — most often when a modal or full-screen message is open and you’d rather the user deal with it than scroll the content behind it.
Freezing the page is one line: set overflow: hidden on document.body. The page locks at whatever scroll position it’s currently at.
The first button freezes the scroll; the second clears the style and releases it. The same trick works on any element, not only document.body — set overflow: hidden on a scrollable container to lock it.
Here that idea is scoped to a single box so it can’t disturb the rest of the page. Toggle the freeze, then try to scroll the box — while it’s frozen, it won’t budge:
There’s a catch worth planning for. When the scrollbar disappears, the space it occupied becomes free, and the content widens to fill it — everything on the page shifts sideways by a dozen-or-so pixels. It’s a small jump, but noticeable and ugly.
Summary
Geometry
-
Visible content area (the viewport):
document.documentElement.clientWidth / clientHeight. -
Full document size, including the scrolled-out part — take the max across six properties, because no single one is reliable everywhere:
let scrollHeight = Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight );
Scrolling
- Read the current scroll (read-only):
window.pageYOffset / pageXOffset, also spelledwindow.scrollY / scrollX. - Change the scroll:
window.scrollTo(pageX, pageY)— jump to absolute coordinates.window.scrollBy(x, y)— move relative to the current position.elem.scrollIntoView(alignToTop)— scroll soelembecomes visible, aligned to the top (true) or bottom (false) of the viewport.
Prefer the window methods and the pageOffset / client* properties over reading and writing scrollTop on documentElement versus body — they smooth over the old cross-browser differences for you.