Files
FabledCurator/frontend/src/utils/preloadImage.js
T
bvandeusen 3a4270e6be
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 13s
CI / frontend-build (push) Successful in 19s
CI / intimp (push) Successful in 3m33s
CI / intapi (push) Successful in 7m41s
CI / intcore (push) Successful in 8m23s
fix(showcase): reveal each tile only once its image is fully decoded
The buffered cascade revealed tiles on an 80ms timer regardless of image
load, so the flip-in animation played on a gray placeholder and the thumbnail
popped in afterward. Worse, MasonryGrid ALSO applied a per-index
animation-delay (index×70ms) that compounded on top of the insert cadence,
so the cascade visibly dragged and desynced as it grew.

Now the producer preloads each queued thumbnail (decode pipelined ahead) and
the consumer awaits that decode before pushing the item — every tile animates
in fully loaded, strictly one at a time. Drop the compounding CSS stagger;
the store's one-item-at-a-time push is the sole pacer, so each tile animates
the instant it mounts. New utils/preloadImage.js (load+decode+timeout gate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 07:19:44 -04:00

22 lines
1004 B
JavaScript

// Resolve once the image at `url` is fully loaded AND decoded (paint-ready),
// or after `timeoutMs` as a safety net so a broken/slow image never stalls a
// cascade. Never rejects — the caller only cares that it's safe to reveal.
//
// Used by the showcase cascade to gate each tile's entry animation on the
// thumbnail actually being ready, so the flip-in plays on a real image rather
// than on a gray placeholder (operator-flagged 2026-06-04).
export function preloadImage (url, timeoutMs = 4000) {
return new Promise((resolve) => {
let done = false
const finish = () => { if (!done) { done = true; resolve() } }
const img = new Image()
img.onload = finish
img.onerror = finish
img.src = url
// decode() resolves at paint-ready (a beat after onload); prefer it when
// available, but onload/onerror/timeout all still settle the promise.
if (typeof img.decode === 'function') img.decode().then(finish).catch(() => {})
setTimeout(finish, timeoutMs)
})
}