InfiniteScroll 无限滚动

可以与 PullToRefresh 下拉刷新组件组合使用。

目前仅支持 touch 事件,请切换到移动端调试模式尝试。

注意:InfiniteScroll 一定要有明确或潜在的固定高度。比如可以通过直接设置 height,或者在某个有固定高度的容器内。

示例

基础用法

<script lang="ts">
  let data = $state<string[]>([])
  let hasMore = $derived(data.length < 26 * 2)

  // 异步加载数据
  async function loadMore() {
    await delay(1000)
    data = [...data, ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
  }
</script>

{#snippet list()}
  {#each data as item}
    <ListItem>{item}</ListItem>
  {/each}
{/snippet}

<InfiniteScroll {hasMore} {loadMore}>
  <List class="bg-white">
    {@render list()}
  </List>
</InfiniteScroll>

下拉刷新

<script lang="ts">
  // 刷新
  async function handleRefresh() {
    await delay(1000)
    data = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
    hasError = false
  }
</script>

<PullToRefresh onRefresh={handleRefresh}>
  <InfiniteScroll class="h-full" {hasMore} {loadMore}>
    <List class="bg-white">
      {@render list()}
    </List>
  </InfiniteScroll>
</PullToRefresh>

加载失败

<script lang="ts">
  let hasError = $state(false)

  // 异步加载数据
  async function loadMore(retry: boolean) {
    // 正常加载
    if (!retry) {
      await delay(1000)
      data = [...data, ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
    }
    // 加载失败重新加载
    else {
      await delay(1000)
      data = [...data, ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
    }
  }
</script>

<InfiniteScroll class="h-full" bind:hasError {hasMore} {loadMore}>
  <List class="bg-white">
    {@render list()}
  </List>
</InfiniteScroll>

自定义

<PullToRefresh class="-mt-[1px] h-full" onRefresh={handleRefresh} completeDuration={0}>
  <!-- 自定义下拉头部 -->
  {#snippet header(status, offset)}
    <div class="flex items-center justify-center p-2">
      {#if status === 'refreshing'}
        <Icon
          class="animate-spin rounded-full bg-white p-1 text-blue-600"
          name="refresh"
          size={32}
        />
      {:else}
        <Icon
          class="rounded-full bg-white p-1 {status === 'loosing'
            ? 'text-blue-600'
            : 'text-gray-400'}"
          style="rotate: {offset * 5}deg"
          name="reset-right"
          size={32}
        />
      {/if}
    </div>
  {/snippet}

  <!-- 无限滚动 -->
  <InfiniteScroll class="h-full" bind:hasError {hasMore} loadMore={loadMoreError}>
    <List class="bg-white">
      {@render list()}
    </List>

    <!-- 自定义底部 -->
    {#snippet footer(status, reload)}
      <div class="flex items-center justify-center" style="height: 50px">
        {#if status === 'idle' || status === 'loading'}
          <Icon
            class="animate-spin rounded-full bg-white p-1 text-blue-600"
            name="refresh"
            size={32}
          />
        {:else if status === 'finished'}
          <Divider
            class="flex-auto"
            style="color: #1677ff; border-color: #1677ff; border-style: dashed"
            >我是有底线的</Divider
          >
        {:else if status == 'error'}
          <Button color="danger-plain" block --sunp-border="0px" onclick={reload}
            >加载失败,点击重试</Button
          >
        {/if}
      </div>
    {/snippet}
  </InfiniteScroll>
</PullToRefresh>

PullToRefresh

属性

属性 说明 类型 默认值
hasMore 是否还有更多内容 boolean 必填
loadMore 加载更多回调函数(retry: 重试) (retry: boolean) => Promise 必填
footer 自定义底部加载状态区域 Snippet<[status: InfiniteScrollStatus, reload: () => Promise]> 组件默认样式
threshold 触发加载事件的滚动触底距离阈值,单位为 px。不包含 footer 的高度 number 0
completeDuration 完成状态持续时间(毫秒)。填 0 表示不会进入等待状态 number 500
hasError 加载错误。组件向调用者提供的状态 boolean 只读

Footer Snippet

Snippet<[status: InfiniteScrollStatus, reload: () => Promise<void>]>
属性 说明 类型
status 当前组件状态 PullToRefreshStatus
reload 重新加载按钮 onclick 事件回调 () => Promise

InfiniteScrollStatus

/** 无限滚动状态 */
export type InfiniteScrollStatus =
  /** 空闲状态 */
  | 'idle'
  /** 加载中 */
  | 'loading'
  /** 加载完成 */
  | 'finished'
  /** 加载失败 */
  | 'error'