Measuring Browser and DOM Dimensions

Last update: 2026-01-08 10:38:14 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Browser APIs expose several coordinate systems because a screen, browser window, viewport, document, and element answer different measurement questions. Choosing an API by name alone can therefore produce scrollbar, rounding, or scrolling errors.

Window, Screen, and Viewport Measurements #

APIMeasurement
window.innerWidth, innerHeightLayout viewport, including a rendered classic scrollbar
window.outerWidth, outerHeightBrowser window’s outside dimensions
screen.width, screen.heightScreen dimensions in CSS pixels
screen.availWidth, screen.availHeightScreen space excluding reserved operating-system areas
document.documentElement.clientWidth, clientHeightLayout viewport excluding a classic scrollbar

CSS pixels are logical units. A CSS pixel can cover multiple device pixels on a high-density display, so screen.width does not necessarily equal the panel’s physical pixel count.

outerWidth and outerHeight include browser chrome. Their difference from the inner dimensions depends on window decoration, tabs, toolbars, and display scaling; thus they should not drive page layout.

js
const viewport = {
  width: document.documentElement.clientWidth,
  height: document.documentElement.clientHeight,
};

Use the root element’s client dimensions when a calculation must exclude a classic scrollbar. Overlay scrollbars consume no layout width, so the difference between innerWidth and clientWidth is platform-dependent.

Layout and Visual Viewports #

The layout viewport establishes the containing block used by page layout. The visual viewport is the portion currently visible after pinch zoom or an on-screen keyboard changes the unobscured area. On supporting browsers, window.visualViewport exposes width, height, scale, offsetLeft, and offsetTop:

js
const viewport = window.visualViewport;

viewport?.addEventListener("resize", positionOverlay);
viewport?.addEventListener("scroll", positionOverlay);

Use these events for overlays that must follow the visible area. Normal document layout should continue to use CSS and the layout viewport; moving the entire page in response to visual viewport events can fight browser scrolling.

Element Box Measurements #

APIIncluded AreaPrecision
clientWidth, clientHeightContent plus padding, excluding border and scrollbarInteger
scrollWidth, scrollHeightMinimum content-plus-padding extent needed without scrollingInteger
offsetWidth, offsetHeightBorder box, including a rendered scrollbarInteger
getBoundingClientRect()Border-box geometry in viewport coordinatesFractional

clientWidth and clientHeight are zero for inline elements that do not create a CSS box. On the root element, they represent the viewport. Historical quirks mode can make the body element participate in viewport measurement, but standards-mode documents should use document.documentElement.

Scrollable overflow makes scrollHeight or scrollWidth exceed the matching client dimension:

js
function hasVerticalOverflow(element) {
  return element.scrollHeight > element.clientHeight;
}

Pseudo-element content can contribute to scroll dimensions. Subpixel layout is rounded by the integer-valued APIs, so use getBoundingClientRect() when fractional geometry matters.

Coordinate Systems and Scrolling #

getBoundingClientRect() returns coordinates relative to the viewport:

js
const rect = element.getBoundingClientRect();
console.log(rect.left, rect.top, rect.width, rect.height);

Convert a viewport coordinate to a document coordinate by adding the current scroll offset:

js
const documentTop = rect.top + window.scrollY;
const documentLeft = rect.left + window.scrollX;

Transforms affect the returned rectangle. The result is the smallest axis-aligned viewport rectangle containing the transformed border boxes, not the element’s untransformed layout dimensions.

Measuring at the Correct Time #

Geometry can change after fonts, images, stylesheets, or application data load. Measure after the dependency that affects layout becomes available. Use ResizeObserver when code must react to later size changes:

js
const observer = new ResizeObserver(([entry]) => {
  const { inlineSize, blockSize } = entry.contentBoxSize[0];
  console.log({ inlineSize, blockSize });
});

observer.observe(element);

Reading geometry can force the browser to finish pending style and layout work. Batch reads before writes in animation code because alternating layout reads and style changes can cause repeated synchronous layout.

requestAnimationFrame selects a rendering phase but does not make layout reads free. Read all required geometry first, compute the next state, and then apply style or DOM writes as a separate batch.

Comments

Read or comment on GitHub (sign-in required to comment)