メインコンテンツへスキップ

概要

このチュートリアルでは、単一のHTML アプリ内メッセージを使用してマルチステップのオンボーディングカルーセルを作成する方法を説明します。スワイプジェスチャーに依存する従来のカルーセルとは異なり、このアプローチはボタン駆動のナビゲーションを使用し、すべてのステップを1つのメッセージ内に保持します。 構築する内容:
  • 画像、テキスト、ボタンを含む2ステップのオンボーディングフロー
  • ボタンナビゲーション(「次へ」をタップして進む、「始める」をタップして閉じる)
  • 進行状況インジケータードット
  • ステップ間のスムーズなフェード遷移
画像、テキスト、次へボタンを含むウェルカム画面を表示するオンボーディングカルーセル
このアプローチを使用する場合:
  • ユーザーを短いオンボーディングまたは教育フロー(2-5ステップ)を通じてガイドする
  • ユーザーに続行するために明示的にボタンをタップさせる必要がある(スワイプジェスチャーなし)
  • すべてを1つのHTML アプリ内メッセージ内に保持してシンプルにする
  • フローが完了したときにメッセージを自動的に閉じる
このガイドでは完全なコントロールのためにHTML アプリ内メッセージを使用します。ドラッグアンドドロップエディタでカードベースのオンボーディングフローを構築することもできます—それらのカードはスワイプ可能ですが、カスタマイズ性は低くなります。

前提条件

開始する前に、以下を確認してください:

マルチステップフローの仕組み

コードに入る前に、技術的なアプローチを理解することが重要です。この実装は1つのHTML アプリ内メッセージを使用し、複数の別々のメッセージをロードするのではなく、コンテンツの表示と非表示によってステップを切り替えます。 アーキテクチャは4つのコアコンポーネントに依存しています:
1

各ステップのカードコンテナ

各ステップはcardクラスと一意のIDを持つ<div>でラップされています:
<div id="card-0" class="card active">...</div>
<div id="card-1" class="card">...</div>
  • すべてのカードは同時にDOMに存在します
  • 一度に1枚のカードのみが表示されます(activeクラスで制御)
2

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はスムーズなフェード効果を作成します
3

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を追加
  • 進行状況インジケータードットを更新
4

ボタンイベントリスナー

ボタンがナビゲーションまたは終了をトリガーします:
// 次のステップに進む
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 アプリ内メッセージを作成

  1. OneSignalダッシュボードで、Messages → In-App Messagesに移動します
  2. New In-App Messageをクリックします
  3. メッセージタイプとしてHTMLを選択します
  4. Full ScreenまたはLargeレイアウトを選択します(視覚的インパクトを最大化するためオンボーディングに推奨)
  5. 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番目のステップを追加するには、次のパターンに従います:
  1. 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>
  1. 進行状況ドットを追加:
<div class="dots" id="dots">
  <div class="dot active"></div>
  <div class="dot"></div>
  <div class="dot"></div> <!-- 新しいドット -->
</div>
  1. 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); // 新しいドット
}
  1. 前のステップのボタンIDを更新: カード1のボタンでid="done"id="next-1"に変更し、クリックリスナーを追加:
document.getElementById("next-1").addEventListener("click", function () {
  setActive(2);
});
  1. 新しい最後のカード(card-2)に閉じるボタンを追加:
document.getElementById("done").addEventListener("click", function (e) {
  if (window.OneSignalIamApi && OneSignalIamApi.close) {
    OneSignalIamApi.close(e);
  }
});
オンボーディングフローは短く保ちましょう(最大2-4ステップ)。ユーザーは長いフローではすぐに離脱します。クリックトラッキングで完了率をテストしてください。

ステップ4:アプリ内メッセージをテスト

テストチェックリスト

  1. OneSignalダッシュボードでメッセージを保存
  2. 配信設定を構成:
    • トリガー条件を設定(例:セッション開始、特定のページビュー)
    • ターゲットオーディエンスを選択するか、テストユーザーを選択
  3. テストデバイスに送信:
    • テストユーザーを使用して本番ユーザーに影響を与えずにプレビュー
    • 物理デバイスにアプリをインストール(正確な動作のためシミュレーターより推奨)
  4. 機能を確認:
    • ✓ 最初のカードが正しいコンテンツで表示される
    • ✓ 「次へ」ボタンがカード2に進む
    • ✓ 進行状況ドットが正しく更新される
    • ✓ フェード遷移がスムーズ
    • ✓ 「始める」ボタンがメッセージを閉じる
    • ✓ メッセージがすぐに再表示されない(頻度制限設定を確認)
シミュレーター/エミュレーターは、特にタッチ操作やSDK統合において、実際のデバイスの動作を正確に反映しない場合があります。本番環境にリリースする前に、必ず物理デバイスでテストしてください。

一般的な問題のトラブルシューティング

問題考えられる原因解決策
メッセージが表示されないトリガー条件が満たされていないアプリ内メッセージトリガーを確認し、テストユーザーが条件を満たしているか確認
ボタンが動作しないJavaScriptエラーまたはIDの不一致ブラウザコンソールでエラーを確認;ボタンIDがイベントリスナーIDと一致しているか確認
画像が読み込まれないCORSの問題または無効なURLHTTPS URLを使用;まずブラウザで画像URLをテスト
メッセージは表示されるが閉じないOneSignal SDKが読み込まれていないMobile SDKセットアップが完了しているか確認

次のステップ

ユーザーエンゲージメントをトラッキング:
  • ステップ間の離脱を測定するためにdata-onesignal-unique-label属性を使用してクリックトラッキングを追加(テンプレートに既に含まれています)
  • Messages → In-App Messages → [あなたのメッセージ] → Analyticsでクリック分析を表示
エクスペリエンスをパーソナライズ: 高度なカスタマイズ:
  • 終了後にユーザーを特定の画面にディープリンク
  • Liquid構文を使用してユーザー名や属性で見出しをパーソナライズ
  • 完了率を最適化するために異なるオンボーディングフローでA/Bテストを実装