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 #
| API | Measurement |
|---|---|
window.innerWidth, innerHeight | Layout viewport, including a rendered classic scrollbar |
window.outerWidth, outerHeight | Browser window’s outside dimensions |
screen.width, screen.height | Screen dimensions in CSS pixels |
screen.availWidth, screen.availHeight | Screen space excluding reserved operating-system areas |
document.documentElement.clientWidth, clientHeight | Layout 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.
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:
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 #
| API | Included Area | Precision |
|---|---|---|
clientWidth, clientHeight | Content plus padding, excluding border and scrollbar | Integer |
scrollWidth, scrollHeight | Minimum content-plus-padding extent needed without scrolling | Integer |
offsetWidth, offsetHeight | Border box, including a rendered scrollbar | Integer |
getBoundingClientRect() | Border-box geometry in viewport coordinates | Fractional |
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:
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:
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:
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:
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)