cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Sizes, scrolling & coordinates

Measure element boxes, read viewport coordinates, control scrolling, handle scroll events safely, and find what is under a pointer.

By the end, you can
  • 01
    Measure real boxesChoose offset, client, or scroll sizes for the job.
  • 02
    Translate coordinatesTurn viewport rectangles into document positions.
  • 03
    Scroll without jankUse scroll APIs, passive listeners, rAF throttling, and hit testing.

The map of every box

Front-end work often asks deceptively physical questions: how wide is this card, how far has this panel scrolled, where is this button on the screen, and what is under the pointer right now? The browser already knows, because it has just laid out the page. This lesson is about reading those answers without guessing.

There are two families of measurements. Box measurements describe an element’s own rectangle: its content, padding, border, scrollbar, and overflowing content. Coordinate measurements describe where a rectangle or point sits in the viewport or in the whole document. Mixing those two is the source of most layout bugs.

The definition

Size properties answer “how big is this element?” Coordinate APIs answer “where is this element or point?” Scrolling connects them by changing which part of a larger document is visible.

Real-life analogyA measured box is a framed picture

Imagine hanging a picture. If you measure the whole framed object, you include the wooden frame. If you measure inside the frame, you include the mat and the picture but not the frame itself. If the artwork is a long scroll rolled behind the frame, the visible part is smaller than the full canvas.

In real life: The picture
In JavaScript: content
In real life: The mat around the picture
In JavaScript: padding
In real life: The wooden frame
In JavaScript: border
In real life: Space on the wall
In JavaScript: margin: not counted by these properties
In real life: The whole framed picture
In JavaScript: offsetWidth and offsetHeight
In real life: Inside the frame
In JavaScript: clientWidth and clientHeight
In real life: A larger canvas rolled behind the frame
In JavaScript: scrollWidth and scrollHeight

Where the analogy stops: Real frames do not have browser scrollbars or fractional CSS pixels. The analogy is for remembering which layers are counted, not for every rendering detail.

The previous lesson, Styles & classes, changed how elements look. Now you will read the layout that those styles produce. You may recognize the rendering rhythm from the Event loop lesson: write styles, let the browser calculate layout, then read measurements in deliberate batches.

offset, client & scroll sizes

INTERACTIVE

The three names tell you which part of the box you are reading:

  • offsetWidth and offsetHeight are the border box: content + padding + border, plus a scrollbar if it takes layout space. They are rounded integers and ignore transforms.
  • clientWidth and clientHeight are the inside of the border: content + padding, minus scrollbar space. clientTop and clientLeft are roughly the top and left border widths.
  • scrollWidth and scrollHeight are the full scrollable content size, including overflow that is currently out of view. scrollTop and scrollLeft are writable positions inside that scrollable content.

Try the playground slowly. Turn on padding first: both offset and client sizes grow. Turn on the border: offset grows again, but client does not. Turn on overflow: scrollHeight grows because the invisible content still exists.

Box measurer: change the frame and read the numbers
Read these propertiesPop out in the code editor (opens in a new tab)JavaScript
const hiddenBelow = element.scrollHeight - element.scrollTop - element.clientHeight;const atBottom = hiddenBelow <= 1;
Bottom check: scrollTop + clientHeight ≈ scrollHeight0 / 0
Live elementat bottom

Content box

offsetWidth
0
offsetHeight
0
clientWidth
0
clientHeight
0
clientTop
0
clientLeft
0
scrollWidth
0
scrollHeight
0
scrollTop
0
Try it yourself

The box is at the bottom within a 1px tolerance. scrollTop + clientHeight has caught up with scrollHeight.

Toggle padding, border, and overflow. The numbers come from the real element in your browser after layout.

A common pattern for infinite lists and “back to top” buttons is checking whether the inner scroll position is near the bottom:

The bottom formulaPop out in the code editor (opens in a new tab)JavaScript
const hiddenBelow = element.scrollHeight - element.scrollTop - element.clientHeight;const atBottom = hiddenBelow <= 1;

The small tolerance matters. CSS pixels can be fractional, and some measurements are rounded. Professional code often asks “within one or two pixels?” instead of demanding perfect equality.

Scroll math without guessing

STEP THROUGH

Now step through the exact arithmetic. The box has a scroll height of 300, the visible client height is 100, and it has already scrolled down 199 pixels. Is one pixel left enough to count as “at the bottom”? Change the tolerance and predict before stepping.

Step through an at-the-bottom check
Step 0 of 4Ready
Your turn: follow the blue line

Predict whether the box is at the bottom. Change the tolerance, then step through the arithmetic.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const hiddenBelow = box.scrollHeight - box.scrollTop - box.clientHeight;const atBottom = hiddenBelow <= 2;console.log(atBottom);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the tolerance on line 3

Try 0 first, then 2. Real pages often need a tiny tolerance because layout can be fractional.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Which measurement is it?
  • offsetWidth
  • offsetHeight
  • clientWidth
  • clientTop
  • scrollWidth
  • scrollHeight
  • rect.top
  • elementFromPoint(x, y)
Try it yourself
0 of 8 correct

Sort each property by what it measures. The names are similar on purpose.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

getBoundingClientRect

LIVE READOUT

element.getBoundingClientRect() returns a DOMRect: top, left, right, bottom, width, and height. The rectangle is the element’s border box, and the coordinates are relative to the viewport. That means rect.top changes when the reader scrolls the page.

Real-life analogyViewport coordinates are a windshield view

If you point at a road sign through the windshield, its position in the glass changes as you drive. The sign’s map location does not. getBoundingClientRect() gives the windshield position; adding scrollX and scrollY gives the map position.

In real life: What you see through the windshield
In JavaScript: the viewport
In real life: A sign’s position in the windshield
In JavaScript: rect.top and rect.left
In real life: Driving forward
In JavaScript: scrolling the page
In real life: A location on the paper map
In JavaScript: rect.top + scrollY document coordinates

Where the analogy stops: The viewport can resize and the document can scroll in two directions. The analogy only separates visible-screen coordinates from whole-page coordinates.

The rectangle can contain fractional values and includes transforms. Toggle the scale below: getBoundingClientRect().width changes visually, but offsetWidth stays the layout width.

Viewport rectangle readout
Viewport to document coordinatesPop out in the code editor (opens in a new tab)JavaScript
const rect = target.getBoundingClientRect();const pageTop = rect.top + window.scrollY;
Measured targetnormal
Target border box
rect.top
0
rect.left
0
rect.width
0
rect.height
0
pageTop
0
pageLeft
0
offsetWidth
0
Try it yourself

Scroll the page and watch top change. Page top stays steadier because it adds scrollY. Width is 0, while offsetWidth is 0.

The readout updates with requestAnimationFrame so scroll events do not force extra rendering work.
Two coordinate systems you must not mix
QuestionViewport answerDocument answer
Where is it on my screen?rect.top, rect.left, clientX, clientYNot the right system
Where is it on the whole page?Add scroll firstrect.top + scrollY, rect.left + scrollX, pageX, pageY
Does it change while scrolling?YesUsually no, unless layout changes
Best forhit testing, sticky UI, visible overlayssaving positions, document overlays, scroll targets
Viewport or document coordinates?
  • event.clientX
  • rect.left
  • document.elementFromPoint(clientX, clientY)
  • event.pageY
  • rect.top + scrollY
  • position an overlay in the document
Try it yourself
0 of 6 correct

Place each value in the coordinate system it belongs to.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Window scrolling & scrollIntoView

INTERACTIVE

Scrolling APIs come in pairs. scrollTo moves to an absolute position. scrollBy moves relative to the current position. Both work on elements and on window: window.scrollTo({ top: 0, behavior: "smooth" }) scrolls the page to the top after a user clicks a button. Avoid yanking the reader’s page automatically.

element.scrollIntoView() is often more convenient: it asks the browser to reveal a particular item. block: "nearest" means “move as little as necessary,” which feels better inside nested scroll containers.

Scroll a contained list
Three scrolling movesPop out in the code editor (opens in a new tab)JavaScript
list.scrollBy({ top: 80, behavior: "smooth" });item.scrollIntoView({ behavior: "smooth", block: "nearest" });
Scrollable listsmooth behavior
Item 1: coordinate practice
Item 2: coordinate practice
Item 3: coordinate practice
Item 4: coordinate practice
Item 5: coordinate practice
Item 6: coordinate practice
Item 7: coordinate practice
Item 8: coordinate practice
Item 9: coordinate practice
Item 10: coordinate practice
Item 11: coordinate practice
Item 12: coordinate practice
Try it yourself

Try the buttons. They only scroll the demo list, not the whole page.

Use the same methods on window for the page, but do it in response to a clear user action.
Viewport width gotcha

window.innerWidth includes the browser scrollbar. document.documentElement.clientWidth excludes it. Use the second value when you want the layout viewport’s usable width.

Scroll events

THROTTLE

Scroll events can fire rapidly. If each event reads layout, writes styles, reads again, and writes again, you can force synchronous layout repeatedly. That pattern is called layout thrashing. Batch reads together, batch writes together, and use requestAnimationFrame for visual updates.

Raw scroll events vs rAF-throttled updates
The scroll-handler patternPop out in the code editor (opens in a new tab)JavaScript
let ticking = false;box.addEventListener("scroll", () => {  if (!ticking) {    requestAnimationFrame(updateReadout);    ticking = true;  }}, { passive: true });
Event counterchecking

Scroll row 1

Scroll row 2

Scroll row 3

Scroll row 4

Scroll row 5

Scroll row 6

Scroll row 7

Scroll row 8

Scroll row 9

Scroll row 10

Scroll row 11

Scroll row 12

Scroll row 13

Scroll row 14

Scroll row 15

Scroll row 16

Scroll row 17

Scroll row 18

raw scroll events
0
rAF updates
0
scrollend events
0
Try it yourself
Scroll the box with your trackpad, mouse wheel, or keyboard.

Raw scroll events can arrive many times in one gesture. The rAF counter batches visual work so it runs at most once per frame.

Use { passive: true } when you do not call preventDefault(). Prefer IntersectionObserver for visibility tracking; the later Observers lesson goes deeper.

Use { passive: true } for scroll listeners when you do not call preventDefault(). It tells the browser the listener will not block scrolling. For “tell me when this card becomes visible,” prefer IntersectionObserver; a later Observers lesson covers it. Modern browsers are also adding scrollend; feature-detect it with "onscrollend" in document before relying on it.

elementFromPoint

HIT TEST

document.elementFromPoint(x, y) asks the browser which element is topmost at a point in the viewport. It uses the same coordinate system as pointer events’ clientX and clientY. If the point is outside the viewport, the result can be null.

Real-life analogyelementFromPoint is a finger on the screen

Point at the screen and ask, “what is directly under my finger?” That is hit testing. It is useful for custom drag-and-drop, inspectors, and teaching tools.

In real life: Your fingertip
In JavaScript: the x, y viewport point
In real life: The top sticker under your finger
In JavaScript: the returned element
In real life: A point off the glass
In JavaScript: null

Where the analogy stops: The browser has to consider stacking, transforms, pointer-events, and shadow DOM. The finger picture is for the basic coordinate idea.

What is under my finger?
Hit test with viewport coordinatesPop out in the code editor (opens in a new tab)JavaScript
area.addEventListener("pointermove", (event) => {  const hit = document.elementFromPoint(event.clientX, event.clientY);});
Pointer areatopmost element
card A
card B overlaps
Try it yourself
Pointer coordinates use clientX and clientY.

Move inside the target area.

elementFromPoint returns the topmost element at viewport coordinates, or null for a point outside the viewport.

Where you’ll use this

Measurement code appears in everyday interfaces. A tooltip reads its target’s rect and positions itself in viewport space. A chat app checks whether the message list is near the bottom before auto-scrolling. A virtualized list uses client height and scrollTop to decide which rows to render. A drawing tool uses elementFromPoint to choose the layer under the pointer.

A practical tooltip sketchJavaScript
const rect = button.getBoundingClientRect();const top = rect.bottom + window.scrollY;const left = rect.left + window.scrollX; tooltip.style.transform = `translate(${left}px, ${top}px)`;

Notice the order: read the rectangle, compute document coordinates, then write one style. If you need several measurements, read them all first; then apply classes or styles. That keeps the browser from recalculating layout over and over.

Misconceptions and edge cases

  • “offsetWidth and getBoundingClientRect().width are the same.” Not always. offsetWidth is an integer layout size and ignores transforms; getBoundingClientRect() can be fractional and includes transforms.
  • “clientWidth is the content width.” It includes padding and excludes borders. If a scrollbar takes space, it is subtracted.
  • “rect.top is a permanent page position.” It is a viewport position. Add scrollY for document coordinates.
  • “Reading layout is free.” Reading after style writes can force synchronous layout. Batch reads and writes.
  • “Hidden elements measure normally.” Elements with display: none measure as 0 and have offsetParent of null.
  • “elementFromPoint uses pageX and pageY.” It uses viewport coordinates. Use clientX and clientY.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict the bottom check

Run the arithmetic in your head. What does the program print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const box = { scrollTop: 240, clientHeight: 60, scrollHeight: 300 };
const hiddenBelow = box.scrollHeight - box.scrollTop - box.clientHeight;
console.log(hiddenBelow <= 1);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeConvert a viewport top to a document top

    A target is 42 pixels from the top of the viewport, and the page has scrolled 300 pixels. What is its document top?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const rect = { top: 42, left: 10 };
    const scrollY = 300;
    console.log(rect.top + scrollY);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the scrollbar width in a layout

      Use the difference between window.innerWidth and document.documentElement.clientWidth.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const windowInnerWidth = 1024;
      const documentClientWidth = 1007;
      console.log(windowInnerWidth - documentClientWidth);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix the coordinate bug

        The popup appears too high after the page scrolls. Fix the coordinate system.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const rect = menuButton.getBoundingClientRect();
        popup.style.top = rect.bottom + "px";
        popup.style.left = rect.left + "px";
          Exercise 5 · ChallengeDesign a scroll listener

          Write a pattern for a sticky header that updates during scrolling without doing heavy work in every raw event.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          let ticking = false;
          window.addEventListener("scroll", () => {
            // your plan here
          });

            Check your understanding

            8 QUESTIONS
            Sizes and coordinates quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Which measurement includes border width?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const box = { scrollTop: 90, clientHeight: 100, scrollHeight: 200 };
              const hiddenBelow = box.scrollHeight - box.scrollTop - box.clientHeight;
              console.log(hiddenBelow);

              Choose an answer to see the explanation.

            3. Question 3 of 8getBoundingClientRect() returns coordinates relative to what?

              Choose an answer to see the explanation.

            4. Question 4 of 8What document top does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const rect = { top: 25 };
              const scrollY = 400;
              console.log(rect.top + scrollY);

              Choose an answer to see the explanation.

            5. Question 5 of 8Which call asks the browser to bring an element into view?

              Choose an answer to see the explanation.

            6. Question 6 of 8What is the safest pattern for expensive scroll updates?

              Choose an answer to see the explanation.

            7. Question 7 of 8elementFromPoint(x, y) expects which coordinate system?

              Choose an answer to see the explanation.

            8. Question 8 of 8Which statement about transforms is true?

              Choose an answer to see the explanation.

            Key takeaways

            • offset* reads the rounded border box and ignores transforms.
            • client* reads the inside of the border; scroll* reads the whole scrollable content.
            • getBoundingClientRect() returns a fractional, transformed border box relative to the viewport.
            • Document coordinates are viewport coordinates plus scrollX and scrollY.
            • Batch layout reads and writes; throttle scroll visuals with requestAnimationFrame.
            • elementFromPoint uses viewport coordinates and returns what is topmost there.

            Sizes tell you the dimensions of boxes; coordinates tell you where those boxes and points are in the viewport or document.

            Up next: Selection & Range.

            CompleteFrontend Clear concepts. Working examples.