diff --git a/.jules/bolt.md b/.jules/bolt.md index 84d2a32..64d3746 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. + +## 2025-02-15 - [CSS Performance: Transform instead of Width for scroll indicators] +**Learning:** Updating the `width` property on an element during high-frequency events (like scroll) triggers expensive layout calculations and repaints, leading to jank. +**Action:** Use `transform: scaleX(0)` with `transform-origin: left` instead. When updating the progress, modify the `transform` property to `scaleX(value)` where `value` is between 0 and 1. This offloads the work to the GPU via composition and avoids layout thrashing. diff --git a/script.js b/script.js index 2d49f98..cd4731e 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; + // ⚡ bolt: offload scroll progress animation to GPU using transform: scaleX + 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; + // ⚡ bolt: offload scroll progress animation to GPU using transform: scaleX + this.bar.style.transform = `scaleX(${Math.max(0, Math.min(1, progress))})`; } }; diff --git a/styles.css b/styles.css index 83da5bc..1237338 100644 --- a/styles.css +++ b/styles.css @@ -594,9 +594,12 @@ button { left: 0; height: 2px; background: var(--accent); - width: 0%; + /* ⚡ bolt: replaced width transitions with GPU-accelerated transform: scaleX */ + width: 100%; + transform-origin: left; + transform: scaleX(0); z-index: 10001; - transition: width 0.1s linear; + will-change: transform; } /* === Gallery === */