diff --git a/.jules/bolt.md b/.jules/bolt.md index 84d2a32..8f9f915 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -2,3 +2,7 @@ **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. + +## 2024-05-16 - [Frontend Performance: GPU-Accelerated Scroll Progress Bars] +**Learning:** Animating layout-inducing CSS properties like `width` on scroll progress bars triggers layout thrashing and forced reflows on every frame, which can significantly degrade scroll performance. +**Action:** Always implement scroll progress bars (and similar animated UI elements) using GPU-accelerated properties. Use `transform: scaleX()` along with `transform-origin: left`, maintaining a constant `width: 100%`. diff --git a/script.js b/script.js index 2d49f98..e872669 100644 --- a/script.js +++ b/script.js @@ -582,16 +582,18 @@ const ScrollProgress = { if (this.isModalActive) return; const scrollTop = window.scrollY; const docHeight = this.cachedDocHeight - this.cachedWindowHeight; - const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; - this.bar.style.width = `${Math.max(0, Math.min(100, progress))}%`; + const progress = docHeight > 0 ? scrollTop / docHeight : 0; + // GPU-accelerated transform: scaleX() prevents layout thrashing + this.bar.style.transform = `scaleX(${Math.max(0, Math.min(1, progress))})`; }, updateModal() { if (!this.isModalActive || !this.modalContent) return; const scrollTop = this.modalContent.scrollTop; const scrollHeight = this.modalContent.scrollHeight - this.modalContent.clientHeight; - const progress = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0; - this.bar.style.width = `${Math.max(0, Math.min(100, progress))}%`; + const progress = scrollHeight > 0 ? scrollTop / scrollHeight : 0; + // GPU-accelerated transform: scaleX() prevents layout thrashing + this.bar.style.transform = `scaleX(${Math.max(0, Math.min(1, progress))})`; } }; diff --git a/styles.css b/styles.css index 83da5bc..57f8784 100644 --- a/styles.css +++ b/styles.css @@ -588,15 +588,18 @@ button { } /* === Scroll Progress === */ +/* Optimized to use GPU-accelerated transform: scaleX() instead of width to prevent layout thrashing */ .scroll-progress { position: fixed; top: 0; left: 0; height: 2px; background: var(--accent); - width: 0%; + width: 100%; + transform: scaleX(0); + transform-origin: left; z-index: 10001; - transition: width 0.1s linear; + transition: transform 0.1s linear; } /* === Gallery === */