Tour 引导
用于逐步介绍界面中的关键功能。
用法
逐步介绍页面中的关键操作
组件结构
加粗的是必需部件。
data-scope="tour":root · backdrop · spotlight · positioner · content · title · description · progress-text · progress-indicator · progress-dot · prev-trigger · next-trigger · skip-trigger · close-trigger · arrow
示例
居中步骤
用于引导的开场与结束
定位
为每一步选择合适的浮层方向
无遮罩
保留页面环境并突出目标
设计指引
何时使用
- 首次进入复杂页面时介绍关键操作。
- 新功能上线后提供一次性引导。
何时不用
- 界面结构本身不清晰时应先改进界面。
- 需要随时查看的说明使用帮助内容或文字提示。
特性
- 聚光灯突出目标,
spotlightPadding控制留白。 - 高亮框同步目标节点的实际圆角,直角与圆角目标保持各自轮廓。
autoScroll自动将目标滚动到可见区域。- 无目标步骤在视口中居中,适合开场与结束。
showBackdrop=false关闭背景暗幕,但保留目标高亮环。- 支持受控步序、完成和跳过回调。
- 气泡走 M4 sheet 三件套(1px 描边、不透明底、投影),与对话框同源;边界由描边承担,不只靠影分层。锚定步从贴着目标的那条边涨开入场,退场按 exit 档收拢。
- 末行三颗按钮与角落的关闭按钮走 Action Control 家族配方:下一步是整条引导的主线动作,显式 solid 品牌实心;上一步为中性描边,跳过为无壳 ghost;关闭按钮为 icon 档 ghost 面。悬停与按下沿画布承载阶梯换底并 0.97 缩放,Space / Enter 与触屏按住期间投影
data-pressed。 - 标题为 heading-3,说明文字为 13px 说明档;说明区是气泡里唯一让步的滚动面,滚到头不带动页面。进度圆点是 8px 正圆,当前那颗拉成 20px 胶囊。
组合
- 使用
progress-text或progress-indicator展示进度。 - 使用
prev-trigger、next-trigger与skip-trigger提供导航。
最佳实践
- 步数控制在三到五步。
- 从第一步开始提供跳过入口。
- 记录完成状态,避免重复展示。
反模式
- 不要强制用户完成引导。
- 不要指向尚未渲染的目标。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-tour> |
| Vue 组件 | XhTourArrow XhTourBackdrop XhTourCloseTrigger XhTourContent XhTourDescription XhTourNextTrigger XhTourPositioner XhTourPrevTrigger XhTourProgressDot XhTourProgressIndicator XhTourProgressText XhTourRoot XhTourSkipTrigger XhTourSpotlight XhTourTitle |
| 组合式函数 | useTour |
| 状态机 | tourMachine |
| 皮肤 | @xihan-ui/styles/tour.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
steps | TourStep[] | 步骤清单。它同时是步序的上界与读屏「第 m 步,共 n 步」的分母。 | |
value | number | 当前步序(0 起)。提供即受控:内部不再自行修改,只发 onValueChange。 | |
defaultValue | number | 非受控初值,默认 0。 | |
open | boolean | ||
defaultOpen | boolean | ||
placement | Placement | 整份引导的首选放置位,默认 bottom;单步可用自己的 placement 覆盖。 | |
dir | Direction | 文字方向,默认 ltr。只改写浮层在行内轴上 start 与 end 的落点。 | |
offset | number | 浮层与目标的间距(px)。 | |
closeOnEscape | boolean | ||
closeOnInteractOutside | boolean | 层外交互关闭,默认 false:引导需经 skip 或 close 两个明确出口退出。 | |
showBackdrop | boolean | 绘制遮罩,默认 true。 | |
spotlightPadding | number | 高亮框在目标四周留出的空白(px),默认 8。 | |
autoScroll | boolean | 展开与换步时自动把目标滚进视口(nearest,已可见时不动),默认 true。 | |
translations | Partial<TourTranslations> | ||
onValueChange | (details: TourValueChangeDetails) => void | 步序变化意图回调;受控时是唯一出口,非受控时随内部写入一并通知。 | |
onOpenChange | (details: TourOpenChangeDetails) => void | open 变化意图回调;受控时是唯一出口,非受控时随内部转移一并通知。 | |
onComplete | (details: TourCompleteDetails) => void | 末步再按下一步:先发它,再经 onOpenChange 关闭。 | |
onSkip | (details: TourSkipDetails) => void | 用户主动放弃(skip-trigger 或 Escape):先发它,再经 onOpenChange 关闭。 |
TourStep
steps 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 稳定标识,写入 data-step-id;作者据此对应(埋点、按步定制渲染)。 |
target | string | null | 高亮目标的 CSS 选择器。null / 省略 / 查询不到节点都视为该步不锚定任何元素: 浮层居中、不绘制高亮框、不显示箭头。 | |
title | string | ||
description | string | ||
placement | Placement | 该步的首选放置位;未提供时沿用整份引导的 placement。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
open-change | TourOpenChangeDetails | open 状态变化;detail 为 { open: boolean } |
value-change | TourValueChangeDetails | 步序变化;detail 为 { value: number } |
complete | TourCompleteDetails | 末步再按下一步;detail 为 { step: number } |
skip | TourSkipDetails | 用户放弃(跳过按钮或 Escape);detail 为 { step: number } |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhTourRoot | default | TourRootSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhTourProgressDot | index | number | string | 是 | 圆点对应的步序,0 基;兼收字符串。 |
XhTourRoot | container | () => Element | null | 本实例三张 Tour 浮层的 Portal 容器;优先于应用级配置。 | |
XhTourRoot | children | SlotChildren<TourRootSlotProps> |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
root | 'open' | 'closed' |
backdrop | 'open' | 'closed' |
spotlight | 'open' | 'closed' |
positioner | 'open' | 'closed' |
content | 'open' | 'closed' |
prev-trigger | 'open' | 'closed' |
next-trigger | 'open' | 'closed' |
skip-trigger | 'open' | 'closed' |
以下名称仅用于内部状态机。
状态:open · closed
事件:OPEN · CLOSE · VALUE.SET · STEP.PREV · STEP.NEXT · SKIP · GEOMETRY.SYNC · CONTROLLED.OPEN · CONTROLLED.CLOSE · PRESS.START · PRESS.END
判据:isOpenControlled · isLastStep · isLastStepOpenControlled · canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
open | boolean | |
value | number | 当前步序,恒在 [0, count - 1] 内;清单为空时为 0。 |
count | number | |
currentStep | TourStep | null | 当前步的声明;清单为空时为 null。 |
firstStep | boolean | 停在首步:上一步按钮据此禁用。 |
lastStep | boolean | 停在末步:下一步按钮据此更换文案(完成)。 |
anchored | boolean | 该步锚定了页面元素:居中步为 false,此时不绘制高亮框也不显示箭头。 |
progressText | string | 「第 m 步,共 n 步」。作者未编写 progress-text 的内容时由适配器填入。 |
setOpen | (next: boolean) => void | |
setValue | (next: number) => void | 直接跳到某一步;越界会被夹回 [0, count - 1]。 |
goToNextStep | () => void | 末步再前进一步 = 完成:先发 onComplete,再关闭。 |
goToPrevStep | () => void | |
skip | () => void | 放弃引导:先发 onSkip,再关闭。 |
remeasure | () => void | 重新测量高亮框与浮层位置:目标节点被外部改动(换位、变尺寸)后调用它校准。 |
getRootProps | () => T['element'] | |
getBackdropProps | () => T['element'] | |
getSpotlightProps | () => T['element'] | |
getPositionerProps | () => T['element'] | |
getContentProps | () => T['element'] | |
getTitleProps | () => T['element'] | |
getDescriptionProps | () => T['element'] | |
getProgressTextProps | () => T['element'] | |
getProgressIndicatorProps | () => T['element'] | |
getProgressDotProps | (props: TourProgressDotProps) => T['element'] | |
getPrevTriggerProps | () => T['button'] | |
getNextTriggerProps | () => T['button'] | |
getSkipTriggerProps | () => T['button'] | |
getCloseTriggerProps | () => T['button'] | |
getArrowProps | () => T['element'] |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Enter / Space | open 且焦点在 content 上(不在按钮等控件上) | 走到下一步;停在末步时完成引导并关闭 |
Escape | open 且 closeOnEscape | 放弃引导(发 onSkip)并关闭 |
ArrowUp / ArrowDown / ArrowLeft / ArrowRight | open | 一概不接管:既不换步也不阻止默认行为,留给页面滚动与读屏浏览 |
Tab / Shift+Tab | open | 焦点陷在 content 内循环,跑出去会被拉回来 |
Enter / Space | held in prev-trigger / next-trigger / skip-trigger / close-trigger | 按住期间该按钮投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦或气泡收起撤下 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
backdrop | aria-hidden | 'true' |
spotlight | aria-hidden | 'true' |
content | aria-describedby | description 部件的 id |
content | aria-hidden | !open || undefined |
content | aria-labelledby | title 部件的 id |
content | aria-modal | 'true' |
content | role | 'dialog' |
progress-text | aria-live | 'polite' |
progress-indicator | aria-hidden | 'true' |
next-trigger | aria-label | translations?.finish | translations?.next |
close-trigger | aria-label | translations?.close |
arrow | aria-hidden | 'true' |
样式参考
皮肤
@xihan-ui/styles/tour.css 使用 [data-scope="tour"][data-part="root"] 部件选择器,位于 xihan.components 与 xihan.motion 层。覆盖样式使用 xihan.overrides。
forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-empty | ''(条件成立时才出现) |
root | data-state | 'open' | 'closed' |
root | data-step | String(value) |
backdrop | data-position | 'anchored' | 'center' |
backdrop | data-state | 'open' | 'closed' |
spotlight | data-dimmed | ''(条件成立时才出现) |
spotlight | data-state | 'open' | 'closed' |
positioner | data-placement | 定位引擎算出的实际落位 |
positioner | data-position | 'anchored' | 'center' |
positioner | data-positioned | ''(条件成立时才出现) |
positioner | data-state | 'open' | 'closed' |
content | data-placement | 定位引擎算出的实际落位 |
content | data-state | 'open' | 'closed' |
content | data-step | String(value) |
progress-text | data-step | String(value) |
progress-indicator | data-count | String(count) |
progress-indicator | data-step | String(value) |
progress-dot | data-complete | ''(条件成立时才出现) |
progress-dot | data-current | ''(条件成立时才出现) |
progress-dot | data-index | String(index) |
prev-trigger | data-disabled | ''(条件成立时才出现) |
prev-trigger | data-pressed | ''(条件成立时才出现) |
prev-trigger | data-state | 'open' | 'closed' |
prev-trigger | data-xh-action-control | '' |
prev-trigger | data-xh-action-display | 'always' |
prev-trigger | data-xh-action-profile | 'text' |
prev-trigger | data-xh-action-size | 'sm' |
prev-trigger | data-xh-action-variant | 'outline' |
next-trigger | data-last | ''(条件成立时才出现) |
next-trigger | data-pressed | ''(条件成立时才出现) |
next-trigger | data-state | 'open' | 'closed' |
next-trigger | data-xh-action-control | '' |
next-trigger | data-xh-action-display | 'always' |
next-trigger | data-xh-action-profile | 'text' |
next-trigger | data-xh-action-size | 'sm' |
next-trigger | data-xh-action-variant | 'solid' |
next-trigger | data-xh-ink-surface | '' |
skip-trigger | data-pressed | ''(条件成立时才出现) |
skip-trigger | data-state | 'open' | 'closed' |
skip-trigger | data-xh-action-control | '' |
skip-trigger | data-xh-action-display | 'always' |
skip-trigger | data-xh-action-profile | 'text' |
skip-trigger | data-xh-action-size | 'sm' |
skip-trigger | data-xh-action-variant | 'ghost' |
close-trigger | data-pressed | ''(条件成立时才出现) |
close-trigger | data-xh-action-control | '' |
close-trigger | data-xh-action-display | 'always' |
close-trigger | data-xh-action-profile | 'icon' |
close-trigger | data-xh-action-size | 'sm' |
close-trigger | data-xh-action-variant | 'ghost' |
arrow | data-placement | 定位引擎算出的实际落位 |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-tour-action-radius | next-triggerprev-triggerskip-trigger | border-radius | default | --xh-shape-control | tour 的 next-trigger、prev-trigger、skip-trigger 部件 border-radius 覆盖槽。 |
--xh-tour-arrow-size | arrow | --xh-_overlay-arrow-size | default | --xh-overlay-arrow-size | tour 的 arrow 部件 --xh-_overlay-arrow-size 覆盖槽。 |
--xh-tour-backdrop-bg | backdrop | background | default | --xh-bg-overlay | tour 的 backdrop 部件 background 覆盖槽。 |
--xh-tour-backdrop-layer | backdrop | z-index | default | --xh-_layer | tour 的 backdrop 部件 z-index 覆盖槽。 |
--xh-tour-bg | arrowcontent | background | default | --xh-material-elevated-bg | tour 的 arrow、content 部件 background 覆盖槽。 |
--xh-tour-border | arrowcontent | border | default | --xh-material-elevated-border | tour 的 arrow、content 部件 border 覆盖槽。 |
--xh-tour-close-bg-active | close-trigger | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-bg-pressed | tour 的 close-trigger 部件 background-color 覆盖槽。 |
--xh-tour-close-bg-hover | close-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | tour 的 close-trigger 部件 background-color 覆盖槽。 |
--xh-tour-close-fg | close-trigger | color | default | --xh-fg-muted | tour 的 close-trigger 部件 color 覆盖槽。 |
--xh-tour-close-fg-hover | close-trigger | color | disabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-fg-focus-visible--xh-_action-variant-fg-hover--xh-_action-variant-fg-pressed | tour 的 close-trigger 部件 color 覆盖槽。 |
--xh-tour-close-radius | close-trigger | border-radius | default | --xh-shape-control | tour 的 close-trigger 部件 border-radius 覆盖槽。 |
--xh-tour-close-size | close-triggercontenttitle | block-sizeinline-sizepadding-inline-end | defaulthas([data-scope='tour'][data-part='close-trigger'])xh-action-profile=icon | --xh-_action-profile-visual-size--xh-control-h-sm | tour 的 close-trigger、content、title 部件 block-size、inline-size、padding-inline-end 覆盖槽。 |
--xh-tour-description-fg | description | color | default | --xh-fg-muted | tour 的 description 部件 color 覆盖槽。 |
--xh-tour-fg | contentroot | color | default | --xh-fg-default--xh-material-elevated-fg | tour 的 content、root 部件 color 覆盖槽。 |
--xh-tour-gap | content | gap | default | --xh-space-2 | tour 的 content 部件 gap 覆盖槽。 |
--xh-tour-icon-size | close-triggercontentnext-triggerprev-triggerrootskip-trigger | --xh-icon-size | default | --xh-_action-profile-glyph-size--xh-glyph-size-md | tour 的 close-trigger、content、next-trigger、prev-trigger、root、skip-trigger 部件 --xh-icon-size 覆盖槽。 |
--xh-tour-max-h | content | max-block-size | default | --xh-overlay-max-h | tour 的 content 部件 max-block-size 覆盖槽。 |
--xh-tour-max-w | content | max-inline-size | default | --xh-overlay-max-w-lg | tour 的 content 部件 max-inline-size 覆盖槽。 |
--xh-tour-next-bg | next-trigger | --xh-ink-surfacebackground-color | defaultfocus-visiblexh-ink-surface | --xh-_action-variant-bg-focus-visible--xh-_action-variant-bg-rest | tour 的 next-trigger 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-tour-next-bg-hover | next-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | tour 的 next-trigger 部件 background-color 覆盖槽。 |
--xh-tour-next-fg | next-trigger | color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-fg-focus-visible--xh-_action-variant-fg-hover--xh-_action-variant-fg-pressed--xh-_action-variant-fg-rest | tour 的 next-trigger 部件 color 覆盖槽。 |
--xh-tour-next-shadow | next-trigger | box-shadow | default | none | tour 的 next-trigger 部件 box-shadow 覆盖槽。 |
--xh-tour-positioner-layer | positioner | z-index | default | --xh-_layer | tour 的 positioner 部件 z-index 覆盖槽。 |
--xh-tour-positioner-padding | positioner | padding | position=center | --xh-space-4 | tour 的 positioner 部件 padding 覆盖槽。 |
--xh-tour-progress-dot-bg | progress-dot | background | default | --xh-border-default | tour 的 progress-dot 部件 background 覆盖槽。 |
--xh-tour-progress-dot-bg-complete | progress-dot | background | complete | --xh-border-strong | tour 的 progress-dot 部件 background 覆盖槽。 |
--xh-tour-progress-dot-bg-current | progress-dot | background | current | --xh-bg-brand | tour 的 progress-dot 部件 background 覆盖槽。 |
--xh-tour-progress-fg | progress-text | color | default | --xh-fg-subtle | tour 的 progress-text 部件 color 覆盖槽。 |
--xh-tour-progress-font-size | progress-text | font-size | default | --xh-text-caption-size | tour 的 progress-text 部件 font-size 覆盖槽。 |
--xh-tour-progress-indicator-gap | progress-indicator | gap | default | --xh-space-1 | tour 的 progress-indicator 部件 gap 覆盖槽。 |
--xh-tour-px | content | padding-inline | default | --xh-surface-px-md | tour 的 content 部件 padding-inline 覆盖槽。 |
--xh-tour-py | content | padding-block | default | --xh-surface-py-md | tour 的 content 部件 padding-block 覆盖槽。 |
--xh-tour-radius | content | border-radius | default | --xh-shape-overlay | tour 的 content 部件 border-radius 覆盖槽。 |
--xh-tour-shadow | content | box-shadow | default | --xh-material-elevated-shadow | tour 的 content 部件 box-shadow 覆盖槽。 |
--xh-tour-skip-trigger-px | next-triggerprev-triggerskip-trigger | padding-inline | default | --xh-_action-profile-padding-inline | tour 的 next-trigger、prev-trigger、skip-trigger 部件 padding-inline 覆盖槽。 |
--xh-tour-spotlight-layer | spotlight | z-index | default | --xh-_layer | tour 的 spotlight 部件 z-index 覆盖槽。 |
--xh-tour-spotlight-radius | spotlight | border-radius | default | --xh-_tour-spotlight-radius | tour 的 spotlight 部件 border-radius 覆盖槽。 |
--xh-tour-spotlight-ring | spotlight | box-shadow | default | --xh-ring-focus | tour 的 spotlight 部件 box-shadow 覆盖槽。 |
--xh-tour-spotlight-shroud | spotlight | box-shadow | dimmed | --xh-bg-overlay | tour 的 spotlight 部件 box-shadow 覆盖槽。 |
--xh-tour-title-fg | title | color | default | --xh-fg-default | tour 的 title 部件 color 覆盖槽。 |
--xh-tour-title-font-size | title | font-size | default | --xh-text-heading-3-size | tour 的 title 部件 font-size 覆盖槽。 |
--xh-tour-title-font-weight | title | font-weight | default | --xh-text-heading-3-weight | tour 的 title 部件 font-weight 覆盖槽。 |
动效
动效角色:按压 · 状态 · 切换 · 指示与换位 · 出现(锚定面板)(见动效规范)。
关键帧 xh-tour-spotlight-in · xh-tour-spotlight-out 随皮肤自带,不引用别处文件里的名字;共享关键帧 xh-fade-in · xh-fade-out · xh-overlay-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;background-color · block-size · border-radius · inline-size · inset-block-start · inset-inline-start 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
