ScrollArea 滚动区域

扩展原生滚动功能,支持跨浏览器的自定义样式,并提供针对大型列表的虚拟滚动支持。

组件特性

  • 跨浏览器的自定义滚动条样式。
  • 支持垂直、水平或双向滚动。
  • 支持平滑滚动 (smooth scrolling)。
  • 提供滚动位置和触边检测事件。
  • 虚拟滚动 支持,高效渲染海量数据列表。

双向滚动

<script setup lang="ts">
import { BrScrollArea } from '@breezeui/vue'

const artworks = [
  {
    artist: 'Ornella Binni',
    art: 'https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80',
  },
  {
    artist: 'Tom Byrom',
    art: 'https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80',
  },
  {
    artist: 'Vladimir Malyavko',
    art: 'https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80',
  },
]
</script>

<template>
  <BrScrollArea class="h-[200px] w-full max-w-[350px] whitespace-nowrap rounded-md border" direction="both" type="always">
    <div class="flex w-max space-x-4 p-4">
      <figure v-for="artwork in artworks" :key="artwork.artist" class="shrink-0">
        <div class="overflow-hidden rounded-md">
          <img
            :src="artwork.art"
            :alt="artwork.artist"
            class="aspect-[3/4] h-fit w-fit object-cover"
            width="150"
            height="200"
          >
        </div>
        <figcaption class="pt-2 text-xs text-muted-foreground">
          Photo by <span class="font-semibold text-foreground">{{ artwork.artist }}</span>
        </figcaption>
      </figure>
    </div>
  </BrScrollArea>
</template>

平滑滚动与事件

<script setup lang="ts">
import { BrScrollArea, BrButton } from '@breezeui/vue'

const scrollTo = (id: string) => {
  const el = document.getElementById(id)
  if (el) {
    el.scrollIntoView({ behavior: 'smooth', block: 'start' })
  }
}

const handleScrollTop = () => { console.log('Scrolled to top') }
const handleScrollBottom = () => { console.log('Scrolled to bottom') }
const handleScrollPosition = (pos: { x: number; y: number }) => { 
  // console.log('Position:', pos) 
}
</script>

<template>
  <div>
    <div class="flex gap-2 mb-4">
      <BrButton size="sm" @click="scrollTo('section-1')">Section 1</BrButton>
      <BrButton size="sm" @click="scrollTo('section-2')">Section 2</BrButton>
      <BrButton size="sm" @click="scrollTo('section-3')">Section 3</BrButton>
    </div>
    <BrScrollArea 
      smooth 
      class="h-[250px] w-full max-w-[400px] rounded-md border p-4"
      @scroll-top="handleScrollTop"
      @scroll-bottom="handleScrollBottom"
      @scroll-position="handleScrollPosition"
    >
      <div class="space-y-8">
        <div id="section-1" class="h-[200px] rounded bg-primary/10 p-4 flex items-center justify-center">Section 1</div>
        <div id="section-2" class="h-[200px] rounded bg-secondary/10 p-4 flex items-center justify-center">Section 2</div>
        <div id="section-3" class="h-[200px] rounded bg-destructive/10 p-4 flex items-center justify-center">Section 3</div>
      </div>
    </BrScrollArea>
  </div>
</template>

与表格集成

<script setup lang="ts">
import { BrScrollArea, BrCard, BrCardHeader, BrCardTitle, BrCardContent, BrTable } from '@breezeui/vue'

const tableColumns = [
  { key: 'invoice', title: 'Invoice', width: 100 },
  { key: 'status', title: 'Status' },
  { key: 'method', title: 'Method' },
  { key: 'amount', title: 'Amount', align: 'right' },
]

const tableData = Array.from({ length: 15 }).map((_, i) => ({
  invoice: `INV00${i + 1}`,
  status: 'Paid',
  method: 'Credit Card',
  amount: `$${(Math.random() * 1000).toFixed(2)}`,
}))
</script>

<template>
  <BrCard class="w-full max-w-[800px]">
    <BrCardHeader>
      <BrCardTitle>Recent Orders</BrCardTitle>
    </BrCardHeader>
    <BrCardContent>
      <BrScrollArea class="h-[300px] w-full rounded-md border" direction="both">
        <div class="min-w-[800px]">
          <BrTable :columns="tableColumns" :data="tableData" />
        </div>
      </BrScrollArea>
    </BrCardContent>
  </BrCard>
</template>

无限滚动

<script setup lang="ts">
import { ref } from 'vue'
import { BrScrollArea } from '@breezeui/vue'

const infiniteList = ref(Array.from({ length: 20 }).map((_, i) => `Item ${i + 1}`))
const isLoadingMore = ref(false)

const handleInfiniteScroll = () => {
  if (isLoadingMore.value) return
  isLoadingMore.value = true
  
  // Simulate network request
  setTimeout(() => {
    const currentLength = infiniteList.value.length
    const moreItems = Array.from({ length: 10 }).map((_, i) => `Item ${currentLength + i + 1}`)
    infiniteList.value.push(...moreItems)
    isLoadingMore.value = false
  }, 1000)
}
</script>

<template>
  <BrScrollArea 
    class="h-[300px] w-full max-w-[400px] rounded-md border p-4"
    :scroll-threshold="50"
    @scroll-bottom="handleInfiniteScroll"
  >
    <div class="space-y-4">
      <div 
        v-for="item in infiniteList" 
        :key="item"
        class="rounded bg-primary/5 p-4 flex items-center justify-between"
      >
        <span>{{ item }}</span>
      </div>
      
      <div v-if="isLoadingMore" class="py-4 flex justify-center items-center text-sm text-muted-foreground">
        <span class="animate-pulse">Loading more...</span>
      </div>
    </div>
  </BrScrollArea>
</template>

虚拟滚动

启用虚拟滚动,可以毫无性能压力地渲染包含成千上万条数据的列表。

<script setup lang="ts">
import { BrScrollArea } from '@breezeui/vue'

const virtualItems = Array.from({ length: 10000 }).map((_, i) => ({
  id: i,
  text: `Virtual Item ${i + 1}`,
}))
</script>

<template>
  <BrScrollArea 
    class="h-[300px] w-full max-w-[400px] rounded-md border bg-card"
    virtual-scroll
    :items="virtualItems"
    :item-height="40"
  >
    <template #item="{ item, index }">
      <div class="flex items-center px-4 h-[40px] text-sm border-b border-border/50 last:border-b-0 hover:bg-muted/50 transition-colors">
        <span class="text-muted-foreground/70 w-12 text-xs">{{ index + 1 }}</span>
        <span class="font-normal text-foreground">{{ item.text }}</span>
      </div>
    </template>
  </BrScrollArea>
</template>

API

属性 (Props)

名称类型默认值说明
type'auto' | 'always' | 'scroll' | 'hover''hover'描述滚动条的可见性行为。
direction'vertical' | 'horizontal' | 'both''vertical'滚动区域的方向。
smoothbooleanfalse是否启用平滑滚动行为。
scrollThresholdnumber0距离边缘多少像素时触发滚动触边事件。
virtualScrollbooleanfalse是否启用虚拟滚动模式。
itemsany[][]启用虚拟滚动时需要渲染的数据数组。
itemHeightnumber40虚拟滚动模式下,每一项的固定高度(像素)。

事件 (Events)

名称参数说明
scroll-position(position: { x: number; y: number })滚动发生时触发,提供当前滚动的坐标。
scroll-top-滚动到顶部边缘时触发(受 threshold 影响)。
scroll-bottom-滚动到底部边缘时触发(受 threshold 影响)。
scroll-left-滚动到左侧边缘时触发(受 threshold 影响)。
scroll-right-滚动到右侧边缘时触发(受 threshold 影响)。

插槽 (Slots)

名称作用域说明
default-需要滚动的主要内容。
item{ item: any, index: number }虚拟滚动模式下,自定义每一项渲染的模板插槽。