InfiniteScroll 无限滚动
获取下一页的通用触发器,滚动只是默认的触发方式。
用法
哨兵滚进可视区即派发 load,取数完成后把 loading 写回 false
组件结构
加粗的是必需部件。
data-scope="infinite-scroll":root · sentinel · load-more-trigger
示例
提前量
distance 把可视区沿块轴向外扩展,哨兵尚未出现就先取下一页
没有更多数据
最后一页取完后开启 disabled,哨兵不再被观察,load 也不再派发
状态透出
phase / loading / disabled 由组件交给宿主,加载提示与结束语都由宿主自行放置
继续往下滚(当前 idle)
取下一页的按钮
与哨兵同一条通路:读屏在虚拟光标模式下不产生滚动事件,该按钮是它的键盘等价入口
设计指引
何时使用
- 时间流、消息列表等用户只需要继续加载的内容。
何时不用
- 用户需要跳到确定位置或分享某一页时,使用分页。
- 页面有页脚需要可达时,无限滚动会使页脚无法到达。
特性
distance是提前量:距底部该距离时触发,用户感觉不到等待。loading与disabled由组件交给宿主,加载提示与结束语由宿主放置。- 加载完成后关闭即可,不会再触发。
load-more-trigger是同一通路的另一个入口:一个真实按钮,取数中与关闭时自动停用。它是铺满一行的独立动作条目:宽度由容器给、高度随内容,中性描边与透明底,按下只换面不缩放。
组合
最佳实践
- 提供明确的结束提示:“没有更多了”优于无声停止。
- 加载失败时可以重试,不静默停止。
- 放置一个
load-more-trigger:读屏在虚拟光标模式下不产生滚动事件,只靠哨兵无法获取第二页。 - 按钮的文案写在按钮内,组件不代填名称,读屏读出的与视觉一致。
反模式
- 页面底部有重要内容(页脚、版权、联系方式)却用无限滚动。
- 不提供结束提示,用户持续向下滚动。
- 与虚拟滚动合用时把哨兵放在条目之间:窗口外的条目不渲染,哨兵永远无法进入可视区。
- 与虚拟滚动合用时不提供
target:提前量按整页可视区计算,而实际滚动的是虚拟滚动的视口。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-infinite-scroll> |
| Vue 组件 | XhInfiniteScrollLoadMoreTrigger XhInfiniteScrollRoot XhInfiniteScrollSentinel |
| 组合式函数 | useInfiniteScroll |
| 状态机 | infiniteScrollMachine |
| 皮肤 | @xihan-ui/styles/infinite-scroll.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
distance | number | 提前量(px):哨兵距可视区该距离即视为进入,默认 0(实际出现才计)。扩展的是 getTargetEl 给出的可视区。 | |
disabled | boolean | 关闭:不再观察,也不再触发。列表已没有下一页时使用。 | |
loading | boolean | 正在取数:期间不观察、不重复触发。取完由宿主写回 false。 | |
onLoad | () => void | 应取下一页。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
load | `` | 应取下一页 |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhInfiniteScrollRoot | default | InfiniteScrollRootSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhInfiniteScrollRoot | target | HTMLElement | null | 裁剪出可视区的滚动容器,默认即整页滚动;distance 的提前量扩展的正是这块区域。 | |
XhInfiniteScrollRoot | children | SlotChildren<InfiniteScrollRootSlotProps> |
状态
以下名称仅用于内部状态机。
状态:idle · loading · paused
事件:SENTINEL.ENTER · LOAD · MODE.SYNC · PRESS.START · PRESS.END
判据:isPaused · isLoading · canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
phase | InfiniteScrollPhase | |
loading | boolean | 正在取数。 |
disabled | boolean | 已关闭,不再观察。 |
getRootProps | () => T['element'] | |
getSentinelProps | () => T['element'] | |
getLoadMoreTriggerProps | () => T['button'] | 取下一页的按钮。文案由作者写在按钮中,组件不代填。 |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Enter / Space | held in load-more-trigger, 未关闭且未在取数 | 按住期间 load-more-trigger 投影 data-pressed,与指针 :active 同一副按压面(row 档只换面不缩放);抬起、失焦或进入取数 / 关闭撤下 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
root | aria-busy | 'true' | undefined |
sentinel | aria-hidden | 'true' |
样式参考
皮肤
@xihan-ui/styles/infinite-scroll.css 使用 [data-scope="infinite-scroll"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-loading | ''(条件成立时才出现) |
load-more-trigger | data-disabled | ''(条件成立时才出现) |
load-more-trigger | data-loading | ''(条件成立时才出现) |
load-more-trigger | data-pressed | ''(条件成立时才出现) |
load-more-trigger | data-xh-action-control | '' |
load-more-trigger | data-xh-action-display | 'always' |
load-more-trigger | data-xh-action-profile | 'row' |
load-more-trigger | data-xh-action-size | 'md' |
load-more-trigger | data-xh-action-variant | 'outline' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-infinite-scroll-load-more-bg | load-more-trigger | --xh-ink-surfacebackground-color | defaultxh-ink-surface | --xh-_action-variant-bg-rest | infinite-scroll 的 load-more-trigger 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-infinite-scroll-load-more-bg-active | load-more-trigger | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-bg-pressed | infinite-scroll 的 load-more-trigger 部件 background-color 覆盖槽。 |
--xh-infinite-scroll-load-more-bg-hover | load-more-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | infinite-scroll 的 load-more-trigger 部件 background-color 覆盖槽。 |
--xh-infinite-scroll-load-more-border | load-more-trigger | border | default | --xh-_action-variant-border-rest | infinite-scroll 的 load-more-trigger 部件 border 覆盖槽。 |
--xh-infinite-scroll-load-more-border-hover | load-more-trigger | border-color | disabledhoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-border-hover--xh-_action-variant-border-pressed | infinite-scroll 的 load-more-trigger 部件 border-color 覆盖槽。 |
--xh-infinite-scroll-load-more-fg | load-more-trigger | color | defaultdisabledhoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-fg-hover--xh-_action-variant-fg-pressed--xh-_action-variant-fg-rest | infinite-scroll 的 load-more-trigger 部件 color 覆盖槽。 |
--xh-infinite-scroll-load-more-font-size | load-more-trigger | font-size | default | --xh-text-body-size | infinite-scroll 的 load-more-trigger 部件 font-size 覆盖槽。 |
--xh-infinite-scroll-load-more-gap | load-more-trigger | gap | default | --xh-_action-profile-gap | infinite-scroll 的 load-more-trigger 部件 gap 覆盖槽。 |
--xh-infinite-scroll-load-more-h | load-more-trigger | block-sizemin-block-size | defaultxh-action-profile=row | --xh-_action-profile-visual-size | infinite-scroll 的 load-more-trigger 部件 block-size、min-block-size 覆盖槽。 |
--xh-infinite-scroll-load-more-icon-size | load-more-trigger | --xh-icon-size | default | --xh-_action-profile-glyph-size | infinite-scroll 的 load-more-trigger 部件 --xh-icon-size 覆盖槽。 |
--xh-infinite-scroll-load-more-px | load-more-trigger | padding-inline | default | --xh-_action-profile-padding-inline | infinite-scroll 的 load-more-trigger 部件 padding-inline 覆盖槽。 |
--xh-infinite-scroll-load-more-radius | load-more-trigger | border-radius | default | --xh-_action-profile-radius | infinite-scroll 的 load-more-trigger 部件 border-radius 覆盖槽。 |
--xh-infinite-scroll-sentinel-size | sentinel | block-size | default | --xh-stroke-thin | infinite-scroll 的 sentinel 部件 block-size 覆盖槽。 |
动效
动效角色:按压 · 状态(见动效规范)。
本组件皮肤不含过渡与关键帧,也没有脚本驱动的动效:状态一变,外观立即到位。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
