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>
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
v-for="item in col" :key="item.id"
|
||||
class="fc-masonry__item"
|
||||
:class="{ 'fc-masonry__item--anim': shouldAnimate(item) }"
|
||||
:style="itemStyle(item)"
|
||||
type="button"
|
||||
@click="$emit('open', item.id)"
|
||||
>
|
||||
@@ -66,12 +65,6 @@ function shouldAnimate(item) {
|
||||
return idx !== undefined && idx >= props.animateFromIndex
|
||||
}
|
||||
|
||||
function itemStyle(item) {
|
||||
if (!shouldAnimate(item)) return {}
|
||||
const idx = idxById.value.get(item.id) - props.animateFromIndex
|
||||
return { '--stagger-index': idx }
|
||||
}
|
||||
|
||||
function aspectStyle(item) {
|
||||
const w = Number(item.width)
|
||||
const h = Number(item.height)
|
||||
@@ -117,12 +110,15 @@ useInfiniteScroll(sentinelEl, () => {
|
||||
.fc-masonry__end { text-align: center; padding: 32px 0; }
|
||||
|
||||
/* Cascade entry: each tile flips up out of a backward tilt and settles
|
||||
into place, one at a time — more pronounced than a plain fade so the
|
||||
showcase reads as an "experience" (operator-flagged 2026-05-28). The
|
||||
`both` fill holds the hidden/tilted 0% state until each tile's staggered
|
||||
turn; the cubic-bezier overshoots slightly past flat then settles.
|
||||
Honors prefers-reduced-motion. Tunables: tilt (-28deg), stagger (70ms),
|
||||
duration (0.6s). */
|
||||
into place — more pronounced than a plain fade so the showcase reads as an
|
||||
"experience" (operator-flagged 2026-05-28). The reveal is paced entirely by
|
||||
the store, which pushes one fully-decoded item at a time (showcase.js); each
|
||||
tile therefore animates the instant it mounts, with NO per-index CSS delay —
|
||||
the old `animation-delay: index×70ms` compounded on top of the insert
|
||||
cadence and made the cascade drag and desync as it grew (operator-flagged
|
||||
2026-06-04). The `both` fill holds the hidden/tilted 0% state until mount;
|
||||
the cubic-bezier overshoots slightly past flat then settles. Honors
|
||||
prefers-reduced-motion. Tunables: tilt (-28deg), duration (0.6s). */
|
||||
@keyframes fc-masonry-item-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
@@ -138,7 +134,6 @@ useInfiniteScroll(sentinelEl, () => {
|
||||
transform-origin: center top;
|
||||
backface-visibility: hidden;
|
||||
animation: fc-masonry-item-in 0.6s cubic-bezier(0.34, 1.45, 0.64, 1) both;
|
||||
animation-delay: calc(var(--stagger-index, 0) * 70ms);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fc-masonry__item--anim {
|
||||
|
||||
Reference in New Issue
Block a user