Document Geometry & Scrolling

This section documents modern ECMAScript and core browser JavaScript APIs — it is not tied to any specific framework or library (React, Vue, Angular, etc.). This content was generated with the assistance of AI and should be verified against the current ECMAScript specification and MDN documentation before relying on it in production, since JavaScript language features and browser API support continue to evolve.

This section’s bibliography lists the reference material consulted while preparing these pages.

The DOM (see DOM Basics) models a document as an abstract tree of elements and text nodes, but a browser also renders that tree into a visual layout in which every element has a concrete on-screen position and size. Most web application code never needs to think about that layout, but sometimes it must — positioning a tooltip next to a button, detecting whether an element has scrolled into view, or programmatically scrolling the page. This page covers the APIs that bridge the tree-based and coordinate-based views of a document: reading an element’s geometry, the confusingly similar offset/client/scroll property families, the different coordinate systems those numbers are expressed in, and controlling scroll position from JavaScript.

Coordinate Systems: Viewport vs. Document vs. Element

Element positions are measured in CSS pixels, with x increasing rightward and y increasing downward, but there are two different origins in play:

  • Viewport coordinates are relative to the top-left corner of the viewport — the portion of the browser window that actually renders content, excluding chrome such as toolbars and tabs (or, for an <iframe>, the frame element itself). getBoundingClientRect(), elementFromPoint(), and the clientX/clientY properties of mouse and pointer events (see Browser Events) all use viewport coordinates.

  • Document coordinates are relative to the top-left corner of the whole document. If the document is smaller than the viewport, or hasn’t been scrolled, the two coordinate systems coincide. Otherwise they differ by the current scroll offset: an element at document y=200 is at viewport y=125 once the page has been scrolled down 75 pixels.

Because CSS overflow lets any element scroll its own content, a single document can contain many independent scrolling regions — so, unlike a sheet of paper, an element does not have one canonical document-coordinate position that holds regardless of scrolling. This is why client-side JavaScript favors viewport coordinates for most geometry work.

CSS position also picks its reference coordinate system: position: fixed positions relative to the viewport; position: absolute positions relative to the document, or to the nearest positioned ancestor if there is one (see Positioning); position: relative positions relative to the element’s own normal-flow location. A position: relative container with top: 0; left: 0 is a common trick for establishing a new origin — sometimes called "container coordinates" — for position: absolute descendants.

A CSS pixel is a software pixel, not a hardware one. window.devicePixelRatio reports how many physical device pixels back each CSS pixel (e.g. 2 on many "retina" displays), so pixel coordinates are not restricted to integers — 3.33 is a perfectly normal CSS-pixel coordinate.

Reading Geometry: getBoundingClientRect()

element.getBoundingClientRect() takes no arguments and returns an object with left, top, right, bottom, width, and height — the element’s position (in viewport coordinates) and size, including border and padding but not margin:

let box = document.querySelector("#tooltip-anchor").getBoundingClientRect();
box.left;    // x coordinate of the upper-left corner, relative to the viewport
box.top;     // y coordinate of the upper-left corner
box.width;   // box.right - box.left
box.height;  // box.bottom - box.top

Block elements (<div>, <p>, images) are always rectangular, so a single rectangle fully describes them. Inline elements (<span>, <em>, <code>) can wrap across multiple lines and therefore occupy several disjoint rectangles; getBoundingClientRect() still returns one rectangle enclosing all of them, which may be wider than any individual line. To get the per-line rectangles instead, call getClientRects(), which returns a read-only, array-like collection of rectangle objects shaped like `getBoundingClientRect()’s return value.

To go the other direction — from a point to the element rendered there — use document.elementFromPoint(x, y), passing viewport coordinates (the clientX/clientY of a mouse event work directly). It returns the innermost, topmost (z-index-wise) element at that point:

document.addEventListener("click", (event) => {
  let hit = document.elementFromPoint(event.clientX, event.clientY);
  console.log("You clicked on:", hit);
});

The offset / client / scroll Property Family

Every Element also exposes three parallel families of read-only size/position properties (scrollLeft and scrollTop are the one exception — they’re writable), plus offsetParent. They look similar but measure subtly different things, which is the single most common source of confusion in this part of the DOM:

Property What it measures Includes

offsetWidth / offsetHeight

The element’s full on-screen size.

Content + padding + border (not margin)

offsetLeft / offsetTop

Position relative to offsetParent (the nearest positioned or table ancestor, falling back to the <body> element for most other elements — never document itself).

 — 

clientWidth / clientHeight

The visible content area.

Content + padding (not border, not scrollbars in most browsers)

clientLeft / clientTop

Width of the left/top border only (rarely useful directly).

Border only

scrollWidth / scrollHeight

The full content size, including content that overflows and is hidden until scrolled. Equal to clientWidth/clientHeight when there is no overflow.

Content + padding + overflowing content

scrollLeft / scrollTop

How far the content is scrolled within the element. Writable — assign to these to scroll the element programmatically.

 — 

offsetParent names the element that offsetLeft/offsetTop are relative to; for most elements this is the document, but descendants of a positioned element (and some special cases like table cells) report coordinates relative to that ancestor instead.

A common use of scrollWidth/scrollHeight versus clientWidth/clientHeight is detecting overflow:

function isOverflowing(el) {
  return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth;
}

Most browsers also implement scrollTo() and scrollBy() directly on Element (mirroring the Window methods below), though support for these on arbitrary elements is newer and less universal than the Window versions.

Window and Document Size

For the top-level window, the equivalents live on Window and document.documentElement (the <html> element) rather than on an arbitrary Element:

window.innerWidth;    // viewport width, in CSS pixels
window.innerHeight;   // viewport height

window.scrollX;       // current horizontal scroll offset (read-only)
window.scrollY;       // current vertical scroll offset (read-only)

document.documentElement.offsetWidth;   // full document width
document.documentElement.offsetHeight;  // full document height -- for a "how far can we scroll" calculation

window.scrollX/scrollY are read-only, so scrolling the page requires one of the methods in the next section rather than assignment.

Controlling Scroll Position

scrollTo() and scrollBy()

window.scrollTo(x, y) scrolls so that document coordinates (x, y) land at the top-left of the viewport (clamped near the document’s edges if the target point is too close to them):

// Scroll to the very bottom "page" of the document
let documentHeight = document.documentElement.offsetHeight;
let viewportHeight = window.innerHeight;
window.scrollTo(0, documentHeight - viewportHeight);

window.scrollBy(dx, dy) behaves the same way but is relative to the current scroll position rather than absolute:

// Auto-scroll 50px every half second (remember to clearInterval() to stop it!)
let timer = setInterval(() => window.scrollBy(0, 50), 500);

Both methods also accept a single options object instead of two numbers, which is required for smooth scrolling (see below):

window.scrollTo({
  left: 0,
  top: documentHeight - viewportHeight,
  behavior: "smooth",
});

scrollIntoView()

Rather than computing a target scroll offset yourself, element.scrollIntoView() scrolls the nearest scrolling ancestor(s) so the element becomes visible. By default it aligns the element’s top edge near the top of the viewport; passing false aligns its bottom edge with the viewport’s bottom instead:

document.querySelector("#section-3").scrollIntoView();       // align to top
document.querySelector("#section-3").scrollIntoView(false);  // align to bottom

An options object gives finer control — behavior: "smooth" for animated scrolling, and block/inline to choose vertical/horizontal alignment ("start", "end", "center", or "nearest"):

document.querySelector("#section-3").scrollIntoView({
  behavior: "smooth",
  block: "center",   // center the element vertically in the viewport
  inline: "nearest",
});

The CSS scroll-behavior Property

Smooth scrolling can also be declared entirely in CSS, without touching behavior: "smooth" in JavaScript at all. Setting scroll-behavior: smooth on the scrolling element (often html) makes any programmatic scroll that targets it animate smoothly by default — including plain scrollTo()/scrollBy()/scrollIntoView() calls that don’t pass a behavior option, and same-page anchor navigation (<a href="#section-3">):

html {
  scroll-behavior: smooth;
}
// With the CSS above in place, this now animates -- no `behavior` option needed
window.scrollTo(0, 0);

This is useful as a blanket default, but it applies to every scroll on that element, including ones triggered by keyboard navigation or Element.focus(), which is not always desirable. Passing an explicit behavior: "auto" in a given JS call opts that one call back out of smooth scrolling regardless of the CSS setting. scroll-behavior has no effect on the user’s own manual scrolling (mouse wheel, trackpad, scrollbar drag) — it only governs scrolls initiated by script or by anchor-link navigation.