Click Heatmaps and Scroll Depth: Reading What Users Actually Do
Why heatmaps complement numbers
Analytics tells you a page has a 71% bounce rate. It does not tell you that visitors are repeatedly clicking a bolded phrase that is not a link, or that 80% never scroll past the hero. Heatmaps answer the why behind the metric.
Three kinds, each answering a different question:
- Click maps — where people click, including where they click things that do nothing.
- Scroll maps — how far down the page people actually get.
- Move maps — where the cursor travels. Loosely correlated with attention on desktop, meaningless on touch devices. Treat it as the weakest of the three.
Capturing clicks that survive a redesign
Storing raw (x, y) pixel coordinates is the naive approach and it breaks immediately: a 1920px desktop and a 390px phone produce incomparable coordinates, and any layout change invalidates all historical data.
Two better strategies, used together:
Relative coordinates. Store x as a fraction of viewport width and y as a fraction of document height, plus the viewport width bucket. Now data from different screen sizes can be rendered onto a single normalized canvas.
document.addEventListener('click', (e) => {
track('click', {
x_pct: +(e.clientX / window.innerWidth).toFixed(4),
y_pct: +((e.pageY) / document.documentElement.scrollHeight).toFixed(4),
viewport_bucket: bucketWidth(window.innerWidth), // 'mobile' | 'tablet' | 'desktop'
selector: cssPath(e.target),
text: e.target.textContent?.trim().slice(0, 60),
});
}, { capture: true, passive: true });Element selectors. A stable CSS path to the clicked element. This is what survives redesigns — "the primary CTA got 3,400 clicks" remains true even when it moves 200px down the page.
Bucket by viewport and always render heatmaps per device class. A combined desktop-and-mobile heatmap is an average of two different layouts and shows patterns that exist on neither.
Scroll depth
Track milestone thresholds rather than continuous position — it is a fraction of the events and answers the same questions:
const marks = [25, 50, 75, 90, 100];
const fired = new Set();
window.addEventListener('scroll', throttle(() => {
const pct = (window.scrollY + window.innerHeight) / document.body.scrollHeight * 100;
for (const m of marks) {
if (pct >= m && !fired.has(m)) { fired.add(m); track('scroll_depth', { pct: m }); }
}
}, 250), { passive: true });Two details that matter: throttle the handler — an unthrottled scroll listener fires dozens of times per second and will show up in your INP numbers — and pass { passive: true } so the listener cannot block scrolling.
A typical healthy article shows 100% at the top, 60–75% at the 50% mark, and 25–40% reaching 90%. A sharp cliff at a specific point is the interesting signal: something at that position is stopping people.
Rage clicks and dead clicks
These two derived signals are usually the highest-value output of click tracking.
Rage click — three or more clicks within roughly 1 second inside a small radius. It means something looked interactive and did not respond. Common causes: a slow AJAX action with no loading state, a disabled button with no explanation, a broken handler.
Dead click — a click on an element with no handler, no link, and no state change. Usually means something looks clickable but is not: a bolded phrase, a styled card, an image that users expect to enlarge.
SELECT path, selector, text, count(*) AS rage_clicks FROM events WHERE event_name = 'rage_click' AND timestamp >= current_date - 7 GROUP BY 1, 2, 3 ORDER BY rage_clicks DESC LIMIT 20;
Both are unusually actionable because they point at a specific element, not a vague page-level metric. A dead-click cluster on a non-linked card is a five-minute fix that removes a real frustration.
Reading heatmaps without fooling yourself
Sample size. Under a few hundred sessions per page per device class, you are looking at noise arranged decoratively. Wait for volume.
Position bias. Things at the top get more clicks because they are at the top. A heavily-clicked hero button is not proof of good copy — compare against what the same element does after being moved.
Dynamic content. If a page renders different content per visitor, a single averaged heatmap overlays incompatible layouts. Segment, or exclude those pages.
Correlation, not causation. A scroll cliff at 40% might be a boring paragraph, an intrusive embed, a slow-loading image causing a layout shift, or a natural stopping point where the question was answered. The heatmap identifies the location; you still have to diagnose it.
Acting on what you find
- Scroll cliff before your CTA → move the CTA above the cliff, or shorten what precedes it.
- Dead clicks on a non-link → make it a link, or restyle it so it does not read as one.
- Rage clicks on a real button → add a loading state; check the network call's latency and error rate.
- Nav items with near-zero clicks → candidates for removal; every extra option costs attention.
- Heavy clicking below the fold on mobile → your mobile layout is burying something people want.
Frequently asked questions
How do heatmaps survive a site redesign?
By recording a stable element selector alongside the coordinates. Pixel positions become meaningless the moment layout changes, while element-anchored clicks stay comparable and work across viewport sizes.
What is a rage click and what does it indicate?
Several rapid clicks on the same element, which almost always means the visitor expected something to happen and nothing did — a dead control, a slow handler, or something styled to look clickable that is not.
What is a dead click?
A click on an element with no handler at all. Usually it is text styled like a link, an image users expect to enlarge, or a disabled control that gives no feedback about why it is disabled.
How much traffic do I need for a useful heatmap?
A few hundred sessions per page before patterns separate from noise. Below that you are reading individual behaviour, which is interesting but not evidence.
Are heatmaps a privacy risk?
They can be, if you record full session replay with form contents. Aggregate click and scroll data tied to a pseudonymous id carries far less risk — capture positions and selectors, never keystrokes or input values.
Privacy considerations
Heatmap capture is where analytics most easily drifts into collecting personal data. Keep it bounded:
- Record selectors and coordinates, never form field values.
- Exclude
input,textarea, andselectcontents entirely — capture that a field was clicked, never what was typed. - Truncate captured element text and skip anything inside a container marked sensitive.
- Do not build full session replay unless you are prepared to handle the consent and data-protection obligations that come with recording someone's screen.
InsightsTrack's heatmaps store relative coordinates plus element selectors, bucketed by device class, with rage-click and dead-click detection — and no form contents, ever. Pair this with funnel analysis to find why a step leaks, and bounce rate for the page-level signal.
Try InsightsTrack free
Open-source, self-hosted, privacy-first analytics — explore the live demo, no install required.
Open live dashboard