SwAddToHomescreenGuide
H5 移动端用户首次访问 PWA 项目时,从屏幕底部滑入一个非阻断式信息卡片,引导用户把站点保存到主屏幕。 iOS Safari 和 Android 主流 Chromium 浏览器(Chrome / Edge / Samsung Internet / Firefox / Opera)分别给出对应操作步骤; 其它环境(桌面 / WebView / iOS 非 Safari / 国产浏览器等)一律静默不弹,避免误导。
组件采用 <Teleport to="body"> + <Transition> 渲染为底部 sheet,
带半透明遮罩(--sw-add-to-homescreen-guide-backdrop,默认 rgb(0 0 0 / 45%)),但不锁滚动 / 不抢焦;
关闭方式三选一:右上角 X 按钮 / 点遮罩 / Escape 键。
触发条件
以下条件全部满足才显示,任一不满足即静默:
- 客户端环境(SSR 阶段不会渲染)
- PWA 已启用 —— 运行时判据是
runtimeConfig.public.swiftWebFramework.pwa.enabled === true,它由配置期的swift.config.json里framework.pwa(布尔)派生 framework.addToHomescreen.enabled !== false- 当前不在 standalone 显示模式,即用户尚未把站点添加到主屏。两条判据缺一不可(fw#352):
window.matchMedia('(display-mode: standalone)').matches === false且navigator.standalone !== true—— 后者是 iOS 专有属性,也是 iOS legacy web clip(无 linked manifest 时「添加到主屏」的产物)唯一可靠的信号,那种形态下媒体查询并不匹配 - 设备类型可明确判定(详见下方 UA 矩阵)
- 用户最近"没被记住关闭"(由
rememberFor决定) - 到点时没有模态对话框开着(fw#387):判据是
document.body.style.pointerEvents === 'none'——SwModal 底层的 reka-ui Dialog 打开期间会把 body 设成 pointer-events: none,引导层就算 z-index 更高、看得见,X 和遮罩也点不动。命中则每 500ms 复查,对话框关掉后再弹。业务自带的年龄确认 / 公告弹窗与引导同屏时尤其常见
PWA 未启用时的警告:dev 模式下,如果检测到组件挂载但运行时
pwa.enabled === false,会在 console 输出一条 warn,提醒开发者要么启用 PWA,要么从app.vue注释/删除挂载点。
设备 / 浏览器支持矩阵
| 平台 | 浏览器 | 是否触发 | 说明 |
|---|---|---|---|
| iOS | Safari | ✓ | iOS 16+ / iOS 17+ / iOS 26+ |
| iOS | Chrome / Firefox / Edge | ✗ | iOS 系统限制只有 Safari 能添加到主屏 |
| iOS | 微信内 / 其它 WebView | ✗ | WebView 无原生入口 |
| Android | Chrome | ✓ | Chrome 主流入口 |
| Android | Edge / Samsung Internet | ✓ | 同样支持 |
| Android | Firefox / Opera | ✓ | 主流 Chromium 衍生版 |
| Android | WebView (; wv)) | ✗ | WebView 无原生入口 |
| Android | UC / QQ / 微信 / 百度 / MIUI / Vivo / Oppo / 夸克 / 360 / 搜狗 | ✗ | 国产浏览器无 PWA 入口或体验差 |
| 桌面(macOS / Windows / Linux) | 任意浏览器 | ✗ | 桌面端不弹移动端引导 |
基础用法
demo 数量豁免说明:本组件的完整 UI 仅在 iOS Safari / Android Chromium 且五道闸全过时可见,桌面 docs 站无法演示更多形态(slots 自定义等需真机验证),故仅保留下方 2 个可运行 demo + 闸门探测。
挂载演示(手机浏览器可见引导)
当前环境闸门探测(为什么不弹)
framework 默认提供纯文字兜底,开箱即用:
<template>
<NuxtLayout><NuxtPage /></NuxtLayout>
<SwModalHost />
<SwToast />
<SwAddToHomescreenGuide />
</template>
挂载到 app.vue 与 <SwModalHost />、<SwToast /> 同级即可。framework 在 mount 后 2 秒(默认)滑入引导,包含 iOS 或 Android 的两步图文。
注入图标库
framework 不内置图标 SVG(遵守"不引图标库"规范);推荐 applier 自行通过 slot 注入图标库做更丰富的视觉。以下为 @phosphor-icons/vue 真实示例:
<script setup lang="ts">
import { PhShareNetwork, PhDotsThreeVertical, PhPlusSquare } from '@phosphor-icons/vue'
</script>
<template>
<SwAddToHomescreenGuide>
<template #ios-instructions>
<li>
<PhShareNetwork :size="20" weight="regular" />
点击 Safari 工具栏的<strong>「共享」</strong>按钮
</li>
<li>
<PhPlusSquare :size="20" weight="regular" />
选择<strong>「添加到主屏幕」</strong>并确认
</li>
</template>
<template #android-instructions>
<li>
<PhDotsThreeVertical :size="20" weight="regular" />
点击浏览器右上角的<strong>菜单</strong>
</li>
<li>
<PhPlusSquare :size="20" weight="regular" />
选择<strong>「添加到主屏幕」</strong>并确认
</li>
</template>
</SwAddToHomescreenGuide>
</template>
控制弹出频率
默认每个浏览器会话只弹一次(关标签页后重置)。如希望"用户关闭后 N 天内不再打扰",使用 rememberFor prop:
<!-- 一周内不再弹 -->
<SwAddToHomescreenGuide remember-for="7d" />
<!-- 一天内不再弹 -->
<SwAddToHomescreenGuide remember-for="1d" />
<!-- 实质永久(100 年) -->
<SwAddToHomescreenGuide remember-for="36500d" />
rememberFor 值 | 存储后端 | 行为 |
|---|---|---|
'session'(默认) | sessionStorage | 关标签页即重置,下次进站重弹 |
| 正数 N | localStorage(带 timestamp) | 关闭后 N 天内不再弹 |
| 0 / 负数 | 不存储 | 每次进站都弹(仅供调试) |
自定义视觉
通过 CSS 变量在 applier 一侧整体调色:
| 变量 | 默认值 | 控制 |
|---|---|---|
--sw-add-to-homescreen-guide-bg | var(--popover) | 面板背景色 |
--sw-add-to-homescreen-guide-accent | var(--primary) | 步骤序号圆点底色 |
--sw-add-to-homescreen-guide-accent-foreground | var(--primary-foreground) | 步骤序号文字色(与上一条配对,别只改一侧) |
--sw-add-to-homescreen-guide-backdrop | rgb(0 0 0 / 45%) | 遮罩层颜色 |
--sw-add-to-homescreen-guide-step-bg | var(--muted) | 单条步骤的底色 |
--sw-add-to-homescreen-guide-radius | 1.25rem | 顶部圆角 |
--sw-add-to-homescreen-guide-z | 1100 | z-index |
--sw-add-to-homescreen-guide-close-bg | var(--muted) | 关闭钮底色(淡圆底,让它读起来像按钮) |
--sw-add-to-homescreen-guide-close-hover-bg | var(--accent) | 关闭钮 hover 底色(仅 hover: hover 设备) |
--sw-add-to-homescreen-guide-shadow | 0 -8px 32px rgb(0 0 0 / 12%) | 面板投影。向上投影,不套 --shadow-lg(那三档按向下投影设计) |
类钩子 .sw-add-to-homescreen-guide(portal 到 body 的根 div)允许深度覆盖布局。组件由于 Teleport 到 body,scoped CSS 不可达,需用非 scoped <style> 覆盖。
<style>
.sw-add-to-homescreen-guide {
--sw-add-to-homescreen-guide-accent: #ff6b00;
--sw-add-to-homescreen-guide-radius: 1.25rem;
/* 直接覆盖框架样式 */
border: 4px solid #000 !important;
box-shadow: 6px 6px 0 0 #000 !important;
}
</style>
项目级配置
除组件 prop 外,可通过 swift.config.json 设置项目级默认(影响 framework module 的 runtimeConfig):
{ "framework": { "pwa": true } }
🔴 写成
{ "pwa": { "enabled": true } }会静默关闭 PWA。framework 的判据是严格=== true(module.ts),对象形态判假 ⇒@vite-pwa/nuxt整个不装, Service Worker / manifest / 主屏链路全都没有 —— 效果与写"pwa": false完全相同, 且全程无报错。sw config set framework.pwa也会拒绝对象形态(它在 CLI 的布尔键名单里)。
引导浮层的行为写在 nuxt.config.ts(本项不进 swift.config.json,CLI 不管它 ——
它是 framework 的组件行为,不是 manifest 内容):
swiftConfig: {
...swiftConfig.framework,
addToHomescreen: { delay: 3000, rememberFor: '7d' }, // enabled 省略 = 跟随 framework.pwa
},
enabled可省略,省略时跟随framework.pwa(与不传整个addToHomescreen同义), 所以只想改延时写{ delay: 3000 }即可。显式enabled: false可在 PWA 开着时单独关掉引导。
优先级:组件 prop > nuxt.config.ts 的 swiftConfig > 内置默认('session' / 2000ms)。
如希望完全禁用引导(保留 PWA 但不弹引导):
swiftConfig: { ...swiftConfig.framework, addToHomescreen: false },
Props
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
delay | number | 2000 | mount 后多久弹出(毫秒)。优先级:prop > runtimeConfig > 2000 | |
rememberFor | 'session' | PeriodSpec | 'session' | 记忆关闭状态多久。'session' = sessionStorage,关标签即重置;'Nm'·'Nh'·'Nd' = localStorage 存 timestamp,N 天内不再弹;0/负数 = 不记忆 |
Slots
| 插槽名 | 默认渲染 | 说明 |
|---|---|---|
icon | manifest 的 /icons/icon-192.png(加载失败则隐藏) | 应用图标 |
title | 站点名(project.title),无则回退"保存到主屏幕" | 弹窗标题 |
ios-instructions | iOS 三步(前两步带内置线性 SVG 图标) | iOS Safari 操作步骤内容 |
android-instructions | Android 三步(前两步带内置线性 SVG 图标) | Android Chromium 操作步骤内容 |
故障排查
引导没弹?按以下顺序检查:
swift.config.json里framework.pwa是否为true(布尔,不是对象)?dev console 应该有相应 warn 帮助定位。写成{ "enabled": true }会静默关闭 PWA,见上「项目级配置」- 是否在客户端首次访问?同一标签页刷新已弹过的不再弹(除非
rememberFor设为 0) - 当前 UA 是否在支持矩阵里?iOS Chrome / 国产浏览器 / 桌面端都不弹
- 是否已经"添加到主屏幕"过?iOS Safari / Android Chrome 添加后 UA 切到 standalone 模式,组件主动跳过
addToHomescreen.enabled是否被显式设为false?检查swift.config.jsonrememberFor: N模式下,localStorage 的sw:a2hs:dismissedAt是否还在 N 天窗口内?清浏览器存储或调小rememberFor重新触发
没有 live demo:本组件依赖移动端 UA + 真实 PWA 配置,桌面浏览器无法触发,文档站不提供 DemoBox。请在真机或 Chrome DevTools「Toggle device toolbar」模拟移动设备 UA 自行验证。
注意事项
- 「怎么都不弹」先按 5 道静默闸排查(Rule UAH-001)—— 多数条件不满足时静默 return、无任何提示(仅「PWA 未启用」有 dev warn):① PWA 未启用 ②
addToHomescreen.enabled === false③ 已在 standalone(在已安装的 PWA 窗口里调试就永远不弹)④ 设备非 iOS Safari / Android Chromium(含糊设备一律静默)⑤ 处于已记忆的静默期内。 rememberFor是三态,最容易选错 —— 正数N= N 天内不再弹(localStorage 持久);'session'= 关标签页即重置;0或负数 = 每次都弹(仅测试用)。想「每次进站演示」用了'session'会变成一个会话只弹一次;想「永久只弹一次」用'session'则关页又弹。常用值是7。- iOS 与 Android 的引导文案 / 图示不同 —— 组件按设备分支渲染,调试时注意 UA 模拟未必能触发对应分支。