diff options
| author | Adam Soltys <[email protected]> | 2026-09-03 21:20:46 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-03 21:20:46 -0700 |
| commit | 3b65462549269e3895345919b51164e017bcd13d (patch) | |
| tree | cf81448d195c18f877f876ee7649983879082112 /src | |
| parent | 5e960611887b4eae51caa1973e411a78caf3d92e (diff) | |
| download | coinosv3-3b65462549269e3895345919b51164e017bcd13d.tar.gz coinosv3-3b65462549269e3895345919b51164e017bcd13d.zip | |
The balance carousel stops stuttering during boot: the post-render alignment no longer reads layout (offsetLeft/scrollLeft) on every one of the many boot renders — an _alignedTo guard short-circuits before any layout touch once the card is in place, and the scroll's own settle marks itself aligned so it never re-aligns behind the user. The pass moved to rAF, and each slide gets layout containment so the history list building in below can't force it to reflow mid-scroll
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 17 | ||||
| -rw-r--r-- | src/style.css | 12 |
2 files changed, 24 insertions, 5 deletions
@@ -3384,16 +3384,22 @@ function balanceCard() { // active one's edge and drags/snaps into place, BlueWallet-style. const ORDER2 = ['spending', 'savings']; // Align the carousel to the selected card after this render lands — but - // never fight a finger or live momentum. - setTimeout(() => { + // never fight a finger or live momentum, and NEVER touch layout when it's + // already where it belongs. Boot fires many renders (cache, ark connect, + // scan, nostr, balance ticks); reading offsetLeft/scrollLeft on each one + // forced a synchronous layout mid-scroll — the stutter. `_alignedTo` + // short-circuits before any layout read once the card is in place. + requestAnimationFrame(() => { const el = document.querySelector('.bal-carousel'); - if (!el || Date.now() - (el._lastScroll || 0) < 350) return; + if (!el || el._alignedTo === sel) return; + if (Date.now() - (el._lastScroll || 0) < 350) return; const k = el.children[ORDER2.indexOf(sel)]; if (!k) return; const target = k.offsetLeft + k.offsetWidth / 2 - el.clientWidth / 2; if (Math.abs(el.scrollLeft - target) > 6) el.scrollTo({ left: target, behavior: el._inited ? 'smooth' : 'auto' }); + el._alignedTo = sel; el._inited = true; - }, 0); + }); _accDir = null; // the drag is the animation here // The settled-card check, shared by the scroll debounce and drag release. // While a finger or mouse button is still DOWN nothing switches — hovering @@ -3408,6 +3414,9 @@ function balanceCard() { }); const view = ORDER2[best]; if (view && view !== accountSel()) { + // the scroll already rests on this card — mark it aligned so the + // post-render pass skips its layout read instead of re-aligning + el._alignedTo = view; ui.account = view; try { localStorage.setItem(ACCOUNT_KEY, view); } catch {} // selecting an account lands on its history (home), any open diff --git a/src/style.css b/src/style.css index e5c3156..e846f52 100644 --- a/src/style.css +++ b/src/style.css @@ -462,7 +462,17 @@ hr.divider { margin: 16px 0; } touch-action: pan-x pan-y; } .bal-carousel::-webkit-scrollbar { display: none; } -.bal-slide { flex: 0 0 calc(100% - 40px); scroll-snap-align: center; min-width: 0; } +/* Isolate each card's layout + paint from the rest of the page: the history + list building in below (and the balance number ticking) can't invalidate + the carousel's layer, and the scroll stays on the compositor even while the + main thread is busy with boot work. */ +.bal-slide { + flex: 0 0 calc(100% - 40px); scroll-snap-align: center; min-width: 0; + /* layout containment (not paint — that would clip the card's own shadow): + the history list building in below and the balance ticking can't force + the slide to reflow, so the scroll stays on the compositor during boot */ + contain: layout; +} .bal-carousel .card + .card { margin-top: 0; } .bal-carousel { cursor: grab; } /* touch never shows a cursor — no media-query hedging */ .bal-carousel.grabbing, .bal-carousel.grabbing * { cursor: grabbing; user-select: none; } |
