shadcn-svelte Scroll Area 组件实战:基于 bits-ui 打造跨浏览器统一的自定义滚动区域

原创2026-09-15 16:58:361,892 阅读
文章标签:UI组件前端CLI开发工具

shadcn-svelte Scroll Area 组件实战:基于 bits-ui 打造跨浏览器统一的自定义滚动区域

导读

Scroll Area 是 shadcn-svelte 中用于增强原生滚动能力、实现自定义跨浏览器滚动条外观的核心组件,它在 bits-ui 的无障碍滚动原语之上封装了一层符合 shadcn 设计体系的样式与 Svelte 5 语法($props() / $bindable / {@render children})。本文将以 Scroll Area 官方文档 为主线,完整讲解其安装方式、基本用法、orientation 等关键属性、双向滚动技巧,并结合仓库中真实的组件源码、示例与样式实现,帮助你彻底掌握这一组件的配置细节与底层原理。

组件定位与适用场景

Scroll Area 组件的官方描述是 "Augments native scroll functionality for custom, cross-browser styling"——即在保留原生滚动行为(触摸、鼠标滚轮、键盘操作)的基础上,用可自定义的滚动条替代各浏览器风格迥异的原生滚动条,从而在不同浏览器间呈现一致的视觉效果。

它在 shadcn-svelte 中的典型应用场景包括:

  • 长列表 / 标签列表容器的局部滚动(如版本发布标签、消息记录);
  • 横向滚动的图片画廊、表格列溢出区域;
  • 侧边栏、代码查看器等需要"内容可滚动但滚动条样式统一"的布局。

从仓库结构看,Scroll Area 的组件代码位于 docs/src/lib/registry/ui/scroll-area/,由三个文件构成:scroll-area.svelte(根容器)、scroll-area-scrollbar.svelte(滚动条)、index.ts(统一导出)。它直接依赖 bits-ui 提供的 ScrollAreaPrimitive.Root、Viewport、Scrollbar、Thumb、Corner 原语,shadcn-svelte 负责在其上做样式与 API 封装。

安装

文档提供了两种安装路径:CLI 自动安装 与 手动安装。

方式一:CLI 安装(推荐)

在项目根目录直接运行:

npx shadcn-svelte@latest add scroll-area

该命令会自动将 ScrollArea 组件写入项目的 $lib/components/ui/scroll-area/ 目录,并完成依赖解析与安装。

方式二:手动安装

  1. 安装底层依赖 bits-ui:
npm install bits-ui
# 或使用 pnpm / bun / yarn 等任意包管理器
  1. 将 scroll-area.svelte、scroll-area-scrollbar.svelte 与 index.ts 三个文件复制到你的 src/lib/components/ui/scroll-area/ 目录,并确保项目已配置好 $lib 别名与 cn 工具函数。

需要说明的是,组件源码依赖样式文件中定义的 cn-scroll-area-* 系列类(详见后文"样式实现"一节),手动安装时请同步引入对应主题的样式文件。

基本用法

首先在脚本中引入组件:

<script lang="ts">
  import { ScrollArea } from "$lib/components/ui/scroll-area/index.js";
</script>

然后使用 ScrollArea 包裹需要滚动的长内容,并通过 Tailwind 类控制尺寸与外观:

<ScrollArea class="h-[200px] w-[350px] rounded-md border p-4">
  Jokester began sneaking into the castle in the middle of the night and
  leaving jokes all over the place: under the king's pillow, in his soup, even
  in the royal toilet. The king was furious, but he couldn't seem to stop
  Jokester. And then, one day, the people of the kingdom discovered that the
  jokes left by Jokester were so funny that they couldn't help but laugh. And
  once they started laughing, they couldn't stop.
</ScrollArea>

要点说明:

  • 必须显式给容器设定高度 / 宽度(如 h-[200px] w-[350px]),滚动区是相对于容器尺寸发生的,未设尺寸时内容会被完整撑开而不会出现滚动;
  • rounded-md border 等样式通过 class 透传到根元素,根元素默认只有 relative 定位,其余外观完全由你通过 class 定制(见源码 scroll-area.svelte);
  • 内容插槽通过 {@render children?.()} 渲染,这也是 Svelte 5 的标准写法。

组件 API 详解

从 scroll-area.svelte 的 $props() 定义可以看出,ScrollArea 在透传 bits-ui 的 ScrollAreaPrimitive.RootProps 之外,还暴露了以下自定义属性:

属性 类型 默认值 说明
orientation "vertical" | "horizontal" | "both" "vertical" 滚动方向。"both" 时同时渲染纵、横两条滚动条
class string 无 追加到根容器的 Tailwind 类,与内置 relative 合并
scrollbarXClasses string "" 额外附加到横向滚动条上的类
scrollbarYClasses string "" 额外附加到纵向滚动条上的类
ref 绑定值 null 根元素引用,使用 $bindable 声明,可通过 bind:ref 获取
viewportRef HTMLElement | null null Viewport(实际滚动容器)的引用,同样支持 bind:

orientation 的底层行为

从源码 scroll-area.svelte 可以看到,orientation 直接决定渲染哪些滚动条:

{#if orientation === "vertical" || orientation === "both"}
  <Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === "horizontal" || orientation === "both"}
  <Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}

即:默认 "vertical" 只渲染纵向滚动条;"horizontal" 只渲染横向滚动条;"both" 则两条都渲染。无论哪种方向,内部都会渲染一个 ScrollAreaPrimitive.Corner 用于填充两条滚动条交汇处的直角空隙。

Scrollbar 子组件

独立导出的 ScrollAreaScrollbar(源码见 scroll-area-scrollbar.svelte)接收 orientation 与 class,内部渲染一个可拖拽的 Thumb(滑块)。默认滑块类为 cn-scroll-area-thumb relative flex-1 bg-border,即使用主题的 border 色作为滑块底色,滑块粗细由外层滚动条类控制。ScrollArea 根组件会按 orientation 自动为你装配好 Scrollbar,因此常规使用无需手动引入它;当你需要深度定制滚动条(如添加自定义的轨道内容)时,可基于 ScrollAreaScrollbar 自行组合。

示例:横向滚动

官方文档给出的横向滚动示例,核心就是设置 orientation 属性为 "horizontal":

<ScrollArea orientation="horizontal" class="...">
  <!-- 横向排列的内容 -->
</ScrollArea>

仓库中的完整示例见 docs/src/lib/registry/examples/scroll-area-horizontal.svelte:它展示了一个横向滚动的图片画廊,关键写法是外层容器加 whitespace-nowrap 防止换行,内部用 flex w-max 让内容宽度由子元素撑开:

<ScrollArea class="w-96 rounded-md border whitespace-nowrap" orientation="horizontal">
  <div class="flex w-max space-x-4 p-4">
    {#each works as artwork (artwork.artist)}
      <figure class="shrink-0">
        <!-- 图片与标题 -->
      </figure>
    {/each}
  </div>
</ScrollArea>

两个实操要点:

  1. 横向内容一定要用 flex + w-max(或给每个子项 shrink-0),否则子元素会被压缩换行,无法触发横向溢出滚动;
  2. 容器自身只需限定宽度,无需给高度设固定值,高度由内容自然决定。

更多示例:垂直列表与双向滚动

除文档示例外,仓库还提供了两类典型用法,均位于 docs/src/lib/registry/examples/:

垂直滚动列表(scroll-area-demo)

docs/src/lib/registry/examples/scroll-area-demo.svelte 演示了"版本标签列表"场景:用 Array.from({ length: 50 }) 生成 50 个 v1.2.0-beta.* 标签,容器设为 h-72 w-48 rounded-md border,条目之间用 Separator 分隔。这种"固定高度 + 长内容"的组合是滚动区域最常见的形态:

<ScrollArea class="h-72 w-48 rounded-md border">
  <div class="p-4">
    <h4 class="mb-4 text-sm leading-none font-medium">Tags</h4>
    {#each tags as tag (tag)}
      <div class="text-sm">{tag}</div>
      <Separator class="my-2" />
    {/each}
  </div>
</ScrollArea>

双向滚动(scroll-area-both)

docs/src/lib/registry/examples/scroll-area-both.svelte 演示 orientation="both":容器固定为 h-[200px] w-[350px],而内部内容宽度被刻意设置为 w-[400px](超过容器宽度),因此纵、横两个方向都会出现滚动条。这是"内容在横纵两个维度都溢出"时的标准解法,ScrollArea 会自动同时渲染纵、横 Scrollbar 与 Corner:

<ScrollArea class="h-[200px] w-[350px] rounded-md border p-4" orientation="both">
  <div class="w-[400px]">
    <!-- 长文本内容 -->
  </div>
</ScrollArea>

样式实现与主题适配

Scroll Area 的外观样式并没有全部内联在组件里,而是通过样式文件中的 cn-scroll-area-* 工具类实现,便于各主题(vega / nova / luma / lyra / sera / rhea / maia / mira)复用与覆盖。以 docs/src/lib/registry/styles/style-vega.css 为例:

/* MARK: Scroll Area */
.cn-scroll-area-scrollbar {
  @apply data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent;
}

.cn-scroll-area-thumb {
  @apply rounded-full;
}

可以看到:

  • 滚动条通过 data-horizontal / data-vertical 属性区分方向:纵向滚动条宽 w-2.5 且占满高度,横向滚动条高 h-2.5 并转为纵向排布(flex-col),轨道的 border-t / border-l 使用透明边框占位,避免布局抖动;
  • 滑块 cn-scroll-area-thumb 统一为 rounded-full 圆角,配合组件源码中的 bg-border 底色(scroll-area-scrollbar.svelte)呈现圆润的胶囊形滑块;
  • 组件根容器的 Viewport 还带有 focus-visible:ring 焦点环样式(scroll-area.svelte),保证键盘无障碍焦点可见。

如果你需要给滚动条追加自定义类(如改变粗细、颜色),优先使用 scrollbarXClasses / scrollbarYClasses 属性,而不是去改组件源码,这样能保持与后续主题升级的兼容性。

源码结构小结

文件(仓库相对路径) 职责
docs/content/components/scroll-area.md 官方组件文档(安装、用法、示例)
docs/src/lib/registry/ui/scroll-area/scroll-area.svelte 根容器:装配 Viewport / Scrollbar / Corner
docs/src/lib/registry/ui/scroll-area/scroll-area-scrollbar.svelte 滚动条与滑块(Thumb)实现
docs/src/lib/registry/ui/scroll-area/index.ts 导出 ScrollArea、ScrollAreaScrollbar
docs/src/lib/registry/examples/scroll-area-demo.svelte 垂直标签列表示例
docs/src/lib/registry/examples/scroll-area-horizontal.svelte 横向图片画廊示例
docs/src/lib/registry/examples/scroll-area-both.svelte 双向滚动示例
docs/src/lib/registry/styles/style-vega.css cn-scroll-area-* 主题样式(各主题均含)

结语

Scroll Area 是 shadcn-svelte 中"小而精"的典型组件:它在 bits-ui 无障碍滚动原语之上,通过 orientation 一个核心属性即可覆盖垂直、横向、双向三种滚动形态,配合 cn-scroll-area-* 主题类实现跨浏览器统一的自定义滚动条外观。实际接入时只需记住三条原则:容器尺寸必须显式设定、横向内容用 flex w-max 撑开、定制滚动条优先使用 scrollbarXClasses / scrollbarYClasses。掌握这些细节后,你便可以在自己的 Svelte 5 项目中快速复刻文档站同款的长列表与画廊滚动效果。

登录后查看全文
shadcn-svelte