From c8c3670ca0eae249efc2a7f803b09f4045eb6263 Mon Sep 17 00:00:00 2001 From: Honey Date: Tue, 28 Apr 2026 09:52:02 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20eliminate=20mobile=20bg=20flash=20?= =?UTF-8?q?=E2=80=94=20double=20rAF=20+=20will-change:opacity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit new Image() onload fires when bytes are downloaded but GPU decode of the blurred layer hasn't happened yet on mobile. Double rAF defers the opacity swap until after the browser has actually painted the new background. will-change:opacity keeps the layers on dedicated compositor layers so the blur doesn't need to be recomputed on every opacity transition. Co-Authored-By: Claude Sonnet 4.6 --- src/player.js | 9 ++++++--- styles.css | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/player.js b/src/player.js index b67c2bf..1d5e680 100644 --- a/src/player.js +++ b/src/player.js @@ -68,9 +68,12 @@ function _crossfadeNpBg(cv) { const nextEl = document.getElementById(`np-bg-${nextId}`); if (!currEl || !nextEl) return; nextEl.style.backgroundImage = cv ? `url('${cv}')` : 'none'; - nextEl.style.opacity = '1'; - currEl.style.opacity = '0'; - _npBgCurr = nextId; + // Double rAF: wait for browser to paint the new bg (GPU decode) before fading + requestAnimationFrame(() => requestAnimationFrame(() => { + nextEl.style.opacity = '1'; + currEl.style.opacity = '0'; + _npBgCurr = nextId; + })); }; if (!cv) { doFade(); return; } const img = new Image(); diff --git a/styles.css b/styles.css index 844c881..79f89f3 100644 --- a/styles.css +++ b/styles.css @@ -515,6 +515,7 @@ nav{flex:1;overflow-y:auto;padding:4px 10px} filter:blur(60px) brightness(.35) saturate(1.4); transform:scale(1.1); transition:opacity .55s ease; + will-change:opacity; z-index:0; }