feat(gallery): reveal tiles on image load + single initial fetch
The 5×10 metadata batching only staggered the cheap layer (JSON); thumbnails load as independent <img> requests and clustered, so tiles "popped in together" after a wait. Two changes: - GalleryItem reveals each tile when ITS OWN thumbnail fires @load (with an onMounted complete-check for cached thumbs), playing a showcase-style flip-up entrance. Tiles now cascade in natural load order instead of all at once. Honors prefers-reduced-motion. - gallery store does ONE initial fetch (limit=50) instead of 10 serial /scroll round-trips. Fewer RTTs, faster first paint; the reveal-on-load is what makes appearance progressive now. Infinite scroll pulls 25/trigger. Tests: GalleryItem gains is-loaded only after @load; loadInitial issues exactly one scroll request at the initial limit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,15 @@ import { ref, computed } from 'vue'
|
||||
import { useApi } from '../composables/useApi.js'
|
||||
import { useInflightToken } from '../composables/useInflightToken.js'
|
||||
|
||||
// Operator-confirmed 2026-05-30: fetch PAGE-sized chunks instead of one
|
||||
// 50-item request so items render as each batch lands. Total initial
|
||||
// count is unchanged (PAGE * INITIAL_BATCHES = 50). Infinite-scroll also
|
||||
// pulls PAGE per trigger to keep appends progressive.
|
||||
const PAGE = 5
|
||||
const INITIAL_BATCHES = 10
|
||||
// Initial paint is a SINGLE request (INITIAL_LIMIT). The old 10×serial-
|
||||
// batch loop (2026-05-30) only staggered METADATA, which isn't the visual
|
||||
// bottleneck — thumbnails load as independent `<img>` requests, and
|
||||
// GalleryItem now reveals each tile on its own image `@load`. One fetch is
|
||||
// far fewer round-trips and faster to first paint; the reveal-on-load is
|
||||
// what makes appearance progressive. Infinite scroll pulls PAGE per trigger.
|
||||
// Reworked 2026-06-04.
|
||||
const PAGE = 25
|
||||
const INITIAL_LIMIT = 50
|
||||
|
||||
export const useGalleryStore = defineStore('gallery', () => {
|
||||
const api = useApi()
|
||||
@@ -32,22 +35,16 @@ export const useGalleryStore = defineStore('gallery', () => {
|
||||
images.value = []
|
||||
dateGroups.value = []
|
||||
nextCursor.value = null
|
||||
// Sequentially fetch INITIAL_BATCHES chunks so items render as each
|
||||
// batch lands rather than blocking on one big response. Stop early
|
||||
// when the backend reports no more pages.
|
||||
for (let i = 0; i < INITIAL_BATCHES; i++) {
|
||||
if (i > 0 && nextCursor.value === null) break
|
||||
await loadMore()
|
||||
}
|
||||
await loadMore(INITIAL_LIMIT)
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
async function loadMore(limit = PAGE) {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
error.value = null
|
||||
const t = inflight.claim()
|
||||
try {
|
||||
const params = { limit: PAGE, ...activeFilterParam() }
|
||||
const params = { limit, ...activeFilterParam() }
|
||||
if (nextCursor.value) params.cursor = nextCursor.value
|
||||
const body = await api.get('/api/gallery/scroll', { params })
|
||||
if (!t.isCurrent()) return
|
||||
|
||||
Reference in New Issue
Block a user