# Waterfall

`ShowtimeWaterfall` 使用 `ResizeObserver` 测量默认插槽中每张卡片的实际高度，并把下一张卡片分配给当前最短列。它不需要也不接受单个卡片的高度参数；数据顺序会按从左到右的视觉顺序进入瀑布流。组件在页面接近底部时触发 `load` 事件，父组件负责切换 `loading` 和 `finished`。

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { ShowtimeWaterfall } from 'showtime-components'

const loading = ref(false)
const finished = ref(false)
const cards = ref([])

async function loadMore() {
  loading.value = true
  const nextCards = await fetchMoreCards()
  cards.value.push(...nextCards)
  finished.value = nextCards.length === 0
  loading.value = false
}
</script>

<template>
  <ShowtimeWaterfall
    :loading="loading"
    :finished="finished"
    loading-text="正在加载"
    finished-text="没有更多内容"
    @load="loadMore"
  >
    <article v-for="card in cards" :key="card.id">
      {{ card.title }}
    </article>
  </ShowtimeWaterfall>
</template>
```

## Props

| 参数名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| `columns` | 瀑布流列数 | `number` | `2` |
| `gap` | 列与卡片间距，数字按 px 处理 | `number \| string` | `12` |
| `loading` | 是否正在请求下一批数据 | `boolean` | `false` |
| `finished` | 是否已无更多数据 | `boolean` | `false` |
| `threshold` | 距离视口底部的加载阈值（px） | `number` | `160` |
| `skeletonCount` | 加载时的内置骨架卡片总数量，会均分到各列 | `number` | `4` |
| `skeletonHeight` | 内置骨架卡片最小高度 | `number \| string` | `180` |
| `loadingText` | 加载状态文字 | `string` | `''` |
| `finishedText` | 加载结束文字 | `string` | `''` |

## Slots

| 插槽名 | 描述 |
| --- | --- |
| `default` | 要排列的内容卡片。 |
| `skeleton` | 自定义加载骨架卡片，接收 `index`。 |

## Events

| 事件名 | 描述 |
| --- | --- |
| `load` | 接近页面底部，且 `loading` 与 `finished` 均为 `false` 时触发。 |

## 样式定制

内置骨架卡片使用 `ShowtimeSkeleton`，默认显示扫光动画，并会均分到各列。它使用共享的表面、边框、阴影和圆角 token，参见 [样式定制](./style-customization.md)。`gap`、`skeletonHeight` 等实例布局仍由组件 props 控制。

## 动效说明

- 已渲染卡片和加载骨架会以轻微的透明度、纵向位移进入，避免内容突然跳入视野。
- 卡片因真实高度测量而重新分配列时，会沿布局路径平滑移动。
- 移除卡片时会先脱离布局流并淡出，避免其他卡片在离场过程中被遮挡。
- 系统开启 `prefers-reduced-motion: reduce` 时，仅保留很短的透明度变化，关闭位移和列移动。
