Scroll Area 📅 发布时间:2026/9/15 18:33:37 👁 浏览次数: Scroll Area【免费下载链接】base-uiUnstyled UI components for building accessible web apps and design systems. From the creators of Radix, Floating UI, and Material UI.项目地址: https://gitcode.com/GitHub_Trending/ba/base-uiBase UIbase-ui/react中的ScrollArea是一个原生滚动容器 自定义滚动条的复合组件它保留浏览器原生滚动的性能与可访问性同时通过组件化的 Root / Viewport / Content / Scrollbar / Thumb / Corner 六件套和丰富的状态属性overflow、scrolling、hovering 等让你可以完全自定义滚动条外观与边缘渐变遮罩等交互效果。本文基于仓库中的官方 API 参考文档 types.md/react/components/scroll-area/types.md)逐部件拆解其 Props、Data Attributes、CSS Variables 与 State 类型并结合 packages/react/src/scroll-area 下的源码实现说明每个参数在底层是如何生效的。一、组件全貌Anatomy 与六个 Part先看官方文档给出的标准结构见 page.mdx/react/components/scroll-area/page.mdx)import { ScrollArea } from base-ui/react/scroll-area; ScrollArea.Root ScrollArea.Viewport ScrollArea.Content / /ScrollArea.Viewport ScrollArea.Scrollbar ScrollArea.Thumb / /ScrollArea.Scrollbar ScrollArea.Corner / /ScrollArea.Root;各 Part 的职责一句话概括Part渲染元素职责ScrollArea.Rootdiv组织所有部件的容器持有共享状态与滚动上下文ScrollArea.Viewportdiv真正可滚动的容器内部强制overflow: scrollScrollArea.Contentdiv内容的容器负责让内容撑开视口ScrollArea.Scrollbardiv垂直或水平滚动条轨道ScrollArea.Thumbdiv滚动条上可拖拽的滑块指示当前滚动位置ScrollArea.Cornerdiv垂直与水平滚动条交叉处的小矩形从源码看这些 Part 通过 index.parts.ts 以命名空间方式导出Root、Viewport、Scrollbar、Content、Thumb、Corner类型则由 index.ts 统一 re-export。Root 通过 ScrollAreaRootContext.ts 向下传递 ref、滚动状态、溢出阈值等上下文Viewport 再通过 ScrollAreaViewportContext.ts 提供computeThumbPosition形成Root 测量、Viewport 滚动、Scrollbar/Thumb 呈现的协作链路。二、Root全局状态与溢出边缘的枢纽Root将滚动区的所有部分组合在一起渲染一个div元素。它的核心能力是测量内容溢出情况并驱动所有子部件的状态属性。Root PropsProp类型默认值说明overflowEdgeThresholdnumber \| Partial{ xStart; xEnd; yStart; yEnd }0溢出边缘属性被应用前必须超过的像素阈值。可传一个数字统一作用于四条边也可传对象逐边配置classNamestring \| ((state) string \| undefined)-应用于元素的 CSS 类或基于组件状态返回类的函数styleReact.CSSProperties \| ((state) React.CSSProperties \| undefined)-应用于元素的样式或基于组件状态返回样式对象的函数renderReactElement \| ((props, state) ReactElement)-用不同标签或另一个组件替换默认 HTML 元素可传元素或返回元素的函数其中overflowEdgeThreshold是 Root 独有的业务属性。在 ScrollAreaRoot.tsx 中normalizeOverflowEdgeThreshold会把数字展开为四条边的配置并统一用Math.max(0, ...)将负数收敛为 0随后computeThumbPosition在每次滚动时用从各边缘的滚动距离与该阈值比较源码位于 ScrollAreaViewport.tsxconst nextOverflowEdges { xStart: !scrollbarXHidden scrollLeftFromStart overflowEdgeThreshold.xStart, xEnd: !scrollbarXHidden scrollLeftFromEnd overflowEdgeThreshold.xEnd, yStart: !scrollbarYHidden scrollTopFromStart overflowEdgeThreshold.yStart, yEnd: !scrollbarYHidden scrollTopFromEnd overflowEdgeThreshold.yEnd, };也就是说只有当滚动距离严格大于阈值时对应的溢出边缘属性才会出现。这在实现滚动 N 像素后才显示边缘阴影的交互时非常有用。Root Data Attributes以下属性由 stateAttributes.ts 中的映射自动应用到元素上布尔值为true时属性以空字符串值出现属性说明data-has-overflow-x内容宽度超过视口宽度时出现data-has-overflow-y内容高度超过视口高度时出现data-overflow-x-end水平方向末端存在溢出时出现data-overflow-x-start水平方向起始端存在溢出时出现data-overflow-y-end垂直方向末端存在溢出时出现data-overflow-y-start垂直方向起始端存在溢出时出现data-scrolling用户在滚动区内部滚动时出现注意data-has-overflow-x在源码中等价于横向滚动条未隐藏hasOverflowX: !hiddenState.x见 ScrollAreaRoot.tsx隐藏状态由 getHiddenState 通过clientHeight scrollHeight判断得出。data-scrolling背后是 500ms 的超时机制常量SCROLL_TIMEOUT 500定义在 constants.ts 中滚动事件触发后500ms 无滚动即移除该属性。这在实现滚动时显示、停止后隐藏滚动条的经典效果时非常实用。Root CSS Variables变量类型说明--scroll-area-corner-heightnumber滚动区角落corner的高度--scroll-area-corner-widthnumber滚动区角落corner的宽度这两个变量由 Root 根据测量到的 corner 实际尺寸写入内联样式见 ScrollAreaRoot.tsx常被 Scrollbar 用来让轨道避开 corner 区域例如垂直滚动条bottom: var(--scroll-area-corner-height)。Root.Props 与 Root.StateScrollArea.Root.PropsRoot 属性的 re-export等价于ScrollAreaRootProps。ScrollArea.Root.State即ScrollAreaRootStatetype ScrollAreaRootState { /** 是否正在滚动 */ scrolling: boolean; /** 是否存在水平溢出 */ hasOverflowX: boolean; /** 是否存在垂直溢出 */ hasOverflowY: boolean; /** 水平轴 inline start 侧是否存在溢出 */ overflowXStart: boolean; /** 水平轴 inline end 侧是否存在溢出 */ overflowXEnd: boolean; /** block start 侧是否存在溢出 */ overflowYStart: boolean; /** block end 侧是否存在溢出 */ overflowYEnd: boolean; /** 滚动条 corner 是否隐藏 */ cornerHidden: boolean; };State会作为className/style函数的入参例如ScrollArea.Root className{(state) (state.hasOverflowY ? styles.withScrollbar : styles.without)} /三、Viewport真正的滚动容器Viewport是滚动区实际可滚动的容器渲染div。源码中它被注入style: { overflow: scroll }见 ScrollAreaViewport.tsx并做了几件关键的事无障碍焦点管理当两个方向都没有溢出时tabIndex为-1不进入 Tab 顺序一旦可滚动则变为0保证键盘用户能聚焦滚动区域对应 ScrollAreaViewport.tsx。rolepresentation从辅助技术视角隐藏容器本身。ResizeObserver 监听视口尺寸或内容尺寸变化时自动重算 thumb 位置动画结束后animation.finished也会重新计算避免 transform 动画干扰 thumb 几何。RTL 支持横向滚动计算在direction rtl时对scrollLeft取反处理源码见 ScrollAreaViewport.tsx。Viewport CSS Variables滚动渐变遮罩的关键Viewport 独有的四个 CSS 变量表示从各边缘到当前滚动位置的像素距离变量类型说明--scroll-area-overflow-x-startnumber距水平起始边缘的距离px--scroll-area-overflow-x-endnumber距水平末端边缘的距离px--scroll-area-overflow-y-startnumber距垂直起始边缘的距离px--scroll-area-overflow-y-endnumber距垂直末端边缘的距离px这些变量在每次滚动帧内由computeThumbPosition写入见 ScrollAreaViewport.tsx。官方文档提供了用它们驱动 CSS mask 实现滚动渐隐的经典用法page.mdx/react/components/scroll-area/page.mdx#L46-L75).Viewport { mask-image: linear-gradient( to bottom, transparent 0, black min(40px, var(--scroll-area-overflow-y-start)), black calc(100% - min(40px, var(--scroll-area-overflow-y-end, 40px))), transparent 100% ); mask-repeat: no-repeat; }当 fade 直接应用在 Viewport 自身时变量可以直接使用但为了性能组件禁用了这些变量的继承源码 removeCSSVariableInheritance 通过CSS.registerProperty({ inherits: false })实现WebKit/Safari 下自动跳过该优化因此子元素必须显式inherit才可用.Child { --scroll-area-overflow-y-start: inherit; --scroll-area-overflow-y-end: inherit; }对于 SSR还可以在var()中提供兜底值让遮罩在水合hydrate前就能显示var(--scroll-area-overflow-y-end, 40px);一个完整的可运行示例见 demos/scroll-fade/css-modules/index.tsx/react/components/scroll-area/demos/scroll-fade/css-modules/index.tsx) 与对应的 index.module.css/react/components/scroll-area/demos/scroll-fade/css-modules/index.module.css)。Viewport.Props 与 Viewport.StateProps 只有通用的className/style/render类型与 Root 相同基于ScrollArea.Viewport.State。Viewport.State与Root.State完全一致源码ScrollAreaViewportState extends ScrollAreaRootState。四、Content内容容器Content是滚动区内容的容器渲染div。它主要负责撑开内容宽度内置minWidth: fit-content保证横向内容不会被压缩见 ScrollAreaContent.tsx。内容尺寸变化时重算通过自己的 ResizeObserver 调用computeThumbPosition并处理内容在视口首次测量之后才挂载的边界情况对应测试见 ScrollAreaRoot.test.tsx内容后挂载时溢出状态与 tabIndex 会随之更新。Props 同样只有className/style/renderContent.State与Root.State一致。五、Scrollbar 与 Thumb可定制的滚动条Scrollbar PropsProp类型默认值说明orientationvertical \| horizontalvertical控制垂直或水平滚动keepMountedbooleanfalse视口不可滚动时是否将元素保留在 DOM 中className/style/render同前-通用外观与替换能力关键实现细节见 ScrollAreaScrollbar.tsxkeepMounted为false默认时对应方向不可滚动hiddenState.x/hiddenState.y为真则返回null直接卸载适合需要过渡动画的场景若设为true元素常驻 DOM配合data-*属性做显隐动画。轨道上的滚轮事件在滚动条轨道上滚动时组件会接管并把增量应用到scrollTop/scrollLeft到达边缘后放行事件不preventDefault让滚轮事件正常冒泡到父级页面。轨道点击跳转点击轨道非 Thumb 区域会按比例跳转滚动位置并支持 RTLscrollLeft为负区间。滚动条对辅助技术隐藏aria-hiddentrue。Scrollbar Data Attributes属性类型说明data-orientationhorizontal \| vertical滚动条方向data-hovering-指针悬停在滚动区上时出现data-scrolling-用户滚动时出现data-has-overflow-x/data-has-overflow-y-对应方向存在溢出时出现data-overflow-x/y-start/data-overflow-x/y-end-各边缘存在溢出时出现data-hovering是 Scrollbar 独有的状态源码在 ScrollAreaRoot.tsx 中通过pointerenter/pointermove判断指针是否位于 Root 内部contains(rootRef.current, event.target)触摸touch模式下不触发 hover。示例中滚动条默认透明、data-hovering或data-scrolling时显现见 index.module.css/react/components/scroll-area/demos/scroll-fade/css-modules/index.module.css#L60-L83)。Scrollbar CSS Variables变量类型说明--scroll-area-thumb-heightnumber滚动条滑块的高度--scroll-area-thumb-widthnumber滚动条滑块的宽度这两个变量由 Root 测量后写入thumbSize状态Thumb 通过var(--scroll-area-thumb-height)/var(--scroll-area-thumb-width)消费实现滑块尺寸随内容比例自动变化。Scrollbar.Statetype ScrollAreaScrollbarState { /** 滚动区是否被悬停 */ hovering: boolean; /** 滚动区是否正在滚动 */ scrolling: boolean; /** 滚动条方向 */ orientation: vertical | horizontal; /** 是否水平溢出 */ hasOverflowX: boolean; /** 是否垂直溢出 */ hasOverflowY: boolean; overflowXStart: boolean; overflowXEnd: boolean; overflowYStart: boolean; overflowYEnd: boolean; /** 滚动条 corner 是否隐藏 */ cornerHidden: boolean; };注意scrolling是按方向区分的垂直滚动条只响应纵向滚动、水平滚动条只响应横向滚动vertical ? scrollingY : scrollingX见 ScrollAreaScrollbar.tsx。ThumbThumb 是可拖拽的滑块渲染div。它没有额外 Props仅className/style/render尺寸完全交给 CSS 变量驱动。其数据属性属性类型说明data-orientationhorizontal \| vertical滑块方向来自父级 Scrollbar 的上下文data-scrolling-用户滚动时出现拖拽交互的底层实现在 ScrollAreaRoot.tsx按下时记录指针位置与视口滚动起点移动时按滑块位移 / 轨道可移动距离的比例换算滚动量并做了一系列健壮性处理——多指同时按下时忽略后到的指针、pointercancel时释放捕获、拖拽期间临时禁用 CSS scroll snapdisableViewportSnap松手后恢复避免滑块拖拽被吸附点打断。滑块有 16px 的最小尺寸MIN_THUMB_SIZE 16见 constants.ts轨道过短时会保护性地将滚动比例收敛为 0。Thumb.State相对精简type ScrollAreaThumbState { /** 是否正在滚动 */ scrolling: boolean; /** 组件方向 */ orientation: horizontal | vertical; };六、Corner交叉处的补丁Corner是垂直与水平滚动条交叉处的小矩形区域渲染div用于防止两个滚动条相互交叠官方示例Both scrollbars即基于此见 page.mdx/react/components/scroll-area/page.mdx#L34-L40)。当hiddenState.corner为真任意一个方向无溢出时Corner 返回null见 ScrollAreaCorner.tsx。元素带aria-hiddentrue定位为position: absolute; bottom: 0; insetInlineEnd: 0尺寸由 Root 的cornerSize状态提供。Corner.State是空对象{}Corner.Props仅className/style/render。七、附加类型与导出约定Additional Types文档还暴露了几个内部辅助类型type Coords { x: number; y: number }; type HiddenState { x: boolean; y: boolean; corner: boolean }; type OverflowEdges { xStart: boolean; xEnd: boolean; yStart: boolean; yEnd: boolean }; type Size { width: number; height: number };它们分别对应滚动坐标、滚动条隐藏状态、溢出边缘布尔集合与尺寸测量结果默认值定义在 ScrollAreaRoot.tsx。Export Groups 与 Canonical Types命名空间导出与扁平类型名之间的对应关系使用时注意命名空间已导入时优先用 Canonical 名否则用 AliasScrollArea.Root→ScrollArea.Root、ScrollArea.Root.State、ScrollArea.Root.PropsScrollArea.Viewport→ScrollArea.Viewport、ScrollArea.Viewport.State、ScrollArea.Viewport.PropsScrollArea.Scrollbar→ScrollArea.Scrollbar、ScrollArea.Scrollbar.State、ScrollArea.Scrollbar.PropsScrollArea.Content→ScrollArea.Content、ScrollArea.Content.State、ScrollArea.Content.PropsScrollArea.Thumb→ScrollArea.Thumb、ScrollArea.Thumb.State、ScrollArea.Thumb.PropsScrollArea.Corner→ScrollArea.Corner、ScrollArea.Corner.State、ScrollArea.Corner.PropsDefault扁平导出→HiddenState、OverflowEdges、Size、Coords及全部ScrollAreaXxxState/PropsCanonical ↔ Alias 映射示例ScrollArea.Root.State↔ScrollAreaRootStateScrollArea.Scrollbar.Props↔ScrollAreaScrollbarProps其余部件同理。八、实战组合与 Tabs 集成官方文档还给出了一个高级组合当 Tab 列表本身需要视口溢出变量做遮罩渐隐时可以用Tabs.List的render属性直接渲染ScrollArea.Viewport让遮罩逻辑与接收滚动状态的元素保持同一节点见 page.mdx/react/components/scroll-area/page.mdx#L79-L94)Tabs.Root defaultValueoverview ScrollArea.Root Tabs.List render{ScrollArea.Viewport /} Tabs.Tab valueoverviewOverview/Tabs.Tab Tabs.Indicator / /Tabs.List /ScrollArea.Root Tabs.Panel valueoverview.../Tabs.Panel /Tabs.Root这正是render属性的用武之地它允许任何部件寄生到另一个组件的元素上保持语义结构不变的同时复用滚动能力。结语ScrollArea的设计思路非常清晰根组件负责状态测量Viewport 负责原生滚动Scrollbar/Thumb/Corner 只负责呈现与交互全部通过data-*属性与 CSS 变量向外暴露状态。因此它既能保持原生滚动的性能与可达性又给了样式层几乎无限的自由度——无论是自定义滚动条、边缘渐隐遮罩还是与 Tabs 等组件组合都能在不触碰内部实现的情况下完成。动手实践时可以直接参考 demos/scroll-fade/react/components/scroll-area/demos/scroll-fade) 下的 CSS Modules 与 Tailwind 两个版本示例并结合 ScrollAreaRoot.test.tsx 中关于溢出属性、RTL、后挂载内容的测试用例验证你对每个状态属性触发时机的理解。【免费下载链接】base-uiUnstyled UI components for building accessible web apps and design systems. From the creators of Radix, Floating UI, and Material UI.项目地址: https://gitcode.com/GitHub_Trending/ba/base-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考