From 1e97e7c805df0273a2867547f1340e2bbec0386a Mon Sep 17 00:00:00 2001 From: Ludwig Mey Date: Tue, 28 Jul 2026 21:41:25 +1200 Subject: [PATCH] =?UTF-8?q?Fix=20slideshow=20crossfade=20flash=20=E2=80=94?= =?UTF-8?q?=20wait=20for=20image=20load=20before=20fading=20in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the active class was applied via requestAnimationFrame before the image had decoded, causing a brief blank-then-flash. Now loadSlide waits for onload before transitioning, and advanceSlide checks complete/naturalWidth (or waits for onload) on the preloaded slot before doing the swap. Co-Authored-By: Claude Sonnet 4.6 --- static/js/app.js | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index 70e91a9..f103073 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -302,10 +302,17 @@ function hideSlideshow() { function loadSlide(slot, url) { const img = document.getElementById(`slide-${slot}`); - img.src = url; if (slot === activeSlot) { - // Slight delay lets the browser decode before fading in - requestAnimationFrame(() => requestAnimationFrame(() => img.classList.add('active'))); + // Only fade in once the image is fully decoded — no blank-then-flash + img.onload = () => { img.onload = null; img.classList.add('active'); }; + img.src = url; + // Handle cached images that won't fire onload + if (img.complete && img.naturalWidth > 0) { + img.onload = null; + img.classList.add('active'); + } + } else { + img.src = url; // preload only } } @@ -314,13 +321,20 @@ function advanceSlide() { const current = document.getElementById(`slide-${activeSlot}`); const next = document.getElementById(`slide-${nextSlot}`); - // Fade in the preloaded next image, fade out current - next.classList.add('active'); - current.classList.remove('active'); - activeSlot = nextSlot; + const doSwap = () => { + next.onload = null; + next.classList.add('active'); + current.classList.remove('active'); + activeSlot = nextSlot; + // Preload the upcoming photo into the now-hidden slot + slideIndex = (slideIndex + 1) % photos.length; + current.src = photos[(slideIndex + 1) % photos.length]; + }; - // Preload the one after next into the now-hidden slot - slideIndex = (slideIndex + 1) % photos.length; - const upcoming = photos[(slideIndex + 1) % photos.length]; - current.src = upcoming; + // If the preloaded image is ready, swap immediately; otherwise wait for it + if (next.complete && next.naturalWidth > 0) { + doSwap(); + } else { + next.onload = doSwap; + } }