Skip to content

Commit 88584a1

Browse files
domlanderabeddow91
andcommitted
Implement intersection observer
Co-authored-by: Anna Beddow <[email protected]>
1 parent a3399cd commit 88584a1

File tree

3 files changed

+98
-69
lines changed

3 files changed

+98
-69
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { ComponentEvent } from '@guardian/ophan-tracker-js';
2+
import { useEffect } from 'react';
3+
import { submitComponentEvent } from '../client/ophan/ophan';
4+
5+
const getCollectionElements = (): HTMLElement[] => {
6+
return Array.from(
7+
document.querySelectorAll('[data-collection-tracking="true"]'),
8+
);
9+
};
10+
11+
const reportInsertEvent = (elements: HTMLElement[]) => {
12+
for (const [index, element] of elements.entries()) {
13+
const sectionName = element.id;
14+
if (sectionName === '') return;
15+
16+
const ophanComponentEvent: ComponentEvent = {
17+
component: {
18+
componentType: 'CONTAINER',
19+
id: element.id,
20+
/**
21+
* Labels:
22+
* - The name of the collection
23+
* - The nummber of the collection in the list (the top collection is 1)
24+
* - The distance from the top of the collection to the top of the viewport
25+
*/
26+
labels: [
27+
sectionName,
28+
(index + 1).toString(),
29+
element.getBoundingClientRect().top.toFixed(0),
30+
],
31+
},
32+
action: 'INSERT',
33+
};
34+
35+
void submitComponentEvent(ophanComponentEvent, 'Web');
36+
}
37+
};
38+
39+
const reportViewEvent = (sectionName: string) => {
40+
const ophanComponentEvent: ComponentEvent = {
41+
component: {
42+
componentType: 'CONTAINER',
43+
labels: [sectionName],
44+
},
45+
action: 'VIEW',
46+
};
47+
48+
void submitComponentEvent(ophanComponentEvent, 'Web');
49+
};
50+
51+
const viewedCollections = new Set<string>();
52+
53+
const setCollectionAsViewed = (id: string) => {
54+
viewedCollections.add(id);
55+
};
56+
57+
/**
58+
* Report fronts section data to Ophan.
59+
*
60+
* Sends an event with action: 'INSERT' for each collection that is rendered on the page, even if out of the viewport.
61+
* Sends an event with action: 'VIEW' when a collection is viewed by the user, i.e. within the viewport.
62+
*
63+
* Assumptions:
64+
* - The id of the section/collection is also the human-readable name of the collection
65+
*/
66+
export const FrontSectionTracker = () => {
67+
useEffect(() => {
68+
const collectionElements: HTMLElement[] = getCollectionElements();
69+
70+
void reportInsertEvent(collectionElements);
71+
72+
const callback = (entries: IntersectionObserverEntry[]) => {
73+
for (const entry of entries) {
74+
if (entry.isIntersecting) {
75+
const sectionName = entry.target.id;
76+
if (!viewedCollections.has(sectionName)) {
77+
setCollectionAsViewed(sectionName);
78+
reportViewEvent(sectionName);
79+
}
80+
}
81+
}
82+
};
83+
84+
const observer = new IntersectionObserver(callback);
85+
86+
for (const collection of collectionElements) {
87+
observer.observe(collection);
88+
}
89+
90+
return () => {
91+
observer.disconnect();
92+
};
93+
}, []);
94+
95+
return null;
96+
};

dotcom-rendering/src/components/SectionTracker.importable.tsx

Lines changed: 0 additions & 67 deletions
This file was deleted.

dotcom-rendering/src/layouts/FrontLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import {
1818
MobileAdSlot,
1919
} from '../components/FrontsAdSlots';
2020
import { FrontSection } from '../components/FrontSection';
21+
import { FrontSectionTracker } from '../components/FrontSectionTracker.importable';
2122
import { HeaderAdSlot } from '../components/HeaderAdSlot';
2223
import { Island } from '../components/Island';
2324
import { LabsHeader } from '../components/LabsHeader';
2425
import { LabsSection } from '../components/LabsSection';
2526
import { Masthead } from '../components/Masthead/Masthead';
2627
import { Section } from '../components/Section';
27-
import { SectionTracker } from '../components/SectionTracker.importable';
2828
import { Snap } from '../components/Snap';
2929
import { SnapCssSandbox } from '../components/SnapCssSandbox';
3030
import { StickyBottomBanner } from '../components/StickyBottomBanner.importable';
@@ -749,7 +749,7 @@ export const FrontLayout = ({ front, NAV }: Props) => {
749749
</BannerWrapper>
750750

751751
<Island priority="enhancement" defer={{ until: 'idle' }}>
752-
<SectionTracker />
752+
<FrontSectionTracker />
753753
</Island>
754754
</>
755755
);

0 commit comments

Comments
 (0)