From 535010e5125b644f9bba715de28e5a66a8a36f47 Mon Sep 17 00:00:00 2001 From: dttdrv <154076940+dttdrv@users.noreply.github.com> Date: Fri, 20 Mar 2026 06:08:00 +0000 Subject: [PATCH] perf(photocarousel): use intersectionobserver for visibility state replaces the synchronous `getboundingclientrect` call in `isinview()` with a cached visibility boolean updated via an asynchronous `intersectionobserver`. this prevents layout thrashing on high-frequency events like keydown or mousewheel when the carousel is present. --- .jules/bolt.md | 5 ++++- script.js | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 6e80324..a1d693b 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,6 @@ ## 2024-05-16 - [Frontend Performance: Caching getBoundingClientRect in Animation Loops] **Learning:** Frequent calls to `getBoundingClientRect()` inside `requestAnimationFrame` loops or scroll handlers (like in `MagneticLetters`, `MobileTouchRepel`, and `ParallaxLayers`) cause significant layout thrashing and forced synchronous layouts, degrading rendering performance. -**Action:** Always pre-calculate and cache document-relative element positions during initialization (`init()`). Update this cache on window resize events and after custom fonts load (using `document.fonts.ready.then()`). During active animations (`animate()`, `update()`, etc.), calculate viewport-relative positions using the cached document coordinates minus the current scroll offset (`window.scrollX` / `window.scrollY`) instead of directly querying the DOM. \ No newline at end of file +**Action:** Always pre-calculate and cache document-relative element positions during initialization (`init()`). Update this cache on window resize events and after custom fonts load (using `document.fonts.ready.then()`). During active animations (`animate()`, `update()`, etc.), calculate viewport-relative positions using the cached document coordinates minus the current scroll offset (`window.scrollX` / `window.scrollY`) instead of directly querying the DOM. +## 2024-05-16 - [Performance Optimization: Use IntersectionObserver to Replace Synchronous getBoundingClientRect] +**Learning:** Checking element visibility synchronously via `getBoundingClientRect` (like in `PhotoCarousel.isInView()`) inside high-frequency event handlers (e.g. scroll, mousewheel, keydown) causes forced synchronous layout thrashing. +**Action:** Always prefer caching the visibility state asynchronously using `IntersectionObserver` when you only need to know if an element is in view, and read that cached boolean instead of calling `getBoundingClientRect`. diff --git a/script.js b/script.js index 7ec7a77..dd53f5f 100644 --- a/script.js +++ b/script.js @@ -864,6 +864,8 @@ const PhotoCarousel = { currentIndex: 2, // Start with center card (index 2) positions: ['far-left', 'left', 'center', 'right', 'far-right'], isAnimating: false, + isVisible: false, // ⚡ bolt: cache visibility state to prevent layout thrashing + observer: null, init() { this.carousel = document.getElementById('photo-carousel'); @@ -916,6 +918,14 @@ const PhotoCarousel = { } }, { passive: false }); + // ⚡ bolt: use intersectionobserver instead of getboundingclientrect + this.observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + this.isVisible = entry.isIntersecting; + }); + }, { threshold: 0.1 }); + this.observer.observe(this.carousel); + // Get the stack element early for all event handlers const stack = this.carousel.querySelector('.carousel-stack'); @@ -1028,9 +1038,8 @@ const PhotoCarousel = { }, isInView() { - if (!this.carousel) return false; - const rect = this.carousel.getBoundingClientRect(); - return rect.top < window.innerHeight && rect.bottom > 0; + // ⚡ bolt: return cached state instead of synchronous layout query + return this.isVisible; }, navigate(direction) {