Tour
tour漫游引导 · 自研零依赖 SVG mask 镂空高亮 + 自定位气泡卡(标题/描述/上一步/下一步/跳过/进度) + resize/scroll 重算
用法
基础引导
受控 open + current;steps 用 target 函数返回 ref 元素,逐步高亮走查(含无 target 的居中开场步)。点「开始引导」试。
搜索资源…
点下方按钮开始新手引导。
tsx
const searchRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
const [current, setCurrent] = useState(0);
const steps = [
{ title: "欢迎使用瑚琏", description: "30 秒带你认识三处核心功能。" },
{
target: () => searchRef.current,
title: "全局搜索",
description: "按关键字快速定位任意资源。",
placement: "bottom",
},
];
<>
<div ref={searchRef}>搜索资源…</div>
<Button onClick={() => { setCurrent(0); setOpen(true); }}>开始引导</Button>
<Tour
steps={steps}
open={open}
current={current}
onChange={setCurrent}
onClose={() => setOpen(false)}
/>
</>点遮罩关闭
maskClosable 允许点暗色遮罩区直接结束引导(默认 false,防误触)。
搜索资源…
点下方按钮开始新手引导。
tsx
<Tour
steps={steps}
open={open}
current={current}
onChange={setCurrent}
onClose={() => setOpen(false)}
maskClosable
/>何时用
新手引导 / 功能上线导览:逐步高亮页面上的真实 DOM 元素并附气泡讲解。需要「逐步串联多个页面元素」用本组件;只是悬停/点击单个元素的提示用 Tooltip / Popover;阻断式确认流程用 Dialog / Modal。
导入
ts
import { Tour, resolveTarget, computeSpotlight, computeCardPosition, type Rect } from "@hulianui/ui"Props
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| steps* | TourStep[] | — | 引导步骤列表(见下) |
| open* | boolean | — | 是否打开(受控) |
| current* | number | — | 当前步索引(受控,从 0 起) |
| maskClosable | boolean | false | 点击遮罩是否关闭(默认不允许误触关闭) |
| spotlightPadding | number | 8 | 高亮镂空在目标四周的留白 px |
| spotlightRadius | number | 8 | 镂空圆角 px |
| gap | number | 12 | 气泡卡与目标的间距 px |
| zIndex | number | 100 | 遮罩 z-index |
TourStep:
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
| target | (() => Element | null) | string | null | — | 高亮目标:函数(() => ref.current,DOM 动态时最稳)/ CSS 选择器字符串 / null 省略(气泡居中,适合开场收尾) |
| title | ReactNode | — | 步骤标题 |
| description | ReactNode | — | 步骤描述 |
| placement | "top" | "bottom" | "left" | "right" | "bottom" | 气泡方位(放不下自动翻到对侧);无目标时忽略 |
Events
| 事件 | 类型 | 说明 |
|---|---|---|
| onChange | (current: number) => void | 当前步变化(点上一步/下一步) |
| onClose | () => void | 关闭(跳过/Esc/末步完成且未传 onFinish) |
| onFinish | () => void | 末步「完成」回调;不传则走 onClose |
Slots
| 插槽 | 类型 | 说明 |
|---|---|---|
| prevText | ReactNode | 「上一步」按钮文案覆盖 |
| nextText | ReactNode | 「下一步」按钮文案覆盖 |
| skipText | ReactNode | 「跳过」按钮文案覆盖 |
| finishText | ReactNode | 「完成」按钮文案覆盖 |
TourStep 内的title/description亦为ReactNode,见上方 TourStep 表。
禁忌 / 坑
- 完全受控:
open与current必须由消费者 state 持有,上一步/下一步只触发onChange,本组件不自管步索引——忘记在onChange里回写current会导致引导卡在第一步。 target用函数(() => ref.current)比 CSS 选择器更稳,尤其目标 DOM 动态挂载/卸载时。- 全屏 overlay 经 Portal 挂到 body;截图/可视验证须「先点开始触发 open 再截」(同 Drawer/Popover/Toast)。
相关
Playground
搜索资源…
点下方按钮开始新手引导。
const [open, setOpen] = useState(false);
const [current, setCurrent] = useState(0);
<Tour
open={open}
current={current}
onChange={setCurrent}
onClose={() => setOpen(false)}
steps={[
{ title: "欢迎", description: "开场居中…" },
{ target: () => searchRef.current, title: "全局搜索", description: "…", placement: "bottom" },
{ target: "#new-btn", title: "新建一条", description: "…", placement: "left" },
]}
/>