From 0fac9a127c25a61113680b4f37043aef7fe87740 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 30 Apr 2024 16:00:28 +0200 Subject: [PATCH] feat(telemetry): measure 25%/50%/75% scroll depth GA4 already measures 90%, and we instrument measurements for 25%/50%/75% to better understand how far users scroll. --- client/src/app.tsx | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/client/src/app.tsx b/client/src/app.tsx index 8ed0e47eba43..99a43ee4f148 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -35,6 +35,7 @@ import { TopPlacement } from "./ui/organisms/placement"; import { Blog } from "./blog"; import { Newsletter } from "./newsletter"; import { Curriculum } from "./curriculum"; +import { useGA } from "./ga-context"; const AllFlaws = React.lazy(() => import("./flaws")); const Translations = React.lazy(() => import("./translations")); @@ -135,6 +136,7 @@ export function App(appProps: HydrationData) { usePing(); useGleanPage(pageNotFound, appProps.doc); + useScrollDepthMeasurement(); const localeMatch = useMatch("/:locale/*"); @@ -349,3 +351,46 @@ export function App(appProps: HydrationData) { ); return routes; } + +function useScrollDepthMeasurement(thresholds = [25, 50, 75]) { + const timeoutID = React.useRef(); + const [currentDepth, setScrollDepth] = React.useState(0); + const { gtag } = useGA(); + + useEffect(() => { + const listener = () => { + if (timeoutID.current) { + window.clearTimeout(timeoutID.current); + } + timeoutID.current = window.setTimeout(() => { + const { scrollHeight } = document.documentElement; + const { innerHeight, scrollY } = window; + const scrollPosition = innerHeight + scrollY; + const depth = (100 * scrollPosition) / scrollHeight; + + const matchingThresholds = thresholds.filter( + (threshold) => currentDepth < threshold && threshold <= depth + ); + + matchingThresholds.forEach((threshold) => { + gtag("event", "scroll", { + percent_scrolled: String(threshold), + }); + }); + + const lastThreshold = matchingThresholds.at(-1); + if (lastThreshold) { + setScrollDepth(lastThreshold); + } + + timeoutID.current = null; + }, 100); + }; + + window.addEventListener("scroll", listener); + + return () => window.removeEventListener("scroll", listener); + }); + + return currentDepth; +}