本教程向您展示如何使用单个 HTML 应用内消息创建多步骤引导轮播。与依赖滑动手势的传统轮播不同,此方法使用按钮驱动导航,并将所有步骤保持在一条消息中。
您将构建的内容:
- 包含图片、文本和按钮的两步引导流程
- 按钮导航(点击”下一步”前进,点击”开始使用”关闭)
- 进度指示点
- 步骤之间的平滑淡入淡出过渡
在以下情况使用此方法:
- 引导用户完成简短的引导或教育流程(2-5 步)
- 要求用户明确点击按钮才能继续(无滑动手势)
- 将所有内容保持在一个 HTML 应用内消息中以简化操作
- 流程完成时自动关闭消息
前提条件
开始之前,请确保您具备:
多步骤流程的工作原理
在深入代码之前,了解技术方法很重要。此实现使用一个 HTML 应用内消息,通过显示和隐藏内容在步骤之间切换,而不是加载多个单独的消息。
该架构依赖于四个核心组件:
每个步骤的卡片容器
每个步骤都包装在一个带有 card 类和唯一 ID 的 <div> 中:<div id="card-0" class="card active">...</div>
<div id="card-1" class="card">...</div>
- 所有卡片同时存在于 DOM 中
- 一次只有一张卡片可见(由
active 类控制)
CSS 可见性控制
CSS 使用透明度和指针事件处理显示/隐藏逻辑:.card {
opacity: 0;
pointer-events: none; /* 防止与隐藏卡片的交互 */
transition: opacity .25s ease;
}
.card.active {
opacity: 1;
pointer-events: auto; /* 允许与可见卡片的交互 */
}
为什么这很重要:
opacity: 0 在视觉上隐藏卡片但保留其在布局中的位置
pointer-events: none 防止意外点击隐藏的卡片
transition 创建平滑的淡入淡出效果
JavaScript 状态管理
setActive(i) 函数控制哪张卡片可见:function setActive(i) {
// 更新卡片可见性
document.getElementById("card-0").className = i === 0 ? "card active" : "card";
document.getElementById("card-1").className = i === 1 ? "card active" : "card";
// 更新进度点
var dots = document.getElementById("dots").children;
dots[0].classList.toggle("active", i === 0);
dots[1].classList.toggle("active", i === 1);
}
此函数:
- 从所有卡片中移除
active
- 将
active 添加到目标卡片
- 更新进度指示点
按钮事件监听器
按钮触发导航或关闭:// 前进到下一步
document.getElementById("next-0").addEventListener("click", function () {
setActive(1);
});
// 关闭应用内消息
document.getElementById("done").addEventListener("click", function (e) {
if (window.OneSignalIamApi && OneSignalIamApi.close) {
OneSignalIamApi.close(e);
}
});
重要提示: OneSignalIamApi.close(e) 是从 HTML 内部关闭应用内消息的 OneSignal SDK 方法。
关键见解: 这是应用内消息中的单页应用 (SPA) 模式。所有内容一次性加载,JavaScript 管理状态变化而无需重新加载。
步骤 1:创建新的 HTML 应用内消息
- 在 OneSignal 仪表板中,转到 Messages → In-App Messages
- 点击 New In-App Message
- 选择 HTML 作为消息类型
- 选择 Full Screen 或 Large 布局(推荐用于引导以最大化视觉效果)
- 继续进入 HTML 编辑器
HTML 编辑器预览可能无法完全反映运行时行为。请始终在真实设备或测试用户上进行测试,以验证动画、按钮行为和关闭操作。
步骤 2:添加 HTML 模板
用以下模板替换编辑器内容。此模板包括:
- 自包含代码: 所有 HTML、CSS 和 JavaScript 在一个文件中
- 按钮驱动导航: 无滑动手势(跨设备更可靠)
- 淡入淡出过渡: 步骤之间平滑的透明度变化
- OneSignal SDK 集成: 使用
OneSignalIamApi.close(e) 关闭消息
- 移动端优化: 带有 viewport meta 标签的响应式布局
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<!-- viewport-fit=cover 确保在带刘海的设备上覆盖安全区域 -->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<style>
/* 基础样式 - 重置和系统字体 */
html, body {
margin: 0;
padding: 0;
background: #ffffff;
font-family: -apple-system, system-ui;
}
/* 带内边距的主容器 */
.wrap {
padding: 28px 22px 24px;
}
/* 舞台容器 - 将所有卡片保持在同一位置 */
.stage {
position: relative;
min-height: 74vh; /* 确保足够的垂直空间 */
}
/* 卡片 - 引导流程的每个步骤 */
.card {
position: absolute; /* 所有卡片重叠在同一位置 */
inset: 0; /* 完全覆盖舞台 */
display: flex;
flex-direction: column;
align-items: center;
opacity: 0; /* 默认隐藏 */
pointer-events: none; /* 隐藏时防止点击 */
transition: opacity .25s ease; /* 平滑淡入淡出效果 */
}
/* 活动卡片可见且可交互 */
.card.active {
opacity: 1;
pointer-events: auto;
}
/* 排版 */
h1 {
margin: 44px 0 12px;
font-size: 26px;
text-align: center;
}
p {
margin: 0;
color: #6b7280;
text-align: center;
max-width: 260px;
line-height: 1.35;
}
/* 图片容器 - 带圆角的正方形 */
.image {
width: 240px;
height: 240px;
border-radius: 16px;
margin: 24px 0 12px;
background-size: cover;
background-position: center;
}
/* 主按钮 */
.btn {
margin-top: auto; /* 将按钮推到卡片底部 */
width: 100%;
max-width: 260px;
height: 52px;
border: 0;
border-radius: 12px;
background: #3b82f6; /* 蓝色 - 可根据您的品牌定制 */
color: #fff;
font-size: 18px;
font-weight: 600;
}
/* 进度指示点 */
.dots {
display: flex;
justify-content: center;
gap: 8px;
padding: 12px 0 8px;
}
.dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: #d1d5db; /* 非活动点颜色 */
}
.dot.active {
background: #6b7280; /* 活动点颜色 */
transform: scale(1.15); /* 活动时略大 */
}
</style>
</head>
<body>
<div class="wrap">
<div class="stage">
<!-- 步骤 1:欢迎卡片(以 "active" 类开始可见) -->
<div id="card-0" class="card active">
<h1>欢迎</h1>
<div
class="image"
style="background-image: url('https://images.pexels.com/photos/6153129/pexels-photo-6153129.jpeg');">
</div>
<p>几分钟内养成平静的日常习惯。</p>
<button
id="next-0"
class="btn"
data-onesignal-unique-label="onboarding_next_0">
下一步
</button>
</div>
<!-- 步骤 2:呼吸卡片(开始隐藏,用户点击"下一步"时显示) -->
<div id="card-1" class="card">
<h1>呼吸</h1>
<div
class="image"
style="background-image: url('https://images.pexels.com/photos/417173/pexels-photo-417173.jpeg');">
</div>
<p>随时进行引导式呼吸来重置状态。</p>
<button
id="done"
class="btn"
data-onesignal-unique-label="onboarding_done">
开始使用
</button>
</div>
</div>
<!-- 进度指示器:2 个点,第一个开始时为活动状态 -->
<div class="dots" id="dots">
<div class="dot active"></div>
<div class="dot"></div>
</div>
</div>
<script>
(function () {
/**
* 通过切换 "active" 类在卡片之间切换
* @param {number} i - 要显示的卡片索引(0 或 1)
*/
function setActive(i) {
// 更新卡片可见性
document.getElementById("card-0").className = i === 0 ? "card active" : "card";
document.getElementById("card-1").className = i === 1 ? "card active" : "card";
// 更新进度点
var dots = document.getElementById("dots").children;
dots[0].classList.toggle("active", i === 0);
dots[1].classList.toggle("active", i === 1);
}
// 按钮:下一步(卡片 0 → 卡片 1)
document.getElementById("next-0").addEventListener("click", function () {
setActive(1);
});
// 按钮:开始使用(关闭应用内消息)
document.getElementById("done").addEventListener("click", function (e) {
// 检查 OneSignal IAM API 是否可用
if (window.OneSignalIamApi && OneSignalIamApi.close) {
OneSignalIamApi.close(e); // 关闭消息
}
});
})();
</script>
</body>
</html>
步骤 3:自定义您的内容
可安全自定义的内容
您可以修改这些元素而不会破坏功能:
内容:
<h1> 标签中的标题文本
<p> 标签中的正文内容
- 按钮标签(
下一步、开始使用)
background-image: url('...') 样式中的图片 URL
视觉样式:
- 颜色:更改
.btn 背景色、文本颜色或点的颜色
- 间距:调整内边距和外边距
- 排版:修改 font-family、font-size、font-weight
- 圆角:更新按钮和图片的
border-radius 值
添加更多步骤
要添加第三个步骤,请按照以下模式操作:
- 添加 HTML 卡片:
<div id="card-2" class="card">
<h1>您的标题</h1>
<div class="image" style="background-image: url('your-image-url');"></div>
<p>您的描述</p>
<button id="next-2" class="btn">下一步</button>
</div>
- 添加进度点:
<div class="dots" id="dots">
<div class="dot active"></div>
<div class="dot"></div>
<div class="dot"></div> <!-- 新点 -->
</div>
- 更新
setActive() 函数:
function setActive(i) {
document.getElementById("card-0").className = i === 0 ? "card active" : "card";
document.getElementById("card-1").className = i === 1 ? "card active" : "card";
document.getElementById("card-2").className = i === 2 ? "card active" : "card"; // 新卡片
var dots = document.getElementById("dots").children;
dots[0].classList.toggle("active", i === 0);
dots[1].classList.toggle("active", i === 1);
dots[2].classList.toggle("active", i === 2); // 新点
}
- 更新上一步骤的按钮 ID:
将卡片 1 按钮的
id="done" 更改为 id="next-1",然后添加点击监听器:
document.getElementById("next-1").addEventListener("click", function () {
setActive(2);
});
- 将关闭按钮添加到新的最后一张卡片(card-2):
document.getElementById("done").addEventListener("click", function (e) {
if (window.OneSignalIamApi && OneSignalIamApi.close) {
OneSignalIamApi.close(e);
}
});
保持引导流程简短(最多 2-4 步)。用户在较长的流程中会快速流失。使用点击跟踪测试完成率。
步骤 4:测试应用内消息
测试清单
- 在 OneSignal 仪表板中保存消息
- 配置发送设置:
- 设置触发条件(例如,会话开始、特定页面浏览)
- 选择目标受众或选择测试用户
- 发送到测试设备:
- 使用测试用户预览而不影响生产用户
- 在物理设备上安装您的应用(推荐而非模拟器以获得准确行为)
- 验证功能:
- ✓ 第一张卡片显示正确内容
- ✓ “下一步”按钮前进到卡片 2
- ✓ 进度点正确更新
- ✓ 淡入淡出过渡平滑
- ✓ “开始使用”按钮关闭消息
- ✓ 消息不会立即重新出现(检查频率限制设置)
模拟器可能无法准确反映真实设备行为,特别是触摸交互和 SDK 集成。在发布到生产环境之前,请务必在物理设备上进行测试。
常见问题故障排除
| 问题 | 可能原因 | 解决方案 |
|---|
| 消息未出现 | 未满足触发条件 | 检查应用内消息触发器并验证您的测试用户是否满足条件 |
| 按钮不起作用 | JavaScript 错误或 ID 不匹配 | 检查浏览器控制台是否有错误;验证按钮 ID 与事件监听器 ID 是否匹配 |
| 图片未加载 | CORS 问题或无效 URL | 使用 HTTPS URL;首先在浏览器中测试图片 URL |
| 消息出现但无法关闭 | OneSignal SDK 未加载 | 验证 Mobile SDK 设置是否完成 |
后续步骤
跟踪用户参与度:
个性化体验:
- 为完成引导的用户添加标签(例如,
onboarding_completed: true)
- 使用标签细分用户并防止重复显示引导流程
- 添加用户数据以个性化未来消息中的内容
高级自定义:
- 关闭后将用户深度链接到特定屏幕
- 使用 Liquid 语法用用户名或属性个性化标题
- 使用不同的引导流程实施 A/B 测试以优化完成率