Skip to content

Commit 52088dd

Browse files
jaysooclaude
authored andcommitted
docs(misc): add 1% scroll depth tracking to docs and non-docs pages (#34246)
This PR adds scroll depth event at the 10% level so we can filter out users who actually engages with the page versus those who maybe land on homepage just to get to docs. <img width="2672" height="1527" alt="Screenshot 2026-01-28 at 4 00 54 PM" src="https://github.com/user-attachments/assets/ad9be5ae-442c-48eb-9c1e-70f0b872563b" /> Also fix the scroll tracker for astro-docs. <img width="2672" height="1527" alt="Screenshot 2026-01-28 at 4 00 54 PM" src="https://github.com/user-attachments/assets/e4a24ef2-b6aa-4f5a-b10b-972b73385fee" /> <img width="2672" height="1527" alt="Screenshot 2026-01-28 at 2 39 09 PM" src="https://github.com/user-attachments/assets/833c71f1-7a96-4637-bb6e-3b21227053f0" /> ## Related Issue(s) Closes CLOUD-4211 Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> (cherry picked from commit b53fd7c)
1 parent cf42933 commit 52088dd

2 files changed

Lines changed: 94 additions & 17 deletions

File tree

astro-docs/public/global-scripts.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,78 @@
144144
window.twq('config', 'obtp4');
145145
};
146146

147+
// Scroll depth tracking
148+
const SCROLL_THRESHOLDS = [10, 25, 50, 75, 90];
149+
let firedThresholds = new Set();
150+
let scrollTrackingEnabled = false;
151+
let scrollRafId = null;
152+
153+
function getScrollPercentage() {
154+
const scrollTop = window.scrollY || document.documentElement.scrollTop;
155+
const scrollHeight = document.documentElement.scrollHeight;
156+
const clientHeight = window.innerHeight;
157+
return (scrollTop + clientHeight) / scrollHeight;
158+
}
159+
160+
function handleScrollTracking() {
161+
if (!scrollTrackingEnabled) return;
162+
163+
const scrollPercentage = getScrollPercentage() * 100;
164+
165+
// Fire events for all thresholds we've passed but haven't fired yet
166+
for (const threshold of SCROLL_THRESHOLDS) {
167+
if (scrollPercentage >= threshold && !firedThresholds.has(threshold)) {
168+
firedThresholds.add(threshold);
169+
sendSearchEvent(`scroll_${threshold}`, {
170+
event_category: 'scroll',
171+
event_label: window.location.pathname,
172+
});
173+
}
174+
}
175+
}
176+
177+
function throttledScrollHandler() {
178+
if (scrollRafId !== null) return;
179+
180+
scrollRafId = requestAnimationFrame(() => {
181+
handleScrollTracking();
182+
scrollRafId = null;
183+
});
184+
}
185+
186+
function attachScrollListener() {
187+
window.addEventListener('scroll', throttledScrollHandler, {
188+
passive: true,
189+
});
190+
}
191+
192+
function setupScrollTracking() {
193+
// Reset scroll depth on navigation (for SPA-like behavior via View Transitions)
194+
firedThresholds = new Set();
195+
scrollTrackingEnabled = false;
196+
197+
// Delay tracking start to avoid false triggers during navigation
198+
setTimeout(() => {
199+
scrollTrackingEnabled = true;
200+
// Immediately check current scroll position to capture thresholds
201+
// that may have been passed during the delay
202+
handleScrollTracking();
203+
}, 500);
204+
205+
attachScrollListener();
206+
207+
// Handle Astro View Transitions - reset on navigation
208+
document.addEventListener('astro:after-swap', () => {
209+
firedThresholds = new Set();
210+
scrollTrackingEnabled = false;
211+
setTimeout(() => {
212+
scrollTrackingEnabled = true;
213+
// Immediately check current scroll position after navigation
214+
handleScrollTracking();
215+
}, 500);
216+
});
217+
}
218+
147219
const SEARCH_DEBOUNCE_MS = 1000;
148220
let searchDebounceTimer;
149221
let lastSearchQuery = '';
@@ -209,12 +281,14 @@
209281
loadGTM();
210282
loadHubSpot();
211283
setupSearchTracking();
284+
setupScrollTracking();
212285
} else if (window.Cookiebot && window.Cookiebot.consent) {
213-
// Statistics cookies (Google Analytics, GTM, Search Tracking)
286+
// Statistics cookies (Google Analytics, GTM, Search, Scroll Tracking)
214287
if (window.Cookiebot.consent.statistics) {
215288
loadGoogleAnalytics();
216289
loadGTM();
217290
setupSearchTracking();
291+
setupScrollTracking();
218292
}
219293

220294
// Marketing cookies (HubSpot, Apollo, Hotjar, Twitter)

nx-dev/feature-analytics/src/lib/use-window-scroll-depth.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,11 @@ import { useEffect, useRef, useCallback } from 'react';
44
import { usePathname } from 'next/navigation';
55
import { sendCustomEvent } from './google-analytics';
66

7-
function getScrollDepth(pct: number): 0 | 25 | 50 | 75 | 90 {
8-
if (pct >= 0.9) return 90;
9-
if (pct < 0.25) return 0;
10-
if (pct < 0.5) return 25;
11-
if (pct < 0.75) return 50;
12-
return 75;
13-
}
7+
const SCROLL_THRESHOLDS = [10, 25, 50, 75, 90] as const;
148

159
export function useWindowScrollDepth(): void {
1610
const pathname = usePathname();
17-
const scrollDepth = useRef(0);
11+
const firedThresholds = useRef<Set<number>>(new Set());
1812
const shouldTrackScroll = useRef(true);
1913
const rafId = useRef<number | null>(null);
2014

@@ -23,13 +17,19 @@ export function useWindowScrollDepth(): void {
2317
if (typeof window === 'undefined') return;
2418

2519
const scrollPercentage =
26-
(window.scrollY + window.innerHeight) /
27-
document.documentElement.scrollHeight;
28-
const depth = getScrollDepth(scrollPercentage);
20+
((window.scrollY + window.innerHeight) /
21+
document.documentElement.scrollHeight) *
22+
100;
2923

30-
if (depth > scrollDepth.current) {
31-
scrollDepth.current = depth;
32-
sendCustomEvent(`scroll_${depth}`, 'scroll', pathname || '/');
24+
// Fire events for all thresholds we've passed but haven't fired yet
25+
for (const threshold of SCROLL_THRESHOLDS) {
26+
if (
27+
scrollPercentage >= threshold &&
28+
!firedThresholds.current.has(threshold)
29+
) {
30+
firedThresholds.current.add(threshold);
31+
sendCustomEvent(`scroll_${threshold}`, 'scroll', pathname || '/');
32+
}
3333
}
3434
}, [pathname]);
3535

@@ -46,12 +46,15 @@ export function useWindowScrollDepth(): void {
4646
shouldTrackScroll.current = false;
4747

4848
const timeout = setTimeout(() => {
49-
scrollDepth.current = 0;
49+
firedThresholds.current = new Set();
5050
shouldTrackScroll.current = true;
51+
// Immediately check current scroll position to capture thresholds
52+
// that may have been passed during the delay
53+
handleScroll();
5154
}, 500);
5255

5356
return () => clearTimeout(timeout);
54-
}, [pathname]);
57+
}, [pathname, handleScroll]);
5558

5659
useEffect(() => {
5760
if (typeof window === 'undefined') return;

0 commit comments

Comments
 (0)