Developers look this API up under many names: getboundingclientrect as one token, get bounding client rect with spaces, getboundingclientrect javascript, javascript getboundingclientrect, js getboundingclientrect, or simply boundingclientrect. Two themes show up constantly in search: what DOMRect.width actually measures (the same intent as mdn getboundingclientrect width includes padding border), and getboundingclientrect performance when the method is called inside scroll or animation loops.
Element.getBoundingClientRect() takes no arguments. It returns a DOMRect (or an object that quacks like one) in CSS pixels relative to the viewport, with x / y aligned to left / top. Margins are outside the box the rectangle describes; padding and border are inside the width / height border box, matching the geometry described on MDN and in the CSSOM View Module.
Pick the element first (get element in JavaScript, DOM selectors); for width-only reads that do not need viewport position, compare offsetWidth / clientWidth.
The HTML below is the same compact fixture used to capture the printed geometry.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
#probe {
width: 40px;
height: 12px;
padding: 5px;
border: 2px solid black;
margin: 10px;
display: block;
box-sizing: content-box;
}
#gone {
display: none;
width: 100px;
height: 50px;
}
#invis {
visibility: hidden;
width: 10px;
height: 10px;
display: block;
box-sizing: content-box;
}
</style>
</head>
<body>
<div id="probe"></div>
<div id="gone"></div>
<div id="invis"></div>
</body>
</html>The printed numbers below were captured with jsdom 24.0.0 plus the same border-box layout shim (jsdom otherwise leaves layout geometry at zero). In a desktop browser on this markup, width and height match the same border-box totals; left / top / x / y here are all 0 because the fixture sits at the origin of the jsdom viewport.
Tested on: Node.js v20.18.2 with jsdom for geometry snippets.
{run=false}on DOM fences matches hosts without a layout engine.
DOMRect.width and DOMRect.height (border box)
For a rendered element, rect.width and rect.height from getBoundingClientRect() describe the element’s border box after layout, including horizontal and vertical padding and border on that axis, in CSS pixels. They do not add the margin. That answers the classic “mdn getboundingclientrect width includes padding border” wording: yes—the border-box width and height include padding and border (and inline-axis scrollbars when they consume space), but not margin.
For #probe, the horizontal border box is 40 + (5 + 5) padding + (2 + 2) border → 54 px; the vertical border box is 12 + (5 + 5) + (2 + 2) → 26 px.
const rect = document.getElementById("probe").getBoundingClientRect();
console.log(rect.width);
console.log(rect.height);You should see 2 lines, in order: 54, 26.
left, top, x, and y
left and top are distances from the viewport’s top-left corner to the element’s border box. x and y are aliases for left and top.
const rect = document.getElementById("probe").getBoundingClientRect();
console.log(rect.left);
console.log(rect.top);
console.log(rect.x);
console.log(rect.y);You should see 4 lines, in order: 0, 0, 0, 0.
If the element is scrolled above the viewport, top can be negative; if it sits to the left, left can be negative—even though width / height stay non-zero when the box exists.
Document coordinates with scrollX / scrollY
getBoundingClientRect() is viewport-relative. To approximate position in document coordinates, add the window scroll offset:
const el = document.getElementById("probe");
const rect = el.getBoundingClientRect();
console.log(rect.top + window.scrollY);
console.log(rect.left + window.scrollX);You should see 2 lines, in order: 0, 0.
In this harness scrollY / scrollX are zero; after scrolling a long page in a browser, the sums move with the document.
display: none vs visibility: hidden
With display: none, the element does not participate in layout, so every DOMRect dimension is zero:
const rect = document.getElementById("gone").getBoundingClientRect();
console.log(rect.width);
console.log(rect.height);You should see 2 lines, in order: 0, 0.
visibility: hidden still reserves layout space, so the border box size is preserved:
const rect = document.getElementById("invis").getBoundingClientRect();
console.log(rect.width);
console.log(rect.height);You should see 2 lines, in order: 10, 10.
getBoundingClientRect performance and layout
Reading geometry after a DOM or style change can force the browser to flush pending style and layout work so the numbers are up to date. Alternating writes and reads in a tight loop (for example, setting element.style.width then immediately calling getBoundingClientRect() on many siblings) causes layout thrashing and is a common source of jank.
Mitigations:
- Batch writes (change classes or
cssTextonce), then batch reads, ideally insiderequestAnimationFramewhen tying work to paint. - Prefer
IntersectionObserverfor “is this near the viewport?” instead of callinggetBoundingClientRect()on everyscrollevent. - Prefer
ResizeObserverwhen you only care about size changes, not absolute viewport coordinates.
Transforms and zoom change how CSS pixels map to the screen; getBoundingClientRect() reflects the transformed border box in those CSS pixels—avoid hand-dividing by guessed scale factors unless you really control the math.
SVG and other roots
You can call getBoundingClientRect() on SVGElement instances; the rectangle is the tight axis-aligned box in viewport space, still subject to transforms on the node or its ancestors.
Summary
getBoundingClientRect javascript lookups are really about Element.getBoundingClientRect(): a DOMRect in CSS pixels relative to the viewport, including padding and border but not margin. People ask whether width “includes padding and border” like the MDN phrasing—yes, width / height describe the border box after layout, while margin sits outside.
Performance FAQs focus on calling the API inside tight scroll handlers: each read can force layout, so batch DOM writes before reads, prefer IntersectionObserver for visibility, and use ResizeObserver when you only care about size changes. Remember transforms and zoom map into the returned rectangle, so avoid mixing hand-tuned scale math unless you control the entire rendering pipeline.
