Scroll Depth Tracking Without Google Analytics: What It Measures, and Where It Lies

A scroll-depth funnel tells you where readers stop. It does not tell you whether they read. Here is how the number is computed, what to conclude from it, and how to get it without GA4, cookies or a consent banner.

By Null Agency · Updated October 16, 2026 · Written for content teams and developers who want to know where readers drop off

TL;DR: four thresholds, one honest caveat

Scroll depth is the furthest point a visitor reached on a page, as a percentage of the scrollable distance. A funnel of 25, 50, 75 and 100 percent turns that into one row per threshold: the share of pageviews that got at least that far.

Want to see the panel on real traffic first? Open the live demo, or start on the free plan.

What scroll depth actually measures

Strip away the dashboards and scroll depth is one division. Take how far the page has been scrolled, divide by how far it can be scrolled, and keep the largest value seen during the visit. In GhostMetrics the calculation is scrollY / (documentHeight - viewportHeight), rounded to a whole percent and capped at 100.

Three details in that formula decide how you should read the result.

It tracks the top edge of the viewport, not the bottom. A visitor on a tall phone who has scrolled to 50 percent has the top of their screen at the halfway point of the scrollable range. The bottom of their screen is further down, so they have seen more than half the document. A reader at 100 percent has reached the very bottom of the page, nothing more.

The denominator is the whole document. Footer links, a comment thread, a newsletter box and a related-posts grid all count as page height. If the last 25 percent of your page is chrome, then the end of the article sits near 75 percent and a reader who finished it never reaches 100.

It is a maximum, not a path. A visitor who scrolls to the bottom and jumps back to the top still records 100. A visitor who jumps straight to an anchor link halfway down records 50 without ever having seen the first half. Scroll depth measures reach, not reading order and not attention.

Pages that fit inside the viewport have nothing to scroll. The tracker only measures inside its scroll listener, so a page that never fires a scroll event usually records 0 percent even though the visitor saw everything. Short pages can therefore drag the site-wide funnel down; compare like with like.

How GhostMetrics records it, with no cookies

The mechanics are small enough to describe completely, which matters if you are the one signing off on a privacy review. If you want the wider background on identifier-free measurement, our cookieless tracking explainer covers it.

  1. A passive scroll listener samples the position. It runs at most once every 250 ms and only updates a single number in memory when the new percentage beats the old one.
  2. The maximum lives in page memory. It is not written to a cookie, to localStorage, or to sessionStorage. The only things GhostMetrics puts in sessionStorage are a per-tab pageview counter and any first-touch UTM, both cleared when the tab closes.
  3. A leave beacon ships the number. When the tab becomes hidden or the page unloads, the tracker sends one beacon carrying scroll depth, time on page and a bounce flag.
  4. Single-page apps reset it per route. A pushState or popstate navigation counts as a new pageview, restarts the timer and starts the scroll maximum from zero. The previous route's depth and time are dropped at that moment, not sent, so in a single-page app only the route that is open when the tab is hidden reports scroll depth.
  5. Browsers that send Global Privacy Control or Do Not Track are not measured at all. No beacon goes out, so those readers are absent from the funnel entirely.

On the dashboard side, the Scroll Depth Funnel panel counts, across all pageviews in the selected period, the percentage that reached at least 25, 50, 75 and 100 percent. The All Pages table adds an Avg Scroll column per URL. The funnel is site-wide for the period; if you want the same four rows for a single page, export the raw data (CSV from the Export data button, or JSON from the API; every plan) and group by page path.

What the funnel tells a content team

A funnel is a shape, and shapes suggest hypotheses. None of the readings below is proof; each tells you what to open next. The best habit is to look at the drop between adjacent thresholds, not at any single bar.

Shape of the funnelLikely meaningWhat to check next
Steep cliff between the start and 25%Visitors leave above the fold: wrong intent, slow render, or an interruptionThe headline against the search or referral source; real-user load timings
Healthy 25% and 50%, then a drop before 75%The middle section loses people: a dense block, a table, a long code sampleWhat sits at the 50 to 75 percent mark; split it or lead with the answer
Flat through 75%, collapse at 100%Readers finish and stop; the footer is the denominator, not a failureNothing. Treat 75% as the finishing line for this template
Almost everyone at 100%Short pages where one flick of the thumb reaches the bottomPage length; compare against pages of similar size only
High Avg Scroll, very short time on pageSkimming or jumping, not readingAvg Time for the same URL in the All Pages table
Low Avg Scroll, long time on pageA tab left open, or a page that answers the question at the topWhether the answer is near the top; whether the exit is a good one

The last two rows are the reason scroll depth should never be read alone. Pair it with time on page (the dashboard has a distribution for it) and with entry and exit pages. A page with 40 percent average scroll and a two-minute average time on page is probably a reference page doing its job. The same scroll number with a six-second time is a page people abandon.

For editors, the highest-value use is comparative. Run the same template across ten articles and sort by Avg Scroll. The bottom three are candidates for a rewrite; the top three show you the structure that holds readers. Both are statements about your own pages relative to each other, which is the one comparison scroll depth supports well. For the broader picture of which numbers to trust, see how to measure website traffic.

Where the number misleads

Every scroll tracker has the same blind spots. Knowing them keeps you from rewriting a page that was fine.

Missing leave beacons count as zero. The depth is only reported when the tab is hidden or unloaded. If a browser kills a page before the beacon goes out, that pageview keeps its default of zero and drags the funnel down. Expect the 25 percent bar to understate reach somewhat, especially on mobile where tabs are backgrounded and discarded aggressively. The server also only attaches a leave beacon to a pageview recorded in the previous ten minutes, so a reader who keeps the tab in front for longer than that is recorded as zero too.

The beacon fires once per pageview. A reader who switches tabs after reading half the article, comes back and finishes, will have reported 50 percent when they first left. Anything scrolled after that point is not sent. Time on page has the same property.

Infinite scroll and lazy-loaded content move the goalposts. When the page grows as you scroll, the denominator grows with it, so the percentage can stall or fall while the reader keeps going. If a page loads more content on scroll, treat its percentages as unreliable and use section events instead.

Anchor links, sticky tables of contents and "jump to" buttons inflate depth. A visitor who taps "Pricing" at the bottom of your contents list records a high maximum having read nothing in between.

It tells you nothing about the person. There is no visitor ID, so you cannot ask "do returning readers scroll further?" and no per-visitor funnel across sessions exists. That is a design decision: no identifier means nothing to follow a person with.

How the other tools handle scroll depth

The question most teams actually have is what they would give up by moving off Google Analytics. For scroll depth the honest answer is: not much, unless you rely on custom thresholds.

ToolScroll depthCookies / consentNotes
GA4Built-in scroll event from enhanced measurement, fired once at a single threshold; finer steps through Tag Manager or custom codeCookies (_ga) by default; consent generally required in the EU/UKCheck current GA4 docs for the threshold and whether it can be changed. Strongest for tying engagement to Google Ads audiences
GhostMetricsMaximum depth per pageview, funnel at 25/50/75/100, Avg Scroll per pageNo cookies, no localStorage; honors GPC and DNTFunnel is site-wide per period; per-page funnel from the raw export. No custom thresholds
Custom event via IntersectionObserverWhatever sections you mark upDepends on the tool that receives the eventBest for "did they reach the pricing table", costs a few lines of JavaScript
Cloudflare Web AnalyticsNot reportedCookielessVisits, pageviews, referrers, countries, devices and Core Web Vitals; no custom events
Netlify AnalyticsNot possibleNo cookies, no scriptBuilt from server logs, so it has no client-side data of any kind

GA4 is the one place where scroll tracking arrives bundled with a wider identity model. If your marketing team feeds engaged audiences back into Google Ads, that is a real reason to keep it, and nothing here replaces it. If the scroll event was the only reason you were reading GA4 reports, you can leave it behind; our GA4 alternative guide compares the broader options.

The bigger practical difference is consent. A cookie-based setup behind a banner gets its scroll data mainly from the visitors who accepted it. A cookieless funnel counts everyone who has not sent a privacy signal, so the sample is not limited to people who clicked "accept".

Add section-level milestones with a custom event

Percentages answer "how far". Editors usually want "which part". The tracker's custom-event API handles this, because an event carries the page path and one metadata field, section. The idea is to mark the sections you care about, observe them, and fire one event the first time each enters the viewport. Our guide to custom events without Google Analytics covers the API in more depth.

  1. Install the tracker if it is not on the page yet: one <script defer> tag with your site ID in the <head>. No plugin, no build step.
  2. Mark the sections you want to measure with a data-section attribute: the heading that opens the pricing table, the code sample, the conclusion.
  3. Paste the observer below just before </body>. It fires seen-<section> once per section per pageview.
  4. Load a page, scroll through it, and open the Custom Events panel. You should see one row per section, with a count and a last-fired time.
  5. Compare the counts against pageviews for that page. The ratio is your per-section reach.
<h2 data-section="pricing">Pricing</h2>
<h2 data-section="conclusion">Conclusion</h2>

<script>
document.addEventListener('DOMContentLoaded', function () {
  // gm.js is deferred, so it has run by now. It is absent if the
  // visitor sends Global Privacy Control or Do Not Track.
  if (!window.ghostmetrics || !('IntersectionObserver' in window)) return;
  var io = new IntersectionObserver(function (entries) {
    entries.forEach(function (entry) {
      if (!entry.isIntersecting) return;
      var id = entry.target.getAttribute('data-section');
      window.ghostmetrics.track('seen-' + id, { section: id });
      io.unobserve(entry.target);
    });
  }, { threshold: 0.5 });
  document.querySelectorAll('[data-section]').forEach(function (el) {
    io.observe(el);
  });
});
</script>

Putting the section into the event name is deliberate. The Custom Events panel lists name, count and last fired, with no drill-down by metadata, so seen-pricing and seen-conclusion show up as separate rows you can read at a glance. The section field still travels with each event and lands in the raw export.

Two limits to keep in mind. The panel is site-wide, so two pages that both mark a pricing section share one row; use distinct names per page type or split the export by page path. And there is no numeric value field and no per-visitor funnel, so you can count how many pageviews reached a section but not follow one reader from section to section.

A weekly routine that fits in ten minutes

Scroll data earns its keep when it changes what you write, not when it sits in a dashboard. A routine that works for most small teams:

  1. Open All Pages for a recent period and sort by views. Ignore anything with a trivially small count; a page with a handful of views produces a noisy average.
  2. Read Avg Scroll next to Avg Time for the top ten pages. Flag only the pages where the two disagree, a high scroll with a tiny time or the reverse.
  3. Check the site-wide funnel for your 75 percent row. Write that number down. When it moves by a few points after a template change, you have measured the change.
  4. Pick one page and look at what sits at the largest drop. Edit that section, then leave the page alone for a couple of weeks so the next comparison is fair.

The discipline is changing one thing at a time. Scroll depth is an average over mixed audiences, and a rewrite that coincides with a new traffic source will move the number for reasons that have nothing to do with the rewrite. The referrers panel and the UTM breakdown will tell you if the audience changed under you.

All of the numbers above come from raw rows you can export, on the free plan as on Pro, so a skeptical colleague can recompute any figure from the data rather than taking the dashboard's word for it.

Scroll depth on every plan, with no cookies and no banner

Scroll Depth FunnelNo cookies1 site free$9.99/mo flat Pro

GhostMetrics records scroll depth, time on page and bounce on every pageview, and shows them as a funnel, a per-page Avg Scroll column and a time-on-page distribution. Install one script tag and you are live; the free plan is free forever: one website, unlimited pageviews, real-time dashboard, every view, full history.

Need more than one site or a public shareable dashboard? Pro is $9.99/mo flat, unlimited pageviews, unlimited websites (fair-use ceiling of 250), with a 30-day free trial: a card starts the trial, nothing is charged for 30 days, and you can cancel anytime. Raw event export as CSV or JSON is on every plan, including free.

Start free, no card See the live demo

FAQ

What is scroll depth in web analytics?
Scroll depth is the furthest point a visitor scrolled down a page, expressed as a percentage of the scrollable distance. GhostMetrics records the maximum for each pageview and reports the share of pageviews that reached 25, 50, 75 and 100 percent.
Does GhostMetrics track scroll depth without cookies?
Yes. The maximum scroll position is held in the page's memory and sent once in a leave beacon when the tab is hidden or closed. No cookies and no localStorage are used, and the only things kept in sessionStorage are a per-tab pageview counter and any first-touch UTM.
Why does my 100% scroll number look so low?
100 percent means the visitor reached the very bottom of the document, footer included. Long footers, comment blocks and related-post grids push that mark far below the end of the article, so 75 percent is usually a better proxy for finishing the piece. Pageviews that never send a leave beacon are also counted as zero.
Can I track when readers reach a specific heading or section?
Yes, with a custom event. Fire window.ghostmetrics.track from an IntersectionObserver when a marked section enters the viewport, and pass the section name in the section field. GhostMetrics stores the event name, the page path and that one metadata field.
How does this compare with GA4 scroll tracking?
GA4's enhanced measurement includes a scroll event that fires once at a single depth threshold, and finer thresholds usually need Google Tag Manager or custom code. GA4 also sets cookies by default, so it generally needs consent in the EU and UK. Check the current GA4 docs for the exact threshold and settings.

Keep reading

Custom Events Without GA

data-gm-event and ghostmetrics.track in practice

Bounce vs Engagement Rate

Pair scroll data with how bounces are counted

Track Outbound Link Clicks

Pair scroll data with where readers click away

GhostMetrics Live Demo

The real dashboard, live data

Disclosure: GhostMetrics is built by Null Agency and we use it on this site, so we're not a neutral party about our own product. The product claims here describe what the tracker and dashboard do today; for GA4 and other third-party tools, check the vendor's current documentation before deciding.