Skip to content

Commit 31aff92

Browse files
committed
feat(analytics): track photo gallery interactions
- Add trackPhotoClick, trackPhotoNavigation, trackLightboxClose helpers - Instrument PhotoGallery.astro with PhotoSwipe event tracking - Instrument MediaGallery.astro with PhotoSwipe event tracking - Track initial photo click, slide navigation, and session completion - Measure engagement depth via viewed photos count and completion rate - Enables analysis of photo click rates, navigation patterns, and gallery engagement
1 parent b96696e commit 31aff92

File tree

3 files changed

+139
-0
lines changed

3 files changed

+139
-0
lines changed

src/components/MediaGallery.astro

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ try {
197197
import PhotoSwipeDynamicCaption from "photoswipe-dynamic-caption-plugin";
198198
import "photoswipe/style.css";
199199
import "photoswipe-dynamic-caption-plugin/photoswipe-dynamic-caption-plugin.css";
200+
import { trackPhotoClick, trackPhotoNavigation, trackLightboxClose } from '@/utils/analytics';
200201

201202
// Check for reduced motion preference
202203
function prefersReducedMotion(): boolean {
@@ -241,6 +242,36 @@ try {
241242

242243
lightbox.init();
243244
console.log("[MediaGallery] Lightbox initialized for:", galleryId);
245+
246+
// Track which photos were viewed during this session
247+
const viewedSlides = new Set<number>();
248+
249+
// Track lightbox open (initial photo click)
250+
lightbox.on('firstUpdate', () => {
251+
const initialIndex = lightbox.pswp?.currSlide?.index ?? 0;
252+
const photoElement = lightbox.pswp?.currSlide?.data?.element;
253+
const photoSrc = photoElement?.getAttribute('href') || 'unknown';
254+
const page = window.location.pathname.split('/').filter(Boolean).pop() || 'unknown';
255+
256+
viewedSlides.add(initialIndex);
257+
trackPhotoClick(galleryId, photoSrc, initialIndex, page);
258+
});
259+
260+
// Track navigation between slides
261+
lightbox.on('change', () => {
262+
const currentIndex = lightbox.pswp?.currSlide?.index ?? 0;
263+
const totalSlides = lightbox.pswp?.getNumItems() ?? 0;
264+
265+
viewedSlides.add(currentIndex);
266+
trackPhotoNavigation(galleryId, currentIndex, totalSlides);
267+
});
268+
269+
// Track session completion when lightbox closes
270+
lightbox.on('close', () => {
271+
const totalSlides = lightbox.pswp?.getNumItems() ?? 0;
272+
trackLightboxClose(galleryId, viewedSlides.size, totalSlides);
273+
viewedSlides.clear(); // Reset for next session
274+
});
244275
});
245276
}
246277

src/components/PhotoGallery.astro

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ try {
196196
import PhotoSwipeDynamicCaption from 'photoswipe-dynamic-caption-plugin';
197197
import "photoswipe/style.css";
198198
import 'photoswipe-dynamic-caption-plugin/photoswipe-dynamic-caption-plugin.css';
199+
import { trackPhotoClick, trackPhotoNavigation, trackLightboxClose } from '@/utils/analytics';
199200

200201
// Check for reduced motion preference
201202
function prefersReducedMotion(): boolean {
@@ -240,6 +241,36 @@ try {
240241

241242
lightbox.init();
242243
console.log("[PhotoGallery] Lightbox initialized for:", galleryId);
244+
245+
// Track which photos were viewed during this session
246+
const viewedSlides = new Set<number>();
247+
248+
// Track lightbox open (initial photo click)
249+
lightbox.on('firstUpdate', () => {
250+
const initialIndex = lightbox.pswp?.currSlide?.index ?? 0;
251+
const photoElement = lightbox.pswp?.currSlide?.data?.element;
252+
const photoSrc = photoElement?.getAttribute('href') || 'unknown';
253+
const page = window.location.pathname.split('/').filter(Boolean).pop() || 'unknown';
254+
255+
viewedSlides.add(initialIndex);
256+
trackPhotoClick(galleryId, photoSrc, initialIndex, page);
257+
});
258+
259+
// Track navigation between slides
260+
lightbox.on('change', () => {
261+
const currentIndex = lightbox.pswp?.currSlide?.index ?? 0;
262+
const totalSlides = lightbox.pswp?.getNumItems() ?? 0;
263+
264+
viewedSlides.add(currentIndex);
265+
trackPhotoNavigation(galleryId, currentIndex, totalSlides);
266+
});
267+
268+
// Track session completion when lightbox closes
269+
lightbox.on('close', () => {
270+
const totalSlides = lightbox.pswp?.getNumItems() ?? 0;
271+
trackLightboxClose(galleryId, viewedSlides.size, totalSlides);
272+
viewedSlides.clear(); // Reset for next session
273+
});
243274
});
244275
}
245276

src/utils/analytics.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,80 @@ export function trackAccordionToggle(
172172
...(section && { section }),
173173
});
174174
}
175+
176+
/**
177+
* Track photo lightbox open (user clicks thumbnail)
178+
*
179+
* Useful for understanding which photos attract attention and get opened.
180+
*
181+
* @param galleryId - Gallery container ID (e.g., 'media-gallery', 'statsbomb-photos')
182+
* @param photoSrc - Image path (unique identifier)
183+
* @param photoIndex - Position in gallery (0-indexed)
184+
* @param page - Page identifier (e.g., 'statsbomb-case-study')
185+
*
186+
* @example
187+
* trackPhotoClick('media-gallery', '/images/team-photo.jpg', 0, 'statsbomb-case-study');
188+
*/
189+
export function trackPhotoClick(
190+
galleryId: string,
191+
photoSrc: string,
192+
photoIndex: number,
193+
page: string,
194+
): void {
195+
trackEvent("photo-click", {
196+
gallery: galleryId,
197+
photo: photoSrc,
198+
position: photoIndex + 1, // Human-readable (1-indexed)
199+
page,
200+
});
201+
}
202+
203+
/**
204+
* Track gallery navigation (prev/next slide in lightbox)
205+
*
206+
* Useful for understanding how deeply users explore galleries.
207+
*
208+
* @param galleryId - Gallery container ID
209+
* @param photoIndex - New slide index (0-indexed)
210+
* @param totalPhotos - Total slides in gallery
211+
*
212+
* @example
213+
* trackPhotoNavigation('media-gallery', 3, 16);
214+
*/
215+
export function trackPhotoNavigation(
216+
galleryId: string,
217+
photoIndex: number,
218+
totalPhotos: number,
219+
): void {
220+
trackEvent("photo-navigation", {
221+
gallery: galleryId,
222+
position: photoIndex + 1,
223+
total: totalPhotos,
224+
depth: `${Math.round(((photoIndex + 1) / totalPhotos) * 100)}%`,
225+
});
226+
}
227+
228+
/**
229+
* Track lightbox close (engagement session end)
230+
*
231+
* Useful for understanding gallery completion rates and engagement depth.
232+
*
233+
* @param galleryId - Gallery container ID
234+
* @param viewedCount - Number of unique photos viewed in this session
235+
* @param totalPhotos - Total slides in gallery
236+
*
237+
* @example
238+
* trackLightboxClose('media-gallery', 5, 16); // Viewed 5 out of 16 photos (31% completion)
239+
*/
240+
export function trackLightboxClose(
241+
galleryId: string,
242+
viewedCount: number,
243+
totalPhotos: number,
244+
): void {
245+
trackEvent("lightbox-close", {
246+
gallery: galleryId,
247+
viewed: viewedCount,
248+
total: totalPhotos,
249+
completion: `${Math.round((viewedCount / totalPhotos) * 100)}%`,
250+
});
251+
}

0 commit comments

Comments
 (0)