Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -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.
**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`.
15 changes: 12 additions & 3 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 });
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous isInView() logic treated the carousel as “in view” when any part intersected the viewport (rect.top < innerHeight && rect.bottom > 0). Using IntersectionObserver with threshold: 0.1 changes that behavior (requires ~10% visibility), which can alter keyboard/wheel navigation activation near the viewport edges. To preserve existing behavior, use a threshold that matches the old condition (typically 0, optionally with an appropriate rootMargin).

Suggested change
}, { threshold: 0.1 });
}, { threshold: 0 });

Copilot uses AI. Check for mistakes.
this.observer.observe(this.carousel);

// Get the stack element early for all event handlers
const stack = this.carousel.querySelector('.carousel-stack');

Expand Down Expand Up @@ -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;
Comment on lines 1040 to +1042
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isVisible starts as false and is only updated asynchronously by the observer callback; immediately after init() (before the first observer delivery), isInView() will return false even if the carousel is already on screen, so early keydown navigation can be ignored. Consider initializing isVisible once during init() (a one-time viewport check) so behavior is correct from the first interaction while still avoiding high-frequency layout reads.

Copilot uses AI. Check for mistakes.
},

navigate(direction) {
Expand Down
Loading