このチュートリアルでは、単一のHTML アプリ内メッセージを使用してマルチステップのオンボーディングカルーセルを作成する方法を説明します。スワイプジェスチャーに依存する従来のカルーセルとは異なり、このアプローチはボタン駆動のナビゲーションを使用し、すべてのステップを1つのメッセージ内に保持します。
構築する内容:
- 画像、テキスト、ボタンを含む2ステップのオンボーディングフロー
- ボタンナビゲーション(「次へ」をタップして進む、「始める」をタップして閉じる)
- 進行状況インジケータードット
- ステップ間のスムーズなフェード遷移
このアプローチを使用する場合:
- ユーザーを短いオンボーディングまたは教育フロー(2-5ステップ)を通じてガイドする
- ユーザーに続行するために明示的にボタンをタップさせる必要がある(スワイプジェスチャーなし)
- すべてを1つのHTML アプリ内メッセージ内に保持してシンプルにする
- フローが完了したときにメッセージを自動的に閉じる
前提条件
開始する前に、以下を確認してください:
マルチステップフローの仕組み
コードに入る前に、技術的なアプローチを理解することが重要です。この実装は1つのHTML アプリ内メッセージを使用し、複数の別々のメッセージをロードするのではなく、コンテンツの表示と非表示によってステップを切り替えます。
アーキテクチャは4つのコアコンポーネントに依存しています:
各ステップのカードコンテナ
各ステップはcardクラスと一意のIDを持つ<div>でラップされています:<div id="card-0" class="card active">...</div>
<div id="card-1" class="card">...</div>
- すべてのカードは同時にDOMに存在します
- 一度に1枚のカードのみが表示されます(
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が1つのファイルに
- ボタン駆動ナビゲーション: スワイプジェスチャーなし(デバイス間でより信頼性が高い)
- フェード遷移: ステップ間のスムーズな透明度変化
- OneSignal SDK統合: メッセージを閉じるために
OneSignalIamApi.close(e)を使用
- モバイル最適化: viewportメタタグを使用したレスポンシブレイアウト
<!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値を更新
ステップを追加
3番目のステップを追加するには、次のパターンに従います:
- 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セットアップが完了しているか確認 |
次のステップ
ユーザーエンゲージメントをトラッキング:
- ステップ間の離脱を測定するために
data-onesignal-unique-label属性を使用してクリックトラッキングを追加(テンプレートに既に含まれています)
- Messages → In-App Messages → [あなたのメッセージ] → Analyticsでクリック分析を表示
エクスペリエンスをパーソナライズ:
高度なカスタマイズ:
- 終了後にユーザーを特定の画面にディープリンク
- Liquid構文を使用してユーザー名や属性で見出しをパーソナライズ
- 完了率を最適化するために異なるオンボーディングフローでA/Bテストを実装