What does scroll depth actually measure?
Scroll depth records the maximum vertical position a user reaches on a page, expressed as a percentage of total scrollable height. The total height is document.documentElement.scrollHeight minus the viewport height, and the user's progress is window.scrollY divided by that total. Most analytics tools then snap each session to the deepest threshold the user crossed.
The conventional buckets are:
- 25% — past the hero, still browsing
- 50% — actively scanning the body
- 75% — engaged reader, likely past the fold of long-form content
- 100% — reached the footer (or close enough)
Some teams add 10% and 90% to detect the "tasted it and left" and "almost finished" cohorts.
IntersectionObserver vs scroll events: which approach is better?
There are two ways to capture depth, and the choice affects performance.
| Approach | How it works | Cost |
|---|---|---|
| Scroll event listener | Listen to scroll, throttle to ~250ms, calculate percentage on each tick | Fires hundreds of times per session, can jank low-end Android |
| IntersectionObserver sentinels | Place invisible divs at 25/50/75/100% of the page, observe when they enter the viewport | Browser-native, off the main thread, fires once per threshold |
IntersectionObserver is the modern default. It avoids layout reads on every scroll tick and survives long pages on cheap devices much better. The one tradeoff: if the page height changes after load (lazy images, infinite scroll), the sentinels need to be repositioned.
Why does rage scroll matter?
Rage scroll is the velocity-based cousin of the rage click. A user who flicks down the page at extreme speed without pausing on any element is signaling frustration or skimming for one specific thing. Capturing scroll velocity, not just depth, surfaces:
- Visitors who can't find pricing
- Mobile readers who hit a wall of text
- Users who blew past a CTA without seeing it
Depth alone tells you they reached the bottom. Velocity tells you whether they actually read anything along the way.
How does GA4 enhanced measurement handle scroll?
GA4's enhanced measurement ships with a single scroll event: it fires once per session when the user reaches 90% of the page. There are no 25/50/75 buckets out of the box, and there is no per-page configuration in the standard UI. Teams that need granular thresholds typically:
- Disable the built-in scroll event
- Push custom
scroll_depthevents from Google Tag Manager with a Scroll Depth Trigger - Send 25/50/75/100 as separate events with the percentage in an event parameter
What are the mobile vs desktop quirks?
Scroll depth is noisier on mobile than desktop for a few reasons:
- URL bar collapse: iOS Safari's address bar shrinks on scroll, which changes viewport height mid-session and can briefly push percentages above 100%.
- Bounce/overscroll: pull-to-refresh and rubber-banding on iOS report negative
scrollYvalues that need clamping. - Touch vs wheel: a wheel scroll on desktop produces fine-grained events, while a touch flick on mobile is one momentum gesture that can skip past several thresholds in 200ms.
- Lazy-loaded content: images and embeds below the fold inflate the page after the user starts scrolling, which can make a 75% reading look like a 60% reading.
The fix is to recompute total scroll height on resize, load, and after large DOM mutations, and to clamp percentages between 0 and 100 before reporting.
Is depth the same as attention?
No. Depth measures position; attention measures time on visible content. A user can scroll to 100% in two seconds without reading anything, and another user can stay at 40% for four minutes deeply engaged with a single section. The strongest engagement signals combine depth, dwell time per viewport, and scroll velocity. Heatmaps overlay all three so you can see where attention pooled, not just how far it traveled.
How it relates to CloseTrace
CloseTrace records scroll depth and scroll velocity automatically on every captured session, with IntersectionObserver-based sampling that does not block the main thread. The depth percentages flow into the same timeline as clicks, form fields, and rage events, so you can replay the exact moment a long-form landing page lost a high-intent visitor.