Learning Objectives
- Measure an element with
getBoundingClientRect()and say what its numbers are relative to - Distinguish the
offset*,client*, andscroll*property families - Explain why reading layout can force the browser to do work, and why reads and writes should not interleave
- Use
IntersectionObserverfor lazy loading and infinite lists
The rectangle
getBoundingClientRect() answers the question you most often mean: where is this element on my screen right now? Its numbers are relative to the viewport — scroll the page and top changes — they are fractional, and they reflect CSS transforms. It is the rendered truth, after everything the engine has done to the box.
The three families
The older measurement properties come in threes, and the prefixes are the whole lesson — each family measures a different box:
offsetWidth/offsetHeight— the border box: content, padding, and border. The element's full footprint, as an integer.clientWidth/clientHeight— the padding box: content plus padding, minus borders and minus any scrollbar. The space available inside.scrollWidth/scrollHeight— the size the content would need: everything in the element, including the parts scrolled out of view. WhenscrollHeightexceedsclientHeight, there is something to scroll to.scrollLeft/scrollTop— how far it currently is scrolled. These two are writable: assign them to scroll programmatically.
The demo's second section puts all eight on one scrollable container whose child is deliberately too big for it — scroll around and watch which numbers move (only scrollLeft/scrollTop) and which are facts about the boxes (all the rest).
offsetWidth or calling getBoundingClientRect() is one of the things that forces it to settle now. One read is nothing; a loop that alternates write, read, write, read forces a full layout pass per iteration — the classic “layout thrash.” Batch your reads, then batch your writes.The inversion: observers
The traditional way to know whether an element is visible was to listen to scroll — a firehose event, as Events step 7 showed — and measure rectangles in the handler: the expensive read, performed at the highest possible frequency. IntersectionObserver is the platform's answer to that whole category of code. You declare what you care about; the browser, which already knows where everything is, calls you only when the answer changes:
The demo uses that for simulated lazy loading — twelve placeholder cards that “load” as they approach the viewport — and a second observer for an infinite list: a sentinel element sits at the bottom of a scroll container, and whenever the sentinel comes into view, more items are appended and the sentinel moves back to the end. No scroll handler anywhere in the file.
The frame below is deliberately shorter than the demo — the point is the scrolling. Scroll inside it and watch the counter as cards load.
observe() targets — repeats across the platform: ResizeObserver for size changes, MutationObserver for tree changes (the demo itself uses one to start observing newly appended list items). Learn the pattern once and you have all three.Applied: the ellipsis that isn't DOM at all
A closing calibration. To truncate overflowing text with an ellipsis, you could measure widths and slice strings — or you could let CSS text-overflow: ellipsis do it, and use the DOM only for what CSS cannot: the width control that drives the demo, and a title attribute holding the full text. Drag the number and the platform does the truncation.
That division of labor — declare what you can, script only the remainder — is the recurring theme of this series, and the right instinct to carry into the finale.
Next Steps
In Step 9: Beyond the raw DOM, the honest closing argument:
- What jQuery solved, and why its wins moved into the platform
- Why the raw DOM is the performance floor a library cannot beat
- What frameworks actually buy — and what they cost