Skip to main content

Element Size and Scrolling in JavaScript

Every visible element on a web page occupies space: it has a width, a height, padding, borders, and sometimes scrollable content that extends beyond what the user can see. JavaScript provides a set of geometry properties on every DOM element that let you measure all of these dimensions precisely.

These properties are essential for building interactive layouts: positioning tooltips, implementing virtual scrolling, detecting when an element is fully visible, creating drag-and-drop interfaces, building custom scrollbars, and calculating where to place dynamically created UI elements.

This guide covers every geometry property available on DOM elements. You will learn what each property measures, how they relate to each other through a visual model, and why you should use these properties instead of getComputedStyle for reading dimensions.

Geometry Properties Overview​

Before diving into individual properties, you need a mental model of how an element's box is structured and which property measures which part. Every element's box follows the CSS box model: content, surrounded by padding, surrounded by border, surrounded by margin.

Here is a visual diagram showing where each geometry property measures. This is the single most important reference for this entire topic:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ offsetParent β”‚
β”‚ β”‚
β”‚ offsetLeft β”‚
β”‚ ◄──────► β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–² β”‚
β”‚ β”‚ BORDER β”‚ β”‚ β”‚
β”‚ β”‚ clientLeft β”‚ β”‚ β”‚
β”‚ β”‚ ◄────► β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–² β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ PADDING β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ CONTENT β”‚ β”‚clientH β”‚offset β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚Height β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ PADDING β”‚ β–Ό β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ BORDER β”‚ β–Ό β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ ◄─────────────────────────────────────────────► β”‚
β”‚ offsetWidth β”‚
β”‚ β”‚
β”‚ ◄───────────────────────────► β”‚
β”‚ clientWidth β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is the same information as a quick reference table:

PropertyWhat It Measures
offsetParentNearest positioned ancestor element
offsetLeft / offsetTopPosition relative to offsetParent
offsetWidth / offsetHeightFull outer size (content + padding + border + scrollbar)
clientLeft / clientTopBorder width (left/top side)
clientWidth / clientHeightInner visible area (content + padding, no border, no scrollbar)
scrollWidth / scrollHeightFull content size including scrolled-out parts
scrollLeft / scrollTopHow far the content is scrolled (writable)

Let's examine each property in detail using this example element:

<style>
#box {
width: 300px;
height: 200px;
padding: 20px;
border: 5px solid #333;
overflow: auto;
}
</style>

<div id="box">
<p>Content that may be taller than the box...</p>
<!-- imagine enough content to create a scrollbar -->
</div>

offsetParent, offsetLeft, offsetTop​

These three properties work together to tell you where an element is positioned relative to its nearest positioned ancestor.

offsetParent​

The offsetParent is the nearest ancestor element that has a CSS position other than static (i.e., relative, absolute, fixed, or sticky). If no such ancestor exists, offsetParent is the <body> element (or <td>/<th>/<table> in some cases).

<div id="container" style="position: relative;">
<div id="wrapper">
<div id="box">Content</div>
</div>
</div>
let box = document.getElementById("box");

// #wrapper has position: static (default), so it's skipped
// #container has position: relative, so it IS the offsetParent
console.log(box.offsetParent); // <div id="container">
console.log(box.offsetParent.id); // "container"

If no ancestor is positioned:

// When no ancestor has position other than static:
console.log(box.offsetParent); // <body> element

When offsetParent Is null​

There are three cases where offsetParent returns null:

// 1. The <body> element itself
console.log(document.body.offsetParent); // null

// 2. Elements with position: fixed
let fixedEl = document.createElement("div");
fixedEl.style.position = "fixed";
document.body.append(fixedEl);
console.log(fixedEl.offsetParent); // null

// 3. Elements not in the DOM or with display: none
let hiddenEl = document.getElementById("hidden");
hiddenEl.style.display = "none";
console.log(hiddenEl.offsetParent); // null
console.log(hiddenEl.offsetWidth); // 0
console.log(hiddenEl.offsetHeight); // 0
tip

You can use the offsetParent check as a quick way to determine if an element is hidden or detached from the DOM:

function isElementHidden(element) {
return element.offsetParent === null && element !== document.body;
}

This is not foolproof (fixed-position elements return null too), but it covers the most common cases.

offsetLeft and offsetTop​

These properties give the element's position (in pixels) relative to the upper-left corner of its offsetParent. They measure the distance from the outer edge of the offsetParent's padding area to the outer edge of the element's border.

<div id="container" style="position: relative; padding: 30px;">
<div id="box" style="margin: 10px; border: 5px solid #333; padding: 20px;">
Content
</div>
</div>
let box = document.getElementById("box");

console.log(box.offsetParent.id); // "container"
console.log(box.offsetLeft); // 40 (30px container padding + 10px box margin)
console.log(box.offsetTop); // 40 (30px container padding + 10px box margin)

Getting the Absolute Position on the Page​

To get an element's position relative to the entire document (not just its offsetParent), you need to walk up the offsetParent chain and sum the offsets:

function getAbsolutePosition(element) {
let left = 0;
let top = 0;
let current = element;

while (current) {
left += current.offsetLeft;
top += current.offsetTop;
current = current.offsetParent;
}

return { left, top };
}

let box = document.getElementById("box");
let pos = getAbsolutePosition(box);
console.log(`Position: ${pos.left}px from left, ${pos.top}px from top`);
info

For most practical purposes, element.getBoundingClientRect() is a more reliable and simpler way to get an element's position. It returns coordinates relative to the viewport and accounts for transforms, scrolling, and other factors that the offset* properties do not handle correctly.

let rect = box.getBoundingClientRect();
console.log(rect.left, rect.top); // Position relative to viewport
console.log(rect.left + scrollX, rect.top + scrollY); // Position relative to document

offsetWidth and offsetHeight​

These properties give you the full outer dimensions of the element, including content, padding, borders, and scrollbar (if present). They are the "outermost" size measurements available.

<style>
#box {
width: 300px; /* content width */
height: 200px; /* content height */
padding: 20px; /* 20px on each side */
border: 5px solid #333; /* 5px on each side */
}
</style>

<div id="box">Content</div>
let box = document.getElementById("box");

// offsetWidth = border-left + padding-left + content-width + padding-right + border-right
// offsetWidth = 5 + 20 + 300 + 20 + 5 = 350
console.log(box.offsetWidth); // 350

// offsetHeight = border-top + padding-top + content-height + padding-bottom + border-bottom
// offsetHeight = 5 + 20 + 200 + 20 + 5 = 250
console.log(box.offsetHeight); // 250

With a Scrollbar​

When the element has a scrollbar (e.g., overflow: auto or overflow: scroll), the scrollbar width is included in offsetWidth/offsetHeight but reduces the available content area:

<style>
#box {
width: 300px;
height: 200px;
padding: 20px;
border: 5px solid #333;
overflow: auto; /* may show scrollbar */
}
</style>
let box = document.getElementById("box");

// offsetWidth is still 350: scrollbar is INSIDE the box
console.log(box.offsetWidth); // 350

// The scrollbar (typically ~16px wide on desktop) eats into the content area
// Content area = 300 - 16 = 284px

Zero Dimensions Mean Hidden or Detached​

If offsetWidth and offsetHeight are both 0, the element is either hidden (display: none), not in the DOM, or has no dimensions:

function isRendered(element) {
return element.offsetWidth > 0 || element.offsetHeight > 0;
}

let visible = document.getElementById("visible-box");
let hidden = document.getElementById("hidden-box"); // display: none

console.log(isRendered(visible)); // true
console.log(isRendered(hidden)); // false

clientLeft and clientTop​

Despite their names suggesting a "left position" and "top position," these properties actually measure the width of the left border and the width of the top border respectively.

<style>
#box {
border-left: 10px solid #333;
border-top: 5px solid #333;
border-right: 3px solid #333;
border-bottom: 8px solid #333;
padding: 20px;
width: 300px;
height: 200px;
}
</style>

<div id="box">Content</div>
let box = document.getElementById("box");

console.log(box.clientLeft); // 10 (left border width)
console.log(box.clientTop); // 5 (top border width)

Scrollbar Shifts clientLeft​

On systems with right-to-left (RTL) text direction, the vertical scrollbar appears on the left side. In this case, clientLeft includes both the border width and the scrollbar width:

<div id="rtl-box" style="direction: rtl; overflow: auto; width: 300px; height: 200px; border-left: 5px solid #333;">
<!-- Long content that causes a scrollbar -->
</div>
let rtlBox = document.getElementById("rtl-box");
// In RTL, clientLeft = border-left + scrollbar width
// e.g., 5 + 16 = 21
console.log(rtlBox.clientLeft); // 21 (on systems with ~16px scrollbar)

For left-to-right layouts (the default), the scrollbar is on the right side and does not affect clientLeft.

Practical Use: Calculating Border Sizes​

function getBorderWidths(element) {
return {
left: element.clientLeft,
top: element.clientTop,
// For right and bottom, we need to calculate:
right: element.offsetWidth - element.clientWidth - element.clientLeft,
bottom: element.offsetHeight - element.clientHeight - element.clientTop
};
}

let box = document.getElementById("box");
console.log(getBorderWidths(box));
// { left: 10, top: 5, right: 3, bottom: 8 }

clientWidth and clientHeight​

These properties measure the inner visible area of the element: content plus padding, but excluding borders and scrollbars. This is the area where content can actually appear.

<style>
#box {
width: 300px;
height: 200px;
padding: 20px;
border: 5px solid #333;
}
</style>

<div id="box">Content</div>
let box = document.getElementById("box");

// clientWidth = padding-left + content-width + padding-right
// clientWidth = 20 + 300 + 20 = 340
console.log(box.clientWidth); // 340

// clientHeight = padding-top + content-height + padding-bottom
// clientHeight = 20 + 200 + 20 = 240
console.log(box.clientHeight); // 240

With a Scrollbar​

When there is a scrollbar, clientWidth and clientHeight exclude it. This is a key difference from offsetWidth/offsetHeight:

<style>
#box {
width: 300px;
height: 200px;
padding: 20px;
border: 5px solid #333;
overflow: auto; /* scrollbar appears when content overflows */
}
</style>
let box = document.getElementById("box");

// Without scrollbar:
// clientWidth = 20 + 300 + 20 = 340
// offsetWidth = 5 + 20 + 300 + 20 + 5 = 350

// With scrollbar (assume 16px scrollbar):
// The scrollbar takes space from the content area
// clientWidth = 20 + (300 - 16) + 20 = 324
// offsetWidth = 5 + 20 + 300 + 20 + 5 = 350 (unchanged, scrollbar is inside)

console.log(box.clientWidth); // 324 (scrollbar excluded)
console.log(box.offsetWidth); // 350 (scrollbar included)

This makes clientWidth the best property for determining how much space is actually available for content rendering.

Relationship Between offset and client Properties​

offsetWidth  = clientLeft + clientWidth + scrollbarWidth + border-right
offsetHeight = clientTop + clientHeight + scrollbarHeight + border-bottom

You can derive the scrollbar width from these:

function getScrollbarWidth(element) {
// Only meaningful if the element has overflow: auto/scroll
return element.offsetWidth - element.clientWidth - element.clientLeft * 2;
// Simplified: assumes equal left and right borders
}

// More precise:
function getVerticalScrollbarWidth(element) {
let computed = getComputedStyle(element);
let borderRight = parseFloat(computed.borderRightWidth);
return element.offsetWidth - element.clientWidth - element.clientLeft - borderRight;
}

Elements Without Padding or Border​

For elements with no padding and no border, clientWidth/clientHeight equal the content area dimensions (minus scrollbar if present):

let simplebox = document.getElementById("simple");
// style: width: 300px; height: 200px; (no padding, no border)

console.log(simplebox.clientWidth); // 300
console.log(simplebox.clientHeight); // 200
console.log(simplebox.offsetWidth); // 300
console.log(simplebox.offsetHeight); // 200

scrollWidth and scrollHeight​

These properties measure the full content size, including the parts that are scrolled out of view. Think of them as clientWidth and clientHeight, but expanded to encompass all the content.

<style>
#box {
width: 300px;
height: 200px;
padding: 20px;
border: 5px solid #333;
overflow: auto;
}
</style>

<div id="box">
<!-- Content that is 500px tall -->
<div style="height: 500px;">Tall content...</div>
</div>
let box = document.getElementById("box");

// clientHeight = inner visible height (content + padding, no scrollbar)
console.log(box.clientHeight); // 240 (20 + 200 + 20)

// scrollHeight = full height of content including scrolled-out parts
console.log(box.scrollHeight); // 540 (20 + 500 + 20, padding + full content)

// clientWidth stays the same if content doesn't overflow horizontally
console.log(box.clientWidth); // 324 (with 16px scrollbar taken out)

// scrollWidth = full width of content
console.log(box.scrollWidth); // 324 (no horizontal overflow, matches clientWidth)

When There Is No Overflow​

If the content fits within the element without scrolling, scrollWidth/scrollHeight equal clientWidth/clientHeight:

let noOverflow = document.getElementById("short-box");
// Content fits within the box

console.log(noOverflow.clientHeight); // 240
console.log(noOverflow.scrollHeight); // 240 (same, no overflow)

Practical Use: Expanding to Full Content Height​

A common use of scrollHeight is to expand an element to show all its content (e.g., expanding a collapsed section):

function expandToFullHeight(element) {
// Set the height to show all content
element.style.height = element.scrollHeight + "px";
}

function collapseElement(element) {
element.style.height = "0px";
}

// Toggle expand/collapse
function toggleExpand(element) {
if (element.style.height === "0px" || !element.style.height) {
element.style.height = element.scrollHeight + "px";
} else {
element.style.height = "0px";
}
}

Detecting if an Element Has Overflow​

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

function hasHorizontalOverflow(element) {
return element.scrollWidth > element.clientWidth;
}

let box = document.getElementById("box");
console.log(hasVerticalOverflow(box)); // true (500px content in 200px box)
console.log(hasHorizontalOverflow(box)); // false

Full Document Size​

To get the full dimensions of the entire document (the scrollable page area), use scrollWidth and scrollHeight on document.documentElement:

// Full page dimensions (including scrolled-out parts)
let fullWidth = Math.max(
document.body.scrollWidth, document.documentElement.scrollWidth,
document.body.offsetWidth, document.documentElement.offsetWidth,
document.body.clientWidth, document.documentElement.clientWidth
);

let fullHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);

console.log(`Full document: ${fullWidth} x ${fullHeight}`);
info

The reason for using Math.max across multiple sources is that different browsers report the document size inconsistently through different properties. Taking the maximum across all variants gives the most reliable result.

scrollLeft and scrollTop​

These properties tell you how far the content has been scrolled inside the element. Unlike all the other geometry properties covered so far, scrollLeft and scrollTop are writable, meaning you can programmatically scroll an element by setting them.

Reading Scroll Position​

let box = document.getElementById("box");

// How many pixels the content has been scrolled from the top
console.log(box.scrollTop); // 0 (at the top, no scrolling yet)

// How many pixels the content has been scrolled from the left
console.log(box.scrollLeft); // 0 (at the left, no horizontal scroll)

// After the user scrolls down 100px:
console.log(box.scrollTop); // 100

Writing Scroll Position (Programmatic Scrolling)​

let box = document.getElementById("box");

// Scroll to the top
box.scrollTop = 0;

// Scroll to a specific position (100px from top)
box.scrollTop = 100;

// Scroll to the bottom
box.scrollTop = box.scrollHeight - box.clientHeight;

// Scroll to the far right
box.scrollLeft = box.scrollWidth - box.clientWidth;

The value is automatically clamped. You cannot scroll past the boundaries:

box.scrollTop = -50;     // Clamped to 0
box.scrollTop = 999999; // Clamped to maximum scroll position

Detecting Scroll Position​

let box = document.getElementById("box");

box.addEventListener("scroll", () => {
let maxScroll = box.scrollHeight - box.clientHeight;

// Is at the very top?
if (box.scrollTop === 0) {
console.log("At the top");
}

// Is at the very bottom?
if (Math.abs(box.scrollTop - maxScroll) < 1) {
console.log("At the bottom");
}

// Scroll percentage
let percent = (box.scrollTop / maxScroll) * 100;
console.log(`Scrolled: ${percent.toFixed(1)}%`);
});
caution

When checking if the user has scrolled to the bottom, use a small threshold instead of strict equality. Scroll positions can be fractional on high-DPI displays, so scrollTop may never exactly equal scrollHeight - clientHeight:

// ❌ May never be exactly true on high-DPI screens
if (box.scrollTop === box.scrollHeight - box.clientHeight) { ... }

// βœ… Use a small threshold
if (box.scrollTop >= box.scrollHeight - box.clientHeight - 1) { ... }

// βœ… Or use Math.abs
if (Math.abs(box.scrollTop - (box.scrollHeight - box.clientHeight)) < 1) { ... }

Practical Example: Scroll-Dependent UI​

let content = document.getElementById("scrollable-content");
let scrollIndicator = document.getElementById("scroll-indicator");
let backToTopBtn = document.getElementById("back-to-top");

content.addEventListener("scroll", () => {
let maxScroll = content.scrollHeight - content.clientHeight;
let progress = content.scrollTop / maxScroll;

// Update a progress bar
scrollIndicator.style.width = `${progress * 100}%`;

// Show "back to top" button after scrolling 200px
backToTopBtn.hidden = content.scrollTop < 200;
});

backToTopBtn.addEventListener("click", () => {
// Smooth scroll to top (if supported on the element)
content.scrollTop = 0;
// or with smooth behavior:
content.scrollTo({ top: 0, behavior: "smooth" });
});

Practical Example: Infinite Scroll​

let feed = document.getElementById("feed");

feed.addEventListener("scroll", () => {
// Check if user is near the bottom (within 100px)
let distanceFromBottom = feed.scrollHeight - feed.clientHeight - feed.scrollTop;

if (distanceFromBottom < 100) {
loadMoreContent();
}
});

async function loadMoreContent() {
// Prevent multiple simultaneous loads
if (feed.dataset.loading === "true") return;
feed.dataset.loading = "true";

try {
let response = await fetch("/api/more-items");
let items = await response.json();

items.forEach(item => {
let div = document.createElement("div");
div.className = "feed-item";
div.textContent = item.title;
feed.append(div);
});
} finally {
feed.dataset.loading = "false";
}
}

Why Not getComputedStyle for Width/Height?​

You might wonder why we need all these geometry properties when getComputedStyle can read CSS width and height. There are several important reasons why geometry properties are the correct choice for measuring elements.

Problem 1: getComputedStyle Returns CSS Values, Not Actual Rendered Size​

The CSS width and height properties may return "auto", a percentage, or a value that does not reflect the actual rendered size:

<style>
#box {
width: auto; /* depends on content and parent */
}
</style>

<div id="box">Some content</div>
let box = document.getElementById("box");
let computed = getComputedStyle(box);

console.log(computed.width); // "785px" (resolved, but...)
// This value depends on the box model and may not be what you expect

// Geometry properties always give you the actual rendered dimensions:
console.log(box.offsetWidth); // 785 (border + padding + content)
console.log(box.clientWidth); // 775 (padding + content, no border)

Problem 2: box-sizing Changes What width Means​

With box-sizing: border-box, the CSS width includes padding and border. With box-sizing: content-box (the default), it does not. The value from getComputedStyle reflects the CSS property, but its meaning changes based on box-sizing:

<style>
#content-box {
box-sizing: content-box;
width: 300px;
padding: 20px;
border: 5px solid #333;
}

#border-box {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 5px solid #333;
}
</style>
let contentBox = document.getElementById("content-box");
let borderBox = document.getElementById("border-box");

let computedCB = getComputedStyle(contentBox);
let computedBB = getComputedStyle(borderBox);

// getComputedStyle returns the CSS value (meaning differs!)
console.log(computedCB.width); // "300px" (content-only width)
console.log(computedBB.width); // "300px" (total width including padding+border)

// Geometry properties are always unambiguous:
console.log(contentBox.offsetWidth); // 350 (5+20+300+20+5)
console.log(borderBox.offsetWidth); // 300 (total size IS 300 with border-box)

console.log(contentBox.clientWidth); // 340 (20+300+20)
console.log(borderBox.clientWidth); // 250 (20+210+20, content area is smaller)

With geometry properties, you always know exactly what you are measuring. offsetWidth is always the full outer width. clientWidth is always the inner area without borders and scrollbars. There is no ambiguity.

Problem 3: Scrollbar Width Is Not Accounted For​

getComputedStyle does not subtract the scrollbar width from the returned width value. But the scrollbar does reduce the available space for content:

let scrollableBox = document.getElementById("scrollable");
let computed = getComputedStyle(scrollableBox);

console.log(computed.width); // "300px" (CSS width, doesn't subtract scrollbar)

// clientWidth correctly excludes the scrollbar:
console.log(scrollableBox.clientWidth); // 284 (300 - 16px scrollbar)

Problem 4: getComputedStyle Can Return Non-Numeric Values​

let computed = getComputedStyle(element);
console.log(computed.width); // Could be "auto" in some situations

Geometry properties always return numeric values (or 0 for hidden elements).

Summary: When to Use What​

NeedUse
Full outer dimensions (with border)offsetWidth / offsetHeight
Inner visible area (no border, no scrollbar)clientWidth / clientHeight
Full content size (including overflow)scrollWidth / scrollHeight
Current scroll positionscrollLeft / scrollTop
Element position relative to parentoffsetLeft / offsetTop
Element position relative to viewportgetBoundingClientRect()
Reading CSS property values for stylinggetComputedStyle()

Use getComputedStyle when you need the CSS value (for example, reading a color, a font-size, or checking a display value). Use geometry properties when you need actual rendered dimensions and positions.

Complete Practical Example​

Here is a comprehensive example that uses all the geometry properties to build an element inspector that displays real-time measurements:

<!DOCTYPE html>
<html>
<head>
<title>Element Geometry Inspector</title>
<style>
#target {
width: 400px;
height: 250px;
padding: 25px;
border: 8px solid #2196f3;
margin: 40px auto;
overflow: auto;
background: #f5f5f5;
position: relative;
}

#target .tall-content {
height: 600px;
background: linear-gradient(to bottom, #e3f2fd, #bbdefb);
padding: 10px;
}

#info {
max-width: 600px;
margin: 20px auto;
font-family: monospace;
font-size: 14px;
line-height: 1.8;
}

.label { font-weight: bold; color: #1565c0; }
.value { color: #333; }
.section { margin-top: 16px; padding-top: 8px; border-top: 1px solid #ddd; }
</style>
</head>
<body>
<h1 style="text-align: center;">Element Geometry Inspector</h1>

<div id="target">
<div class="tall-content">
Scroll this content to see scrollTop change in real time.
The content is taller than the container to create a scrollbar.
</div>
</div>

<div id="info"></div>

<script>
let target = document.getElementById("target");
let info = document.getElementById("info");

function updateInfo() {
info.innerHTML = `
<div class="section">
<span class="label">offsetParent:</span>
<span class="value">${target.offsetParent?.tagName || "null"}</span><br>
<span class="label">offsetLeft:</span>
<span class="value">${target.offsetLeft}px</span><br>
<span class="label">offsetTop:</span>
<span class="value">${target.offsetTop}px</span>
</div>

<div class="section">
<span class="label">offsetWidth:</span>
<span class="value">${target.offsetWidth}px</span>
<span class="value">(border + padding + content + scrollbar)</span><br>
<span class="label">offsetHeight:</span>
<span class="value">${target.offsetHeight}px</span>
</div>

<div class="section">
<span class="label">clientLeft:</span>
<span class="value">${target.clientLeft}px</span>
<span class="value">(left border width)</span><br>
<span class="label">clientTop:</span>
<span class="value">${target.clientTop}px</span>
<span class="value">(top border width)</span>
</div>

<div class="section">
<span class="label">clientWidth:</span>
<span class="value">${target.clientWidth}px</span>
<span class="value">(padding + content, no border/scrollbar)</span><br>
<span class="label">clientHeight:</span>
<span class="value">${target.clientHeight}px</span>
</div>

<div class="section">
<span class="label">scrollWidth:</span>
<span class="value">${target.scrollWidth}px</span>
<span class="value">(full content width)</span><br>
<span class="label">scrollHeight:</span>
<span class="value">${target.scrollHeight}px</span>
<span class="value">(full content height)</span>
</div>

<div class="section">
<span class="label">scrollLeft:</span>
<span class="value">${target.scrollLeft.toFixed(1)}px</span><br>
<span class="label">scrollTop:</span>
<span class="value">${target.scrollTop.toFixed(1)}px</span><br>
<span class="label">Scroll progress:</span>
<span class="value">${((target.scrollTop / (target.scrollHeight - target.clientHeight)) * 100).toFixed(1)}%</span><br>
<span class="label">Has overflow:</span>
<span class="value">${target.scrollHeight > target.clientHeight ? "Yes" : "No"}</span>
</div>

<div class="section">
<span class="label">Scrollbar width:</span>
<span class="value">${target.offsetWidth - target.clientWidth - target.clientLeft * 2}px</span>
</div>
`;
}

// Update on scroll
target.addEventListener("scroll", updateInfo);

// Update on resize
window.addEventListener("resize", updateInfo);

// Initial update
updateInfo();
</script>
</body>
</html>

This inspector updates in real time as you scroll the target element, showing how scrollTop changes while all other geometry properties remain constant (since the element's dimensions do not change during scrolling).

Summary​

JavaScript provides a complete set of geometry properties on every DOM element for measuring size, position, and scroll state.

Position:

  • offsetParent is the nearest positioned ancestor (or body). Returns null for hidden, fixed, or body elements.
  • offsetLeft / offsetTop give the element's position relative to its offsetParent, measured from the outer edge.

Outer Size (including borders and scrollbar):

  • offsetWidth / offsetHeight measure the full outer size: content + padding + border + scrollbar. Returns 0 for hidden elements.

Border Size:

  • clientLeft / clientTop measure the left and top border widths (plus scrollbar width in RTL layouts).

Inner Visible Size (no borders, no scrollbar):

  • clientWidth / clientHeight measure the visible inner area: content + padding, excluding borders and scrollbar. This is the area available for content.

Full Content Size (including overflow):

  • scrollWidth / scrollHeight measure the total content dimensions, including scrolled-out parts. Equal to clientWidth/clientHeight when there is no overflow.

Scroll Position (readable and writable):

  • scrollLeft / scrollTop tell you (and let you set) how far the content is scrolled. Values are automatically clamped to valid ranges.

Why not getComputedStyle for dimensions?

  • Geometry properties are unambiguous regardless of box-sizing.
  • They correctly account for scrollbar width.
  • They always return numeric pixel values.
  • They reflect the actual rendered size, not the CSS property value.

Use geometry properties for measuring and positioning. Use getComputedStyle for reading CSS values like colors, fonts, and display modes.