
做前端这么多年我接过不少“给产品加个新手引导”的需求。以前一想到这个就头大因为要么得自己写高亮遮罩、步骤气泡、进度条要么就得在一堆页面里埋点控制状态。直到后来在 React 和 Vue 两个项目里都用上了 Supademo这个事情才变得没那么痛苦。这篇文章就从选型思路、实际接入步骤、路由联动、埋点事件到各种排障细节完整记录一下我在两类技术栈里引入 Supademo 的过程。1. 为什么是交互式新手指引为什么是 Supademo1.1 新手指引的常见痛点我相信不少团队都经历过这几种做引导的方式。第一种产品经理画一张带红框的截图让开发弹个 Modal用户点开看一眼就关。开发成本确实低但引导和真实界面是脱节的用户看完截图回到页面还是不知道按钮在哪更别说实际操作路径了。第二种开发直接引一个开源引导库在代码里编排步骤。这种方案本身没问题但一旦页面布局调整高亮位置就偏了每次需求改动都要发版维护成本会滚雪球一样涨。尤其当你要针对不同角色、不同产品模块设计不同引导路径时写死在代码里的引导几乎是一种灾难。第三种方案就是用 Supademo 这类交互式演示和引导平台。它的核心思路是把“引导内容”当成一个可录制、可编辑、可独立投放的产物而不是写死在代码里的组件。产品经理可以在真实的网页操作流程上录制引导系统自动生成高亮区域、文字气泡、遮罩层前端只需要把这段成品嵌入项目再用代码控制“什么时候打开”。这意味着引导内容迭代不需要发版产品同学自己就能改开发和产品之间的协作效率会明显提升。我在实际项目中感受到的最大差别是自研引导方案永远在解决“怎么把步骤写出来”的问题而用 Supademo 只需要解决“怎么把引导嵌入我的应用并控制触发”的问题。后者边界清晰得多代码量也会少很多这正是我选它的核心理由。1.2 Supademo 的核心能力与选型对比Supademo 本身能做两类事情。一类是产品演示录制一段操作录屏转成交互式 Demo可以放进官网或销售材料里另一类就是新手指引把录制的步骤变成用户跟着点的引导流程支持在指定元素上高亮也可以给每一步加标题、描述、快捷键提示。做新手指引时我更多用到的是后面这一类能力。选型的时候我对比过三种路线纯自研、开源引导库、Supademo 这类托管服务。判断标准就三条第一引导内容是否需要产品/运营频繁调整第二引导路径是否跟用户真实操作数据强绑定第三团队的交付速度要求。如果每次调整都要走开发流程显然托管服务更灵活如果引导步骤完全依赖后端返回的用户路径甚至要实时计算下一步那自研依然是正解如果只是想快速验证 MVP 阶段的新手任务Supademo 几乎是成本最低的。从时间成本上看自研一个支持高亮、气泡、步骤回退、进度保存、分群发布的引导组件前后端加一起至少 5 到 10 个工作日后续每次界面调整还要修定位。开源库快一些但样式定制和版本升级也需要持续投入。而用 Supademo产品经理录制一遍流程前端复制一段嵌入代码熟练的话半小时内就能跑通。当然它也有边界比如非常复杂的业务联动、完全自定义的视觉交互这类工具不一定都能满足后面我也会讲到哪些场景不该用。2. 集成前必须想清楚的三个问题2.1 引导触发时机与用户分群动手写代码之前我最先想的不是“怎么嵌入”而是“什么时候弹、给谁弹、弹几次”。这个问题如果不想清楚很容易把新手引导做成老用户眼里的弹窗广告。我之前在一个 React 项目里就踩过坑。产品最初只提了一个需求新用户登录后自动弹出引导。结果上线后一片吐槽内部测试号被弹了无数次老用户每次登录也会被弹窗打扰最夸张的是用户已经完成了关键操作下次进来还是会被强制引导一遍。后来我们统一定义了三条规则触发条件、受众过滤、频率控制。Supademo 这类工具通常会在后台提供展示规则配置也支持通过 URL 参数手动触发。但实际接入时我倾向于把“用户是否完成引导”这个状态存在自己后端前端根据用户信息判断后再去加载或打开引导。这样主动权在自己手里后续做用户分群、流失分析也更灵活。比如“只看过一次引导”和“已经完成了项目创建”这两个条件放在前端 localStorage 里也能做但跨设备、跨浏览器就会失效放在后端才是稳妥的。2.2 嵌入方式选型iframe 还是 SDKSupademo 的接入方式从我接触到的情况看主要有两种一种是 iframe 嵌入直接把引导流程页面放进 iframe 里另一种是使用官方提供的 SDK 或脚本方式在页面初始化后通过代码控制打开、关闭、跳步。我在 React 和 Vue 项目里分别试过各有优劣。iframe 的优点很明显宿主页面和引导界面完全隔离CSS 不会互相污染样式一致性有保障。缺点是想做“引导步骤和页面本地交互联动”会比较绕因为跨域通信只能靠 postMessage。SDK 方式则灵活很多可以直接监听引导事件、动态控制步骤、在步骤间执行自己的业务逻辑但代价是会多引入一段运行时脚本和样式需要关注包体积和主题冲突。我的选择标准很简单如果引导是纯展示型比如带用户熟悉后台功能概览用 iframe 就够了开发量最小如果引导需要和业务逻辑深度绑定比如引导用户创建项目、填写表单后再进入下一步那就得用 SDK。这次我做的 React 项目因为要分角色、做步骤联动用了 SDKVue 项目偏展示型直接用 iframe 嵌入两边都能跑得很顺畅。2.3 环境与权限准备接入 Supademo 之前有一个容易被忽略的准备工作网络白名单和域名授权。很多公司内网或开发环境有严格的 CSP 策略如果 iframe 的地址或 SDK 脚本域名没加到白名单控制台会报错表现就是引导区域白屏。另外Supademo 后台一般也要求配置允许嵌入的域名本地开发地址、临时预览地址、正式域名都要加进去尤其是本地开发如果用了自定义 host 映射很容易漏配。我建议在项目里单独抽出配置模块把引导平台的 URL、环境标识、API Key 这类信息集中管理不要散落在各个页面组件里。React 项目里我放在src/config/onboarding.jsVue 项目里放在src/config/onboarding.ts按 dev、staging、prod 区分不同环境指向不同的引导流程。这样做还有一个好处测试同学可以自由验证 staging 流程而线上用户只会看到已审核通过的 production 流程不会因为产品同学正在调试而出现内容闪烁。权限方面也要注意Supademo 后台通常会区分可以编辑、可以发布的人员。不要把后台账号和密码直接共享给所有人至少要把“能修改内容”和“能发布上线”分开。我见过一个团队客服同学帮忙排查问题时不小心把正在编辑的半成品引导发布到了生产环境影响范围非常大。3. 在 React 项目中接入 Supademo 的完整步骤3.1 最小可用接入iframe 方案如果你的项目对引导交互要求不高最省事的做法就是 iframe 嵌入。核心组件代码我封装成下面这样只负责“打开时渲染弹层 关闭时销毁”。// OnboardingGuide.tsx import { useState } from react; interface OnboardingGuideProps { demoUrl: string; open?: boolean; onClose?: () void; } export default function OnboardingGuide({ demoUrl, open false, onClose }: OnboardingGuideProps) { if (!open) return null; return ( div style{{ position: fixed, inset: 0, zIndex: 9999, background: rgba(0,0,0,0.6), }} div style{{ position: absolute, top: 50%, left: 50%, transform: translate(-50%, -50%), width: 80vw, height: 80vh, background: #fff, borderRadius: 12, overflow: hidden, }} iframe src{demoUrl} titleonboarding-demo style{{ width: 100%, height: 100%, border: none }} allowclipboard-write / button onClick{onClose} style{{ position: absolute, top: 12, right: 12 }} 关闭 /button /div /div ); }这里有两个小细节值得注意。第一iframe 的src要用 Supademo 后台生成的可嵌入链接并且一定要在后台配好“只允许指定域名嵌入”防止链接被复制到别的站点使用。第二allowclipboard-write是给引导流程里出现的复制操作准备的比如引导用户复制 API Key如果不加这个权限复制按钮在 iframe 里会静默失败用户点了没反应很容易被当成工具 bug。使用方式也很直接业务页里维护一个guideOpen状态点击按钮或首次登录时置为 true 即可。// Dashboard.tsx import { useState } from react; import OnboardingGuide from ./OnboardingGuide; const DEMO_URL https://your-workspace.supademo.com/embed/xxxxxx; export default function Dashboard() { const [guideOpen, setGuideOpen] useState(false); return ( div button onClick{() setGuideOpen(true)}查看新手指引/button OnboardingGuide demoUrl{DEMO_URL} open{guideOpen} onClose{() setGuideOpen(false)} / /div ); }3.2 封装 React 组件按路由触发最小可用版本只适合单个页面真正的业务场景里新手指引通常是跨路由的。比如用户注册后先进项目列表页再点创建项目最后进入详情页整个路径都要被引导覆盖。这个需求下我选择把引导状态提升到全局并在路由配置里给页面加“引导步骤”的元信息。如果你用的是 React Router可以在路由对象里通过handle字段携带当前页面对应的引导步骤标识然后在布局组件中统一读取。// router.tsx import { createBrowserRouter } from react-router-dom; import Dashboard from ./pages/Dashboard; import ProjectDetail from ./pages/ProjectDetail; export const router createBrowserRouter([ { path: /dashboard, element: Dashboard /, handle: { onboardingStep: dashboard }, }, { path: /project/:id, element: ProjectDetail /, handle: { onboardingStep: project-detail }, }, ]);布局组件里用useMatches获取当前匹配到的路由链找到最深层的 handle然后根据引导步骤状态打开或关闭引导。// AppLayout.tsx import { useEffect } from react; import { useMatches, Outlet } from react-router-dom; import OnboardingGuide from ./OnboardingGuide; export default function AppLayout() { const matches useMatches(); const currentHandle matches[matches.length - 1]?.handle; const currentStep currentHandle?.onboardingStep; useEffect(() { if (currentStep isOnboardingActive()) { OnboardingController.open(currentStep); } else { OnboardingController.close(); } }, [currentStep]); return ( div Outlet / OnboardingGuide / /div ); }这里要特别提醒一个细节useMatches返回的数组顺序是从父路由到子路由所以取matches[matches.length - 1]才能拿到当前最深的页面配置。我第一次写的时候取的是第一个嵌套路由下一直拿到根布局的 handle导致引导在子页面不触发排查了半天。3.3 与用户行为埋点联动做过 B 端产品的人应该都有体感新手引导如果不接埋点根本不知道它到底有没有用。用户是卡在第三步流失的还是压根没点开或者看完就完成了关键操作这些数据比引导本身更重要。Supademo 一般会提供事件回调或 webhook常见事件包括引导开始、步骤变化、引导完成、手动关闭。我在 React 项目里会把接入层统一封装成一个 hook把这些事件转成自己的埋点事件。以 iframe 方案为例页面和 iframe 之间通常通过postMessage通信所以我会在 window 上监听 message 事件。但一定要校验event.origin只处理 Supademo 合法域名派发的消息否则任何页面里的脚本都能伪造消息埋点数据会被污染。// useOnboardingEvents.ts import { useEffect } from react; const SUPADEMO_ORIGIN https://your-workspace.supademo.com; function track(eventName: string, payload: Recordstring, unknown) { // 这里对接你自己的埋点系统比如自定义事件上报 } export function useOnboardingEvents() { useEffect(() { const handleEvent (event: MessageEvent) { if (event.origin ! SUPADEMO_ORIGIN) return; const { type, step, demoId } event.data || {}; if (type onboarding-step-change) { track(onboarding_step_change, { step, demoId }); } if (type onboarding-completed) { track(onboarding_completed, { demoId }); markUserOnboardingDone(demoId); } }; window.addEventListener(message, handleEvent); return () window.removeEventListener(message, handleEvent); }, []); }如果是 SDK 方式通常可以直接监听官方提供的事件回调不需要自己处理 postMessage。但不管哪种方式最关键的一点是收到“完成”事件后一定要把用户状态标记为已完成避免下次登录再弹。同时把完成率、平均步数、跳出步骤这些指标都埋好后续才能判断引导内容是否需要迭代。3.4 在 React 18 并发渲染下的注意事项如果你的项目已经升级到 React 18并且用了createRoot和并发特性有一个坑需要提前避开远程 SDK 脚本还没加载完成就初始化或者在StrictMode下组件双挂载导致 SDK 实例被初始化两次。这类问题在开发环境最容易暴露因为 StrictMode 会刻意模拟挂载、卸载、再挂载的流程如果 effect 清理函数没写清楚就会出现两个遮罩层或者引导状态错乱。我实际遇到过一次打开引导弹窗时页面短暂卡一下然后出现两个遮罩背景明显变深。排查后发现useEffect 里同时做了两件事——动态加载 Supademo 脚本和初始化容器但组件卸载时没有销毁实例。第一次挂载创建的实例残留在页面上第二次挂载又创建了一个新的两个遮罩叠在一起。解决思路是把副作用清理写完整动态添加的脚本标签要移除SDK 实例要销毁全局事件监听要解绑。另外用useId生成容器 ID避免多次挂载时 id 冲突。useEffect(() { let cancelled false; loadSupademoScript().then(() { if (!cancelled) { initSupademo(); } }); return () { cancelled true; destroySupademo(); }; }, []);还有一个和 React 18 自动批处理相关的细节如果引导过程中同时更新了多个状态比如“当前步骤”和“用户已完成标记”一起更新它们会被合并到同一次渲染。这本身不是 bug但如果你依赖旧状态去计算新状态容易得到意外结果。引导状态一旦复杂起来我建议直接改用useReducer把步骤切换、开关、进度更新都收敛到 reducer 里逻辑更清楚出 bug 的概率也更低。4. 在 Vue 项目中接入 Supademo 的完整步骤4.1 用 Vue 3 组合式 API 封装引导模块Vue 项目的接入思路和 React 类似但代码组织方式更贴合 Vue 的响应式习惯。我这里用的是 Vue 3 TypeScript script setup所以直接把引导逻辑封装成一个组合式函数所有页面都可以复用。先定义一个全局状态保存引导是否打开、当前 demo URL、当前步骤。Vue 3 里可以用reactive或ref创建状态再用导出的函数来修改。// src/composables/useOnboarding.ts import { reactive, readonly } from vue; const state reactive({ open: false, demoUrl: , step: , }); export function useOnboarding() { function openGuide(demoUrl: string, step?: string) { state.demoUrl demoUrl; state.step step || ; state.open true; } function closeGuide() { state.open false; state.demoUrl ; state.step ; } return { state: readonly(state), openGuide, closeGuide, }; }我用readonly包了一层 state目的是防止组件里随意修改state.open。所有变更只能走openGuide和closeGuide排查问题的时候只需要盯着这两个函数不会出现某个组件把状态改得乱七八糟的情况。这个习惯在多人协作的项目里特别有用。接下来写一个承载 iframe 的弹窗组件或者直接基于现有 UI 库的 Modal。由于state.open是响应式的只要它变成 true组件就会自动渲染配合 Teleport 可以避免很多层级问题。!-- OnboardingModal.vue -- template Teleport tobody div v-ifstate.open classonboarding-mask click.selfcloseGuide div classonboarding-panel iframe :srcstate.demoUrl titleonboarding-demo classonboarding-iframe allowclipboard-write / /div /div /Teleport /template script setup langts import { useOnboarding } from ../composables/useOnboarding; const { state, closeGuide } useOnboarding(); /script style scoped .onboarding-mask { position: fixed; inset: 0; z-index: 9999; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; } .onboarding-panel { width: 80vw; height: 80vh; background: #fff; border-radius: 12px; overflow: hidden; position: relative; } .onboarding-iframe { width: 100%; height: 100%; border: none; } /style这里有一个容易被误解的小点虽然 Teleport 把弹窗节点挂到了 body 下但style scoped里的样式仍然会生效因为 Vue 会正常给这些节点注入带作用域哈希的 class。如果发现样式没生效大概率是全局样式优先级更高可以检查一下类名是否被覆盖。4.2 路由守卫控制新手任务的打开与关闭Vue Router 和 React Router 的差异主要体现在 API 上。React 里用useMatches读取当前路由Vue Router 则更适合用全局导航守卫。我在router.afterEach里处理引导开关代码非常直观。// router/index.ts import { createRouter, createWebHistory } from vue-router; import { useOnboarding } from ../composables/useOnboarding; const router createRouter({ history: createWebHistory(), routes: [ { path: /dashboard, component: () import(../views/Dashboard.vue), meta: { onboarding: { demoUrl: https://your-workspace.supademo.com/embed/dashboard-onboarding, step: dashboard, }, }, }, { path: /project/:id, component: () import(../views/ProjectDetail.vue), meta: { onboarding: { demoUrl: https://your-workspace.supademo.com/embed/project-onboarding, step: project-detail, }, }, }, ], }); router.afterEach((to) { const { closeGuide, openGuide } useOnboarding(); const onboarding to.meta.onboarding; if (onboarding shouldShowGuide()) { openGuide(onboarding.demoUrl, onboarding.step); } else { closeGuide(); } });有一点需要特别提醒to.meta.onboarding在 TypeScript 下会被推断成unknown或任意类型直接访问.demoUrl会报类型错误。我建议在项目里声明全局的RouteMeta类型扩展比如在src/types/vue-router.d.ts里写import vue-router; declare module vue-router { interface RouteMeta { onboarding?: { demoUrl: string; step: string; }; } }这样路由配置和afterEach里的读取都能获得完整的类型提示不用到处写as断言。4.3 通过 v-model 或 Teleport 管理弹层使用第三方 UI 库时引导弹窗的层级问题经常出现在“Modal 嵌套”场景下。比如页面里已经打开了一个项目设置抽屉这时候再触发新手引导引导遮罩可能被抽屉盖住用户完全看不到引导内容。这个问题和框架无关但 Vue 项目里我习惯用 Teleport 强制把引导层挂到 body 最外层再配合足够大的 z-index。如果项目用的是 Element PlusModal 组件本身支持append-to-body属性可以直接把弹层挂到 body 下效果类似 Teleport。但我更推荐自己写一个轻量级的引导容器因为引导弹层的样式通常需要和业务 Modal 区分开遮罩透明度、圆角、关闭按钮的位置都不一样自写的灵活性更高。另外一个常见的联动场景是引导打开时业务页面的滚动应该被锁定否则用户滑动滚轮时背景内容在引导层后面滚动视觉上非常乱。我通常会在引导打开时给 body 加overflow: hidden关闭时移除。但要注意如果页面本身有滚动监听或懒加载锁滚动可能触发一些意外的 resize 或 scroll 事件需要在代码里做一层兼容。4.4 多环境与多产品线配置管理Vue 项目处理多环境我习惯用环境变量加配置表。比如在src/config/onboarding.ts里根据import.meta.env区分当前环境然后导出对应的 Supademo 配置。// src/config/onboarding.ts const env import.meta.env.VITE_APP_ENV || development; interface OnboardingConfig { baseUrl: string; defaultDemoUrl: string; } const configs: Recordstring, OnboardingConfig { development: { baseUrl: https://staging.supademo.workspace/embed, defaultDemoUrl: https://staging.supademo.workspace/embed/xxx, }, production: { baseUrl: https://app.supademo.workspace/embed, defaultDemoUrl: https://app.supademo.workspace/embed/yyy, }, }; export const onboardingConfig configs[env] || configs.development;为什么要把环境区分开因为产品经理很可能在 staging 上调试新引导内容还没确认前不希望线上用户看到。如果前端写死同一个 demo URL就会出现 staging 上的半成品流程被线上用户看到的情况。测试环境走测试流程生产环境走审核通过的流程发布节奏就安全很多。多产品线的场景也可以沿用这个思路。同一个 Vue 应用如果包含多个子产品建议在路由 meta 里挂一个module字段然后根据 module 从配置表里取对应的 demo URL不要在每个页面组件里写一串 if 判断。配置驱动的方式后期加新引导流程时只需要改配置不需要动业务代码维护成本低很多。5. 常见问题与排障实录5.1 白屏 / 加载不出来接入这类第三方嵌入内容白屏是我遇到最多的现象归一下类大概有四种原因。第一网络请求被拦。公司内网或开发环境如果配置了严格的 CSPiframe 地址和 SDK 脚本域名都必须加进白名单。排查方法很简单打开 DevTools 的 Network 面板看 Supademo 相关的请求是不是报blocked by CSP或者net::ERR_BLOCKED_BY_CLIENT。如果是找运维或前端负责人调整 CSP 规则。第二HTTPS 混合内容。宿主页面是 HTTPS但 iframe 地址写成 HTTP浏览器会默认拦截。复制代码时一定要检查协议别直接从某个已过期地址里复制。第三iframe 的X-Frame-Options或frame-ancestors限制。Supademo 后台一般会要求配置允许嵌入的域名如果本地开发域名没加进去iframe 就会被拒页面里出现空白区域。本地开发如果用了非 localhost 的 host特别容易漏配一定要把所有可能跑的域名都填进去。第四加载时序问题。SDK 方式下脚本还没加载完就调初始化方法会导致初始化失败。这种问题要加异步控制不能直接在组件 mounted 后立即调用应该等脚本加载完成再做初始化。5.2 样式被覆盖 / 层级问题iframe 方案下样式冲突很少但 SDK 方案下冲突几乎必然存在。比如项目里有一条全局样式* { box-sizing: border-box }或者给所有div加了position: relative都可能影响 SDK 生成的遮罩和气泡定位。我遇到的具体案例是某 Vue 项目里有一个全局样式给所有div设置了position: relative结果 SDK 生成的高亮层定位全乱每个高亮框都偏到右下角。用 DevTools 逐个检查才发现是全局样式覆盖。解决方向有几个一是把 SDK 容器挂载到隔离节点下面对容器内部做样式重置二是使用 Shadow DOM 隔离如果 SDK 支持三是统一约定引导容器相关的元素不套用全局 reset。层级问题则主要出在弹窗上。页面里的下拉菜单、Drawer、Modal 如果 z-index 很高会把引导遮罩盖住。常规做法是把引导层的 z-index 提到项目最高。但要小心一个隐藏问题如果引导层所在的父容器有transform、filter、will-change这类属性会创建新的 stacking context子元素的 z-index 会被限制在当前上下文里设置得再高也出不去。遇到这种场景建议把引导弹层直接挂到 body 下用 Teleport 或 createPortal。5.3 路由变化后引导状态丢失跨页面引导最烦人的问题是用户跟着引导走到第二步然后切换了路由再回来时引导状态没了。这多半不是工具的问题而是前端集成时把状态放错了位置。我的解决思路是把引导状态提升到全局绝不放在具体页面组件里。React 用 Context 或全局 storeVue 用 composable 里的 reactive 状态都能做到跨路由保留。同时要监听路由变化动态切换 iframe 的 src 或 SDK 指向的步骤。如果 Supademo 支持通过 URL 参数指定步骤就在路由变化时重新拼接 URL如果不支持就在路由变化时重新打开对应流程。另外一个容易被忽略的点是浏览器前进后退。如果用户点了引导里的某个链接导致 history 变化返回时虽然路由变了但引导状态不会自动恢复。这种情况建议在路由变化时统一做一次引导状态同步不要依赖各个页面自行处理。5.4 性能优化与代码分割第三方引导工具通常在用户触发时才需要加载如果一进应用就加载全部脚本对首屏性能一定有影响。尤其 C 端页面首屏速度直接影响用户留存。iframe 方案最简单默认不渲染 iframe只有引导开关变成 true 时才挂载。React 用条件渲染Vue 用v-if一行代码就能避免无意义的 iframe 请求。SDK 方案则建议动态加载脚本不要在前端入口静态引入。可以封装一个loadSupademoScript函数内部用 Promise 缓存加载状态避免同个脚本被重复注入。如果引导流程很多还可以考虑在 Supademo 后台拆成不同资源包按需加载对应包。如果项目本身对包体积敏感可以把引导相关组件全部通过动态 import 引入React 用React.lazyVue 用defineAsyncComponent。这样引导组件会被拆到独立 chunk 里首屏完全不加载用户真正触发引导时才会拉取。我实际测过这样能让首屏资源体积减少几十 KB对网络环境差的用户感知很明显。5.5 与工单/客服系统冲突最后一个坑比较隐蔽但 B 端产品几乎都会遇到引导弹层和客服气泡、工单按钮、在线文档入口这类常驻浮层互相打架。用户反馈“新手指引弹出来后右下角客服按钮消失了”其实不是消失而是被遮罩盖住了但用户不知道只能强杀页面。这个问题的本质是浮层优先级冲突。解决办法是在引导打开时主动隐藏或降级客服组件引导关闭后再恢复。React 项目里可以监听引导状态再通过 class 或 store 控制客服按钮显示Vue 项目里用 computed 状态控制或者直接给根节点加一个onboarding-activeclass用样式控制所有浮层。我实际踩过一次坑后养成了一个习惯在接引导类工具之前先梳理一遍项目里所有“常驻型浮层组件”比如客服、返回顶部、在线文档、智能问答统一考虑它们和引导遮罩的共存关系。提前把方案定好比线上出问题再补要省心得多。提示如果你发现引导打开时页面里有某些组件被误遮挡先别急着改第三方配置第一步是确认这些组件是不是都在同一个 stacking context 下。很多 z-index 问题不是数值不够而是父级容器创建了新的上下文。6. 一些实际经验与后续扩展方向回到最开始的问题新手引导到底应该怎么做才算好我的体会是工具只是把“引导内容”的生产和分发成本降下来真正决定引导效果的是产品对用户路径的理解。Supademo 的存在让产品经理可以快速上线、快速调整而前端要做的是把触发逻辑、路由联动、事件埋点这些基础设施搭好不要每次改引导文案都来麻烦开发。后续想更进一步可以把引导效果和用户激活漏斗打通。比如记录用户从注册到完成关键动作的每一步转化观察哪些用户看了引导后完成了创建流程哪些用户没看引导但自己摸索完成了。两组数据对比下来引导的增量价值会非常直观。再配合 A/B 测试对不同用户群投放不同版本的引导流程效果会比永远只弹同一套引导好很多。还有一个建议每次上线引导流程前最好让测试完整走一遍尤其要覆盖“断网后重试”“引导中刷新页面”“引导中切换路由”这些极限操作。引导类功能最怕的不是代码逻辑错而是状态错乱之后用户既无法继续引导又无法正常操作页面陷入一个两难境地。把边界情况处理好新手指引才能真正帮到用户而不是变成又一个让用户想卸载产品的理由。