Scroll View 滚动视图

对齐 WinUI 3 ScrollView 的容器控件:内容超出视口时滚动、平移、缩放,滚动条为 WinUI 3 的 overlay 样式(2px 细滑块,指针进入容器即显示;指针移到滚动条上时滑块加粗为 6px、药丸轨道连同两端步进按钮展开,离开后收起,2s 无交互整体淡出)。

基础用法(垂直)

默认 content-orientation="vertical",滚轮 / 触控平移 / 拖动滚动条均可滚动。

第 1 行内容 · Windows 11 原生滚动体验
第 2 行内容 · Windows 11 原生滚动体验
第 3 行内容 · Windows 11 原生滚动体验
第 4 行内容 · Windows 11 原生滚动体验
第 5 行内容 · Windows 11 原生滚动体验
第 6 行内容 · Windows 11 原生滚动体验
第 7 行内容 · Windows 11 原生滚动体验
第 8 行内容 · Windows 11 原生滚动体验
第 9 行内容 · Windows 11 原生滚动体验
第 10 行内容 · Windows 11 原生滚动体验
第 11 行内容 · Windows 11 原生滚动体验
第 12 行内容 · Windows 11 原生滚动体验
第 13 行内容 · Windows 11 原生滚动体验
第 14 行内容 · Windows 11 原生滚动体验
第 15 行内容 · Windows 11 原生滚动体验
第 16 行内容 · Windows 11 原生滚动体验
第 17 行内容 · Windows 11 原生滚动体验
第 18 行内容 · Windows 11 原生滚动体验
基础用法(垂直)

横向滚动

content-orientation="horizontal",内容高度约束到视口,宽度自由增长。Shift + 滚轮或触控横向滑动。

卡片 1
卡片 2
卡片 3
卡片 4
卡片 5
卡片 6
卡片 7
卡片 8
卡片 9
卡片 10
横向滚动

双向滚动

content-orientation="both",内容在横向与纵向均不受约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
双向滚动

嵌套滚动

内层 ScrollView 位于外层内容之中。鼠标滚轮归属对齐 WinUI 3:指针停在内层上时由内层独占滚轮, 即使内层已滚到极限,外层也不会跟着滚动;把指针移到内层之外的外层内容上,滚轮才交给外层(触控 / 笔由指针捕获独占)。

外层内容 1 · 指针在此处滚轮才会滚动外层
外层内容 2 · 指针在此处滚轮才会滚动外层
外层内容 3 · 指针在此处滚轮才会滚动外层
外层内容 4 · 指针在此处滚轮才会滚动外层
内层 A · 第 1 行(滚到底后外层不动)
内层 A · 第 2 行(滚到底后外层不动)
内层 A · 第 3 行(滚到底后外层不动)
内层 A · 第 4 行(滚到底后外层不动)
内层 A · 第 5 行(滚到底后外层不动)
内层 A · 第 6 行(滚到底后外层不动)
内层 A · 第 7 行(滚到底后外层不动)
内层 A · 第 8 行(滚到底后外层不动)
内层 A · 第 9 行(滚到底后外层不动)
内层 A · 第 10 行(滚到底后外层不动)
内层 B · 第 1 行(滚到底后外层不动)
内层 B · 第 2 行(滚到底后外层不动)
内层 B · 第 3 行(滚到底后外层不动)
内层 B · 第 4 行(滚到底后外层不动)
内层 B · 第 5 行(滚到底后外层不动)
内层 B · 第 6 行(滚到底后外层不动)
内层 B · 第 7 行(滚到底后外层不动)
内层 B · 第 8 行(滚到底后外层不动)
内层 B · 第 9 行(滚到底后外层不动)
内层 B · 第 10 行(滚到底后外层不动)
外层尾部内容 1
外层尾部内容 2
外层尾部内容 3
外层尾部内容 4
外层尾部内容 5
外层尾部内容 6
嵌套滚动

焦点滚动

焦点(Tab 或程序化 focus())进入内容内的元素时,元素自动滚进视口 —— 对齐 WinUI BringIntoViewOnFocusChange(缺省开启),键盘用户不会把焦点留在视口之外;元素已在视口内时不产生动画。 该次滚动同样触发 bring-into-view 事件,可在回调中取消或改写目标偏移。

容器获得焦点(或焦点在内容内)时,方向键、PageUp / PageDown、Home / End 滚动视图;命中 input / textarea / select / contenteditable 时按键交还给输入控件。

缩放

zoom-mode="enabled":Ctrl / Cmd + 滚轮或双指捏合缩放,缩放中心为指针位置。对齐 WinUI 的 min-zoom-factor / max-zoom-factor 约束。

Ctrl + 滚轮缩放
缩放

滚动条可见性

vertical-scroll-bar-visibility 支持 auto(默认,overlay)/ visible(常驻)/ hidden(仍可滚动)。

auto(默认,overlay)

visible(常驻)

hidden(仍可滚动)

滚动条可见性

程序化 API

对齐 WinUI 方法:scrollTo / scrollBy / zoomTo / zoomBy / addScrollVelocity / addZoomVelocity / bringIntoView(支持动画与 correlation ID), 以及只读属性 horizontalOffset / verticalOffset / zoomFactor / extentWidth / extentHeight / viewportWidth / viewportHeight / scrollableWidth / scrollableHeight / state / currentAnchor。完整签名见下方「方法」与「只读属性」两张表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
(交互后显示)
程序化 API

事件

view-changed / extent-changed / state-changed / scroll-animation-starting / scroll-completed / zoom-animation-starting / zoom-completed / anchor-requested / bring-into-view, 载荷见下方「事件」表。这些是组件事件(@event);供页面自动化使用的 DOM 命令事件 见「Agent / 自动化访问」, 事件名带 fluere: 前缀,在根元素上派发。

事件

Agent / 自动化访问

本组件的偏移由内容 transform 表达、presenter 是 overflow: clip(不是原生滚动容器), 因此浏览器与自动化工具的原生滚动语义在这里不成立:scrollTop 恒为 0, scrollBy / scrollIntoView / scrollIntoViewIfNeeded 都找不到可滚动祖先 (Playwright 点击视口外元素前的自动滚动同样失效,这也是 style-spec §7 记录的那条坑)。组件为此提供两条 DOM 通路, 都不需要接触组件实例:

  • 只读反射:根元素始终带一组状态属性,用来判断「动没动、还剩多少」;
  • 命令事件:在根元素上派发 fluere:* 事件即可驱动滚动,落地后收到回执。

只读反射属性

属性(Attribute)说明
data-scroll-x当前水平偏移(像素,取整)
data-scroll-y当前垂直偏移(像素,取整)
data-scroll-max-x可滚动宽度上限(内容 × 缩放 − 视口,取整)
data-scroll-max-y可滚动高度上限,取整
data-zoom-factor当前缩放系数
data-scroll-state'idle' | 'interaction' | 'inertia' | 'animation'

取整后可安全地做相等比较来判断「到底了没有」(data-scroll-y === data-scroll-max-y)。 这些属性在偏移变化时逐帧重写,不要在 CSS 里匹配它们(否则动画期间每帧都会触发样式重算; 仓库有契约测试守着这条约定)。

命令事件

在组件根元素(.fui-scrollview)上派发,bubbles: true,因此也可以挂在页面级监听。

事件(DOM 事件)载荷(event.detail)说明
fluere:scroll-to{ x?, y?, animationMode? }滚到绝对偏移;缺省分量保持不变
fluere:scroll-by{ x?, y?, animationMode? }按增量滚动;缺省分量按 0 处理
fluere:bring-into-view{ element?, selector?, margin? }把内容区内的元素滚入视口(element 优先)
fluere:scroll-settled{ x, y, zoomFactor }(出站)命令落地后由组件派发,用于等待稳定帧

载荷非法(非有限数、目标不可解析)时是空操作,不抛错;bring-into-view 只接受内容区内的元素。 animationMode 传 'disabled' 可跳过动画,适合截图前的确定性定位; agentCommands 设为 false 可关闭命令事件(只读反射保留)。

const el = document.querySelector('.fui-scrollview')

// 1. 先读状态:还需要滚多少?
const remaining = Number(el.dataset.scrollMaxY) - Number(el.dataset.scrollY)

// 2. 派发命令(bubbles 让页面级监听也能收到)
el.dispatchEvent(
  new CustomEvent('fluere:scroll-by', { detail: { y: remaining, animationMode: 'disabled' } }),
)

// 3. 等回执再截图 / 断言
await new Promise((resolve) =>
  el.addEventListener('fluere:scroll-settled', resolve, { once: true }),
)
第 1 行
第 2 行
第 3 行
第 4 行
第 5 行
第 6 行
第 7 行
第 8 行
第 9 行
第 10 行
第 11 行
第 12 行
第 13 行
第 14 行
第 15 行
第 16 行
第 17 行
第 18 行
第 19 行
第 20 行
第 21 行
第 22 行
第 23 行
第 24 行
第 25 行
第 26 行
第 27 行
第 28 行
第 29 行
第 30 行
(读取 data-* 中…)
Agent 交互面

仍不适用的原生语义:组件不是原生滚动容器,因此 el.scrollTop、el.scrollBy()、 el.scrollIntoView()、CDP DOM.scrollIntoViewIfNeeded、浏览器「在页面中查找」与 #:~:text= 文本片段定位依旧不生效,请用上面的命令事件替代。 另有一条既有通路:焦点进入内容区元素时组件会按 BringIntoViewOnFocusChange 自动把它滚入视口 (对齐 WinUI,见「方法」表的 bringIntoView)。

API

属性(Props)

属性(Props)类型默认说明
contentOrientation'vertical' | 'horizontal' | 'none' | 'both''vertical'内容布局方向,决定内容如何受视口约束
horizontalScrollMode'enabled' | 'disabled''enabled'是否允许用户水平滚动
verticalScrollMode'enabled' | 'disabled''enabled'是否允许用户垂直滚动
horizontalScrollBarVisibility'auto' | 'visible' | 'hidden''auto'水平滚动条展示策略(auto 为 overlay,指针进入容器才显示)
verticalScrollBarVisibility'auto' | 'visible' | 'hidden''auto'垂直滚动条展示策略
horizontalScrollChainMode'auto' | 'always' | 'never''auto'水平滚动的链式传递;见下方「滚轮归属」说明
verticalScrollChainMode'auto' | 'always' | 'never''auto'垂直滚动的链式传递,语义同上
horizontalScrollRailMode'enabled' | 'disabled''enabled'水平触控平移导轨
verticalScrollRailMode'enabled' | 'disabled''enabled'垂直触控平移导轨
zoomMode'enabled' | 'disabled''disabled'是否允许用户缩放
zoomChainMode'auto' | 'always' | 'never''auto'缩放的链式传递
ignoredInputKindsScrollingInputKinds | ScrollingInputKinds[][]需要忽略的输入种类,可传单个值或数组;命中则对该输入不响应
minZoomFactornumber0.1最小缩放系数
maxZoomFactornumber10最大缩放系数
horizontalAnchorRationumberNaN水平锚点比例(0~1),NaN 表示不启用水平锚定;0 让内容左缘贴住视口左缘、1 让右缘贴住右缘
verticalAnchorRationumberNaN垂直锚点比例(0~1),NaN 表示不启用垂直锚定
backgroundstring—内容区背景色
tabIndexnumber0键盘可聚焦(Arrow / PageUp / Home 等方向键滚动)
labelstring—可访问名;设置后根元素带 role="region",供屏幕阅读器与无障碍树型 Agent 识别该滚动区域
agentCommandsbooleantrue是否响应 fluere:* DOM 命令事件(见「Agent / 自动化访问」);只读状态反射始终保留

事件(Events)

事件(Events)载荷说明
view-changed—视口偏移或缩放发生变化(交互过程中连续触发)
extent-changed—内容区尺寸发生变化
state-changed'idle' | 'interaction' | 'inertia' | 'animation'交互状态变化
scroll-animation-startingScrollingScrollAnimationStartingEventArgs滚动动画即将开始,可改写目标偏移
scroll-completedScrollingScrollCompletedEventArgs一次滚动结束(含 correlationId)
zoom-animation-startingScrollingZoomAnimationStartingEventArgs缩放动画即将开始
zoom-completedScrollingZoomCompletedEventArgs一次缩放结束(含 correlationId)
anchor-requestedScrollingAnchorRequestedEventArgs请求锚点(可返回自定义锚点元素)
bring-into-viewScrollingBringingIntoViewEventArgs即将把元素滚入视口,可改写目标偏移

方法(Methods)

通过模板 ref 调用;都返回本次操作的 correlationId,用于与 scroll-completed / zoom-completed 的载荷对应。

方法(Methods)签名说明
scrollTo(horizontalOffset: number, verticalOffset: number, options?: ScrollingScrollOptions) => number滚动到绝对偏移
scrollBy(horizontalOffsetDelta: number, verticalOffsetDelta: number, options?: ScrollingScrollOptions) => number按增量滚动
zoomTo(zoom: number, centerPoint?: { x, y } | null, options?: ScrollingZoomOptions) => number缩放到指定系数,centerPoint 为缩放中心(默认视口中心)
zoomBy(zoomFactorDelta: number, centerPoint?: { x, y } | null, options?: ScrollingZoomOptions) => number按增量缩放
addScrollVelocity(offsetsVelocity: { x, y }, inertiaDecayRate?: { x, y } | null) => number追加滚动速度(惯性滚动)
addZoomVelocity(zoomFactorVelocity: number, centerPoint?: { x, y } | null, inertiaDecayRate?: number | null) => number追加缩放速度
bringIntoView(element: HTMLElement, options?: { margin?: number }) => number把元素滚入视口(焦点元素自动触发)
registerAnchorCandidate(element: HTMLElement) => void注册锚点候选(也可用 data-can-scroll-anchor 标记)
unregisterAnchorCandidate(element: HTMLElement) => void注销锚点候选

ScrollingScrollOptions / ScrollingZoomOptions 的 animationMode 取 'disabled' \| 'enabled' \| 'auto'(默认 'auto'),另有 snapPointsMode 占位。

只读属性(Readonly)

属性(Readonly)类型说明
horizontalOffsetnumber当前水平偏移
verticalOffsetnumber当前垂直偏移
zoomFactornumber当前缩放系数
extentWidthnumber内容区宽度
extentHeightnumber内容区高度
viewportWidthnumber视口宽度
viewportHeightnumber视口高度
scrollableWidthnumber可滚动宽度(内容 − 视口)
scrollableHeightnumber可滚动高度(内容 − 视口)
state'idle' | 'interaction' | 'inertia' | 'animation'当前交互状态
currentAnchorHTMLElement | null当前生效的锚点元素

插槽(Slots)

插槽(Slots)说明
default滚动内容

滚轮归属(对齐 WinUI 3):指针位于本组件内、且该方向确实可滚动时,本次滚轮由本组件独占——即使已到滚动极限也不会滚到外层 ScrollView;horizontal/verticalScrollChainMode 为 auto / never 时到极限即吞掉滚轮(等价 overscroll-behavior: contain),为 always 时才把未消化的剩余增量显式交给外层。本方向没有可滚动内容、或该方向被禁用时不占有滚轮,冒泡交给外层。触控 / 笔由指针捕获独占,不走这套判定。

v0.3.0-alpha.2