SwGallery

SwGallery is a non-scrolling cover-card container that wraps into an equal-height grid, complementary to SwRail (horizontal scrolling row). It only owns layout and fetch pacing — all card visuals (cover / badge / rating / episode count) are rendered by the business via the #default scoped slot (usually with SwImage); movie / comic / novel covers are all the same to the component.

Poster wall: minItemWidth adaptive columns + load-more button (with remaining count)

加载中...

Column control (pick one)

ModeUsageFits
Fixed card width, adaptive (default):min-item-width="180"Poster walls: column count follows window resizing (CSS Grid auto-fill + minmax)
Locked column count:columns="3" or :columns="{ mobile: 1, tablet: 2, desktop: 3 }"Designs with explicit column counts that must not change on ultra-wide screens (breakpoints align with framework: mobile <768 / desktop ≥1024; missing tiers fall back to the nearest)

Both are pure CSS (media query + CSS variables) — zero JS, SSR-safe. Passing columns overrides minItemWidth.

columns breakpoint lock (1/2/3 columns) + custom #load-more button

加载中...

loadMode auto: load on reaching bottom

加载中...

total remaining count + auto finish

加载中...

Load more (three-state loadMode, default button)

loadModeBehaviorScenario
'button' (default)Bottom button; with total it shows a count ("加载更多 (N)" — "Load more (N)"): with pageSize also passed, N = min(pageSize, remaining); otherwise N = remainingFull list pages
'auto'An IntersectionObserver sentinel near the bottom triggers automatically (autoThreshold lead distance; keeps filling until the first screen is full)Infinite-scroll lists
'none'Pure gridMulti-row content blocks on category home pages; paginated paging (with SwPagination, replace-mode swapping the whole items set)

Append-mode data flow: on @load-more, append to items; an internal busy lock + the loading prop form a double lock against duplicate triggers (appending to items / finishing loading auto-unlocks, so failure cases can retry). Set finished when there is no more data (with total passed, remaining=0 is automatically treated as the end).

Failure & retry: when a request fails, the parent sets error: true and the load area becomes a "加载失败,点击重试" (load failed, tap to retry) button (clicking still emits @load-more; the parent clears error before retrying); in auto mode a failure removes the sentinel and switches to manual retry (preventing auto-retry loops) — once error is cleared and items are appended, auto loading resumes.

Failure & retry: error flag → retry button → clear and continue (with page-size button count)

加载中...

loadMode none: category-home content block (pure grid)

加载中...

Standard Usage

<script setup lang="ts">
const items = ref<Movie[]>([])
const total = ref(0)
const loading = ref(false)

async function loadMore() {
  loading.value = true
  try {
    const r = await request<MovieList>('/movie/list', { body: { offset: items.value.length } })
    items.value = [...items.value, ...r.list]
    total.value = +r.total
  } finally {
    loading.value = false
  }
}
await loadMore()
</script>

<template>
  <SwGallery :items="items" :total="total" :loading="loading" :min-item-width="180" @load-more="loadMore">
    <template #default="{ item }">
      <SwLink :to="`/movie/${item.id}`" :track="{ /* card tracking goes through SwLink; the grid has none built in */ }">
        <SwImage :src="item.img" :alt="item.name" ratio="2/3" />
        <p>{{ item.name }}</p>
      </SwLink>
    </template>
  </SwGallery>
</template>

Props

NameTypeRequiredDefaultDescription
itemsT[]✓—Append-mode data source (generic; the component doesn't care about the shape)
minItemWidthnumber | string240Minimum card width (auto-fill mode). A number is treated as px; a string is passed through as-is, e.g. min(15.625rem, 46%) or var(--x)
columnsnumber | { mobile?, tablet?, desktop? }undefinedLocked column count; overrides minItemWidth when passed
loadMode'button' | 'auto' | 'none''button'Load-more mode
loadingbooleanfalseExternal loading state (one of the double locks)
finishedbooleanfalseNo more data
totalnumberundefinedTotal count → button count; remaining=0 is treated as the end
pageSizenumberundefinedItems per load → the button count shows min(pageSize, remaining); falls back to the remaining count when omitted
errorbooleanfalseLast load failed (maintained by the parent); when true the load area becomes a "load failed, tap to retry" button
autoThresholdnumber | string200Sentinel lead distance for auto mode. A number is treated as px; a string is passed straight to rootMargin, so % works for viewport-relative prefetching. Can be changed at runtime (the observer is rebuilt)
ariaLabelstringundefinedOutputs role="region" + aria-label only when passed

Events

NamePayloadDescription
load-more—Asks the parent to append items (de-duplicated inside the double lock)

Slots

NameScopeDescription
default{ item, index }Each card (the business renders all visuals)
load-more{ loading, remaining, error }Custom load-more button area
finished—End-of-list text (default 「已经到底了」, i.e. "You've reached the end")
empty—Empty state when items is empty (default 「暂无内容」, i.e. "No content yet")

Expose

NameDescription
loadMore()Imperatively trigger one load (subject to the double lock)

CSS Variables

VariableDefaultDescription
--sw-gallery-gap1remGrid gap
--sw-gallery-minWritten from minItemWidthMinimum card width
--sw-gallery-templateFalls back to minItemWidth auto-fillCustom-breakpoint escape hatch: the columns prop only offers mobile/tablet/desktop. For more breakpoints, omit columns and set this variable inside your own media queries (its value is grid-template-columns). Mutually exclusive with columns — passing the prop overrides it

Caveats

  • No built-in tracking: card impression / click go through SwLink :track inside the card; the grid itself reports nothing.
  • Pager scenario: numbered paging is a replace-mode data flow — use loadMode="none" + SwPagination (framework#131); don't stuff page numbers into the Gallery.
  • For component selection (horizontal scroll / carousel / video feed / grid) see the SwRail selection table.
  • Pass :loading for retry-on-error too: both error and loading are parent-maintained; on failure, passing only :error without :loading keeps the internal load lock held and the retry click does nothing — pass :loading as well, and clear error back to false before firing the retry request.
  • Pair @load-more with :finished or :total: with neither, "reached the end" is never detected — the button stays put (button mode) or the sentinel keeps re-firing empty pages (an infinite loop in auto mode). Set :finished="true" when there's no more data, or pass :total so the remaining count converges to zero.

See Also