エグジットインテントのJavaScript実装ガイド|コピペOKのコード付き【2026年版】

サイトを訪れたユーザーが離脱しようとする瞬間、「もう少し見てもらえたら…」と思ったことはありませんか。実は、ユーザーがページを離れる直前にポップアップを表示して、離脱を防ぐ技術があります。それがエグジットインテント(Exit Intent)です。

エグジットインテントをJavaScriptで実装すれば、コストをかけずに離脱率を10〜20%削減し、コンバージョン率を最大46%向上させることが可能です。本記事では、初心者でもコピペで使えるJavaScriptコードから、実践的なカスタマイズ方法まで、エグジットインテントの実装を完全解説します。

この記事を読めば、今日からあなたのサイトに離脱防止ポップアップを導入でき、売上やメール登録数の向上が期待できます。

離脱防止ポップアップのデータプッシュを試して、CV数を向上させませんか?
[今すぐ無料で試してみる]


エグジットインテントとは?基本概念の解説

エグジットインテントの仕組みと動作原理

エグジットインテントは、ユーザーがWebページから離脱しようとする瞬間を検知してポップアップを表示する技術です。主にJavaScriptを使用し、マウスカーソルの動きを追跡することで実現します。

PCブラウザでは、ユーザーがブラウザの「×」ボタンやタブを閉じようとする際、マウスカーソルが画面上部(アドレスバーやタブの方向)へ移動します。JavaScriptのmouseleaveイベントとclientY座標を組み合わせることで、この動きを検知できます。具体的には、カーソルのY座標が一定値(通常10〜50ピクセル)以下になり、かつブラウザ外に出た場合に、離脱意図があると判定します。

検知方法説明使用イベント
マウス移動検知カーソルがブラウザ上端に向かう動きを追跡mouseleave, mouseout
座標判定Y座標が負の値または閾値以下になったことを検知clientY
タイミング制御一定時間滞在後のみ反応させるsetTimeout

この仕組みにより、ユーザーが離脱する「その瞬間」にアプローチできるため、通常のポップアップよりも効果的にユーザーの注意を引くことができます。

エグジットインテントが必要とされる理由と効果

エグジットインテントが必要とされる最大の理由は、高いカート放棄率とページ離脱率の改善です。ECサイトでは平均で約70%のユーザーがカートに商品を入れたまま購入せずに離脱すると言われています。

エグジットインテントポップアップを導入することで、以下のような効果が期待できます。

主な効果:

  • カート放棄率を10〜20%削減
  • コンバージョン率を最大46%向上
  • メール登録数を最大53%回復
  • ROI(投資対効果)200%以上を達成

エグジットインテントが効果的な理由は、離脱しようとする「その瞬間」にアプローチできることにあります。カートに商品を入れたユーザーは、すでに購買意欲が高い状態です。しかし、送料の高さや決済の複雑さなど、何らかの理由で離脱を決めようとしています。このタイミングでクーポンや割引、送料無料などの魅力的なオファーを提示することで、迷っているユーザーの背中を押し、購入完了へと導くことができます。

一般的なポップアップと比較して、エグジットインテントポップアップはユーザー体験を損なわずに表示できるため、離脱率や直帰率への悪影響が少ないという特徴もあります。

一般的な活用シーンとCV率改善事例

エグジットインテントは様々な業種やサイトタイプで活用されており、それぞれのシーンに応じた効果を発揮しています。

主な活用シーン:

  1. ECサイトでのカート放棄防止
    • クーポンコードや送料無料オファーの提示
    • 期間限定の割引キャンペーン告知
    • 在庫残りわずか通知による購買促進
  2. BtoBサイトでのリード獲得
    • ホワイトペーパーや資料ダウンロードの提供
    • 無料トライアルやデモ予約への誘導
    • メールマガジン登録の促進
  3. メディアサイトでのエンゲージメント向上
    • 関連記事のレコメンド表示
    • プッシュ通知の許可リクエスト
    • 会員登録やニュースレター購読の促進

実際の改善事例:

業種施策内容改善結果
アパレルEC初回購入10%OFFクーポン提示CV率35%向上
SaaS企業無料トライアル期間延長オファー登録率28%増加
教育サイト無料eBook提供メール登録53%回復
旅行予約サイトタイムセール通知予約完了率18%改善

これらの事例から分かるように、エグジットインテントは適切なオファーとタイミングで表示することで、大幅なコンバージョン率の改善を実現できる強力なマーケティング手法です。


エグジットインテントの基本実装コード【コピペOK】

HTMLの基本構造とポップアップ要素の作成

エグジットインテントポップアップを実装する最初のステップは、HTMLで表示するポップアップの構造を作成することです。以下のコードをHTMLファイルの<body>タグ内にコピー&ペーストしてください。

<!-- ポップアップのHTML構造 -->
<div id="exit-popup-overlay" style="display: none;">
  <div id="exit-popup-content">
    <button id="exit-popup-close" class="close-btn">&times;</button>
    <h2>ちょっと待ってください!</h2>
    <p>今なら初回限定10%OFFクーポンをプレゼント中!</p>
    <form id="exit-popup-form">
      <input type="email" placeholder="メールアドレスを入力" required>
      <button type="submit">クーポンを受け取る</button>
    </form>
  </div>
</div>

HTML構造の説明:

要素役割説明
#exit-popup-overlay背景オーバーレイ画面全体を覆う半透明の背景
#exit-popup-contentポップアップ本体実際に表示されるコンテンツ領域
#exit-popup-close閉じるボタンユーザーがポップアップを閉じるためのボタン

このHTMLは、デフォルトでdisplay: none;により非表示になっています。JavaScriptがエグジットインテントを検知した際に、この要素を表示させる仕組みです。初期状態で非表示にすることで、ページ読み込み時にポップアップが表示されることを防ぎます。

フォーム部分は任意のコンテンツに変更可能です。クーポン提供ではなく、資料ダウンロードや関連記事の表示など、サイトの目的に応じてカスタマイズしてください。

JavaScriptコードの全文解説

エグジットインテントを検知し、ポップアップを表示するJavaScriptコードです。以下のコードを<script>タグ内、またはJavaScriptファイルに記述してください。

// エグジットインテント検知の実装
document.addEventListener('DOMContentLoaded', function() {
  // 要素の取得
  const popup = document.getElementById('exit-popup-overlay');
  const closeBtn = document.getElementById('exit-popup-close');
  let popupShown = false; // ポップアップ表示フラグ
  let exitIntentActive = false; // エグジットインテント有効化フラグ
  
  // 3秒後にエグジットインテント検知を有効化
  setTimeout(function() {
    exitIntentActive = true;
  }, 3000);
  
  // マウスがドキュメントから離れたときのイベント
  document.addEventListener('mouseleave', function(e) {
    // Y座標が負(画面上端より上)で、まだ表示していない場合
    if (e.clientY < 0 && !popupShown && exitIntentActive) {
      popup.style.display = 'flex';
      popupShown = true;
      
      // Cookieに表示履歴を保存(24時間)
      setCookie('exitPopupShown', 'true', 1);
    }
  });
  
  // 閉じるボタンのクリックイベント
  closeBtn.addEventListener('click', function() {
    popup.style.display = 'none';
  });
  
  // オーバーレイクリックで閉じる
  popup.addEventListener('click', function(e) {
    if (e.target === popup) {
      popup.style.display = 'none';
    }
  });
  
  // Cookie管理関数
  function setCookie(name, value, days) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    const expires = "expires=" + date.toUTCString();
    document.cookie = name + "=" + value + ";" + expires + ";path=/";
  }
  
  function getCookie(name) {
    const nameEQ = name + "=";
    const ca = document.cookie.split(';');
    for(let i = 0; i < ca.length; i++) {
      let c = ca[i];
      while (c.charAt(0) == ' ') c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
  }
  
  // Cookieチェック:既に表示済みなら何もしない
  if (getCookie('exitPopupShown')) {
    exitIntentActive = false;
  }
});

コードの動作フロー:

  1. ページ読み込み完了後、3秒間の待機時間を設定
  2. mouseleaveイベントでマウスの動きを監視
  3. マウスY座標が負の値(画面上端より上)になったら離脱意図と判定
  4. ポップアップを表示し、Cookieに記録
  5. 一度表示したら、24時間は再表示しない

このコードをコピー&ペーストするだけで、基本的なエグジットインテント機能が実装できます。

CSSスタイリングの実装例

ポップアップを見やすく魅力的にするためのCSS実装例です。以下のコードを<style>タグ内、またはCSSファイルに記述してください。

/* ポップアップオーバーレイ */
#exit-popup-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: none;
  justify-content: center;
  align-items: center;
  z-index: 9999;
  animation: fadeIn 0.3s ease-in-out;
}

/* ポップアップコンテンツ */
#exit-popup-content {
  background: #fff;
  padding: 40px;
  border-radius: 12px;
  max-width: 500px;
  width: 90%;
  position: relative;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  animation: slideUp 0.4s ease-out;
}

/* 閉じるボタン */
#exit-popup-close {
  position: absolute;
  top: 15px;
  right: 15px;
  background: none;
  border: none;
  font-size: 28px;
  cursor: pointer;
  color: #999;
  line-height: 1;
  transition: color 0.3s;
}

#exit-popup-close:hover {
  color: #333;
}

/* 見出し */
#exit-popup-content h2 {
  margin: 0 0 15px 0;
  font-size: 26px;
  color: #333;
}

/* テキスト */
#exit-popup-content p {
  margin: 0 0 25px 0;
  font-size: 16px;
  color: #666;
  line-height: 1.6;
}

/* フォーム */
#exit-popup-form input[type="email"] {
  width: 100%;
  padding: 12px 15px;
  border: 2px solid #ddd;
  border-radius: 6px;
  font-size: 16px;
  margin-bottom: 15px;
  box-sizing: border-box;
  transition: border-color 0.3s;
}

#exit-popup-form input[type="email"]:focus {
  outline: none;
  border-color: #4CAF50;
}

#exit-popup-form button[type="submit"] {
  width: 100%;
  padding: 14px;
  background-color: #4CAF50;
  color: #fff;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  font-weight: bold;
  cursor: pointer;
  transition: background-color 0.3s;
}

#exit-popup-form button[type="submit"]:hover {
  background-color: #45a049;
}

/* アニメーション */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes slideUp {
  from {
    transform: translateY(50px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

/* レスポンシブ対応 */
@media (max-width: 600px) {
  #exit-popup-content {
    padding: 30px 20px;
    width: 95%;
  }
  
  #exit-popup-content h2 {
    font-size: 22px;
  }
}

CSSの主なポイント:

  • position: fixedで画面に固定表示
  • z-index: 9999で最前面に配置
  • フェードインとスライドアップのアニメーション効果
  • レスポンシブ対応でスマートフォンでも見やすく表示
  • ホバー効果で操作性を向上

このCSSにより、プロフェッショナルな見た目のポップアップが実現します。色やサイズは自由にカスタマイズ可能です。

離脱率を15%改善を目指す!
DataPushデータプッシュを無料トライアル
■無料で利用可能 ■A/Bテスト可能 ■ノーコードで設定
今すぐ試す →

動作確認とデバッグ方法

実装したエグジットインテントが正しく動作するか確認する方法を解説します。確実な動作確認により、本番環境でのトラブルを未然に防ぐことができます。

基本的な動作確認手順:

  1. ブラウザでHTMLファイルを開く
    • 実装したファイルをブラウザで表示
    • 3秒間待機してエグジットインテントを有効化
  2. マウスカーソルを画面上端に移動
    • カーソルをブラウザのタブやアドレスバーに向けて素早く移動
    • ポップアップが表示されることを確認
  3. 閉じる動作の確認
    • ×ボタンで閉じられるか確認
    • オーバーレイ(背景)クリックで閉じられるか確認

開発者ツールを使ったデバッグ方法:

// デバッグ用のコンソール出力を追加
document.addEventListener('mouseleave', function(e) {
  console.log('マウス離脱検知:', e.clientY); // Y座標を確認
  
  if (e.clientY < 0 && !popupShown && exitIntentActive) {
    console.log('ポップアップ表示条件達成');
    popup.style.display = 'flex';
    popupShown = true;
  }
});

よくある問題と解決方法:

問題原因解決方法
ポップアップが表示されない要素IDの不一致HTML/JavaScriptのID名を確認
すぐに表示されてしまう待機時間が短いsetTimeoutの値を増やす
何度も表示されるCookie保存が動作していないCookie関数の動作を確認
モバイルで動作しないmouseleaveイベント未対応タッチイベントの追加実装が必要

ブラウザ互換性の確認:

実装したコードは以下のブラウザで動作確認を行ってください。

  • Google Chrome(最新版)
  • Firefox(最新版)
  • Safari(最新版)
  • Microsoft Edge(最新版)

デバッグ時は必ずブラウザの開発者ツールのコンソールを開き、エラーメッセージが出ていないか確認することが重要です。


エグジットインテント実装の詳細解説

mouseleaveイベントの仕組みと判定ロジック

mouseleaveイベントは、マウスポインタが特定の要素から離れたときに発火するJavaScriptイベントです。エグジットインテント実装では、このイベントをdocument全体に適用することで、ブラウザウィンドウからマウスが離れる瞬間を検知します。

mouseleaveとmouseoutの違い:

イベント特徴エグジットインテント実装での適性
mouseleave指定要素から離れたときのみ発火(バブリングしない)◎最適
mouseout子要素に移動してもイベント発火(バブリングする)△誤検知が多い

エグジットインテント実装ではmouseleaveを使用する理由は、バブリング(イベントの伝播)が発生しないため、ドキュメント全体からマウスが離れた瞬間だけを正確に検知できるからです。

判定ロジックの詳細:

document.addEventListener('mouseleave', function(e) {
  // 条件1: Y座標が負の値(画面上端より上)
  const isMovingUp = e.clientY < 0;
  
  // 条件2: まだポップアップを表示していない
  const notYetShown = !popupShown;
  
  // 条件3: エグジットインテント機能が有効化されている
  const isActive = exitIntentActive;
  
  // すべての条件を満たした場合のみポップアップ表示
  if (isMovingUp && notYetShown && isActive) {
    showPopup();
  }
});

この判定ロジックにより、ユーザーがタブを閉じようとする、別のタブに移動しようとする、アドレスバーにアクセスしようとするといった「離脱の兆候」を的確に捉えることができます。

マウス速度による誤検知を防ぐため、一定時間内に複数回イベントが発火しないよう、デバウンス(debounce)処理を追加することも推奨されます。

clientYによる座標判定の実装方法

clientYプロパティは、ブラウザのビューポート(表示領域)内におけるマウスカーソルのY座標(垂直位置)を取得します。エグジットインテント実装では、この座標値を使って「マウスがブラウザ上端を越えたか」を判定します。

clientYの座標系:

Y座標 = -10px  ← ブラウザ外(タブやアドレスバー)
━━━━━━━━━━━━━━━━━━━━━━━
Y座標 = 0px    ← ブラウザの最上端
Y座標 = 50px   ← ページコンテンツ内
Y座標 = 100px
...

実装コードの詳細:

document.addEventListener('mouseleave', function(e) {
  // clientYが負の値 = マウスがブラウザ上端より上に移動
  if (e.clientY < 0) {
    console.log('離脱意図を検知:', e.clientY);
    // ポップアップ表示処理
  }
  
  // より厳密な判定:一定の閾値を設定
  const threshold = -10; // -10px以上上に移動した場合
  if (e.clientY < threshold) {
    // 誤検知を減らした判定
  }
});

座標判定のカスタマイズ例:

閾値(しきいち)を調整することで、検知の感度を変更できます。

閾値設定動作使用シーン
e.clientY < 0わずかでも上端を越えたら検知標準的な実装
e.clientY < -1010px以上上に移動したら検知誤検知を減らしたい場合
e.clientY < -5050px以上上に移動したら検知確実な離脱意図のみ検知

また、X座標(clientX)も組み合わせることで、より精度の高い判定が可能になります。例えば、ブラウザの閉じるボタンがある右上の領域に限定して検知するといった実装も可能です。

座標判定は環境によって挙動が異なる場合があるため、実際の動作環境で十分にテストすることが重要です。

ポップアップの表示/非表示制御

ポップアップの表示と非表示を適切に制御することで、ユーザー体験を損なわず、効果的なエグジットインテント機能を実現できます。制御方法には複数のアプローチがあり、サイトの要件に応じて最適な方法を選択します。

基本的な表示制御:

// ポップアップ表示
function showPopup() {
  const popup = document.getElementById('exit-popup-overlay');
  popup.style.display = 'flex'; // または 'block'
  document.body.style.overflow = 'hidden'; // 背景のスクロールを無効化
}

// ポップアップ非表示
function hidePopup() {
  const popup = document.getElementById('exit-popup-overlay');
  popup.style.display = 'none';
  document.body.style.overflow = ''; // スクロールを元に戻す
}

CSSクラスを使った制御(推奨):

.popup-hidden {
  display: none;
}

.popup-visible {
  display: flex;
  animation: fadeIn 0.3s ease-in-out;
}
// クラスによる制御
function showPopup() {
  popup.classList.remove('popup-hidden');
  popup.classList.add('popup-visible');
}

function hidePopup() {
  popup.classList.remove('popup-visible');
  popup.classList.add('popup-hidden');
}

段階的な表示制御:

ポップアップの表示と非表示を段階的に制御することで、より滑らかなユーザー体験を提供できます。

制御タイミング実装方法効果
即座に表示display切り替えのみシンプルだが唐突
フェードインopacityアニメーション自然な表示
スライドインtransformアニメーション動きのある表示
遅延表示setTimeout併用ユーザーに考える時間を与える

閉じる動作の実装パターン:

// パターン1: 閉じるボタン
closeBtn.addEventListener('click', hidePopup);

// パターン2: オーバーレイクリック
popup.addEventListener('click', function(e) {
  if (e.target === popup) { // 背景部分のクリック
    hidePopup();
  }
});

// パターン3: ESCキーで閉じる
document.addEventListener('keydown', function(e) {
  if (e.key === 'Escape' && popup.classList.contains('popup-visible')) {
    hidePopup();
  }
});

// パターン4: 一定時間後に自動で閉じる
setTimeout(function() {
  if (popup.classList.contains('popup-visible')) {
    hidePopup();
  }
}, 30000); // 30秒後

表示制御の実装では、ユーザーがポップアップを簡単に閉じられることが重要です。複数の閉じる方法を提供することで、ユーザビリティが向上します。

1回のみ表示させるためのフラグ管理

エグジットインテントポップアップを同じユーザーに何度も表示すると、ユーザー体験を損ない、サイトからの離脱を促進してしまいます。1回のみ表示させる制御は、JavaScriptのフラグ変数とCookieやLocalStorageを組み合わせて実装します。

セッション内での制御(ページリロードで再表示):

let popupShown = false; // フラグ変数

document.addEventListener('mouseleave', function(e) {
  if (e.clientY < 0 && !popupShown) {
    showPopup();
    popupShown = true; // フラグをtrueに設定
  }
});

この方法は最もシンプルですが、ユーザーがページをリロードすると再度表示されてしまいます。

Cookieによる永続的な制御(推奨):

// Cookie設定関数
function setCookie(name, value, days) {
  const date = new Date();
  date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
  const expires = "expires=" + date.toUTCString();
  document.cookie = name + "=" + value + ";" + expires + ";path=/";
}

// Cookie取得関数
function getCookie(name) {
  const nameEQ = name + "=";
  const ca = document.cookie.split(';');
  for(let i = 0; i < ca.length; i++) {
    let c = ca[i].trim();
    if (c.indexOf(nameEQ) == 0) {
      return c.substring(nameEQ.length);
    }
  }
  return null;
}

// 実装例
if (!getCookie('exitPopupShown')) {
  document.addEventListener('mouseleave', function(e) {
    if (e.clientY < 0 && !popupShown) {
      showPopup();
      popupShown = true;
      setCookie('exitPopupShown', 'true', 7); // 7日間有効
    }
  });
}

LocalStorageによる制御:

// LocalStorageチェック
if (!localStorage.getItem('exitPopupShown')) {
  document.addEventListener('mouseleave', function(e) {
    if (e.clientY < 0 && !popupShown) {
      showPopup();
      popupShown = true;
      localStorage.setItem('exitPopupShown', 'true');
      
      // 期限付きで保存する場合
      const expiryDate = new Date().getTime() + (7 * 24 * 60 * 60 * 1000);
      localStorage.setItem('exitPopupExpiry', expiryDate);
    }
  });
  
  // 期限チェック
  const expiry = localStorage.getItem('exitPopupExpiry');
  if (expiry && new Date().getTime() > parseInt(expiry)) {
    localStorage.removeItem('exitPopupShown');
    localStorage.removeItem('exitPopupExpiry');
  }
}

各保存方法の比較:

方法メリットデメリット推奨用途
フラグ変数のみ実装が簡単ページリロードで再表示テスト環境
Cookieドメイン間で共有可能ユーザーが無効化可能一般的な実装
LocalStorageデータ容量が大きいドメイン毎に独立複雑な条件管理

効果的なフラグ管理により、ユーザーに過度な干渉を与えず、適切なタイミングでポップアップを表示できます。


実用的なカスタマイズ方法

Cookie/LocalStorageを使った永続的な表示制御

Cookieまたは LocalStorageを活用することで、ブラウザを閉じた後も「ポップアップを表示済み」という情報を保持し、同じユーザーに繰り返しポップアップを表示することを防げます。どちらを使用するかは、サイトの要件によって選択します。

高度なCookie制御の実装:

// 拡張Cookie管理クラス
const PopupStorage = {
  // Cookie保存
  saveCookie: function(name, value, days, sameSite = 'Lax') {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    const expires = "expires=" + date.toUTCString();
    const secure = location.protocol === 'https:' ? ';secure' : '';
    document.cookie = `${name}=${value};${expires};path=/;SameSite=${sameSite}${secure}`;
  },
  
  // Cookie取得
  getCookie: function(name) {
    const nameEQ = name + "=";
    const cookies = document.cookie.split(';');
    for(let cookie of cookies) {
      cookie = cookie.trim();
      if (cookie.indexOf(nameEQ) === 0) {
        return cookie.substring(nameEQ.length);
      }
    }
    return null;
  },
  
  // Cookie削除
  deleteCookie: function(name) {
    document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;';
  },
  
  // 複数条件の保存
  saveMultiple: function(data, days) {
    this.saveCookie('exitPopupData', JSON.stringify(data), days);
  },
  
  // 複数条件の取得
  getMultiple: function() {
    const data = this.getCookie('exitPopupData');
    return data ? JSON.parse(data) : null;
  }
};

// 使用例:複数の条件を管理
const popupData = {
  shown: true,
  timestamp: new Date().getTime(),
  pageUrl: window.location.href,
  count: 1
};
PopupStorage.saveMultiple(popupData, 30);

LocalStorageによる詳細な制御:

// LocalStorage管理クラス
const PopupLocalStorage = {
  // データ保存
  save: function(key, value, expiryDays) {
    const data = {
      value: value,
      expiry: expiryDays ? new Date().getTime() + (expiryDays * 24 * 60 * 60 * 1000) : null,
      savedAt: new Date().toISOString()
    };
    localStorage.setItem(key, JSON.stringify(data));
  },
  
  // データ取得
  get: function(key) {
    const item = localStorage.getItem(key);
    if (!item) return null;
    
    const data = JSON.parse(item);
    
    // 有効期限チェック
    if (data.expiry && new Date().getTime() > data.expiry) {
      this.remove(key);
      return null;
    }
    
    return data.value;
  },
  
  // データ削除
  remove: function(key) {
    localStorage.removeItem(key);
  },
  
  // 全データ削除
  clear: function() {
    localStorage.clear();
  },
  
  // 表示回数のカウント
  incrementViewCount: function() {
    const current = this.get('popupViewCount') || 0;
    this.save('popupViewCount', current + 1, 30);
    return current + 1;
  }
};

// 使用例
PopupLocalStorage.save('exitPopupShown', true, 7);
if (PopupLocalStorage.get('exitPopupShown')) {
  console.log('既に表示済み');
}

Cookie vs LocalStorage の選択基準:

項目CookieLocalStorage
保存容量約4KB約5-10MB
サーバー送信毎回自動送信送信されない
有効期限設定可能手動管理必要
サブドメイン共有可能不可
セキュリティSecure/SameSite設定可XSS脆弱性注意

一般的なエグジットインテント実装では、シンプルなCookieで十分ですが、詳細な分析データを保存したい場合はLocalStorageが適しています。

タイマー設定で即時表示を防ぐ実装

ページにアクセスした直後にエグジットインテントが反応してしまうと、ユーザー体験を大きく損ないます。タイマー設定により、一定時間経過後またはユーザーがコンテンツを閲覧した後にのみ、エグジットインテント機能を有効化する実装が推奨されます。

基本的なタイマー実装:

document.addEventListener('DOMContentLoaded', function() {
  let exitIntentActive = false;
  
  // 5秒後にエグジットインテント有効化
  setTimeout(function() {
    exitIntentActive = true;
    console.log('エグジットインテント有効化');
  }, 5000);
  
  document.addEventListener('mouseleave', function(e) {
    if (e.clientY < 0 && exitIntentActive && !popupShown) {
      showPopup();
    }
  });
});

段階的な有効化の実装:

// 複数条件による有効化
const ExitIntentController = {
  isActive: false,
  conditions: {
    timeElapsed: false,
    scrolled: false,
    interacted: false
  },
  
  // 5秒経過をチェック
  checkTime: function() {
    setTimeout(() => {
      this.conditions.timeElapsed = true;
      this.updateStatus();
    }, 5000);
  },
  
  // スクロール量をチェック(50%以上)
  checkScroll: function() {
    window.addEventListener('scroll', () => {
      const scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
      if (scrollPercent > 50) {
        this.conditions.scrolled = true;
        this.updateStatus();
      }
    });
  },
  
  // ユーザーの操作をチェック
  checkInteraction: function() {
    const events = ['click', 'mousemove', 'keypress'];
    const handler = () => {
      this.conditions.interacted = true;
      this.updateStatus();
      events.forEach(event => document.removeEventListener(event, handler));
    };
    events.forEach(event => document.addEventListener(event, handler));
  },
  
  // 条件確認と有効化
  updateStatus: function() {
    // すべての条件を満たしたら有効化
    if (this.conditions.timeElapsed && this.conditions.scrolled) {
      this.isActive = true;
      console.log('すべての条件を満たしました:エグジットインテント有効');
    }
  },
  
  // 初期化
  init: function() {
    this.checkTime();
    this.checkScroll();
    this.checkInteraction();
  }
};

// 使用例
ExitIntentController.init();

遅延時間の設定ガイドライン:

サイトタイプ推奨遅延時間理由
ブログ/メディア10-15秒コンテンツを読む時間を確保
EC商品ページ5-10秒商品情報の確認時間
ランディングページ3-5秒短時間で離脱判断される
SaaS製品ページ15-20秒詳細情報の理解に時間が必要

タイマー設定により、ユーザーがコンテンツを実際に閲覧した後にのみポップアップが表示されるため、ユーザー体験を損なわず、コンバージョン率の向上が期待できます。

スクロール率と組み合わせた条件分岐

エグジットインテントとスクロール率を組み合わせることで、「コンテンツに興味を持っているユーザー」だけにポップアップを表示できます。この方法により、ターゲットを絞った効果的なアプローチが可能になります。

スクロール率の計算と判定:

// スクロール率を計算する関数
function getScrollPercent() {
  const scrollTop = window.scrollY;
  const docHeight = document.documentElement.scrollHeight;
  const winHeight = window.innerHeight;
  const scrollPercent = (scrollTop / (docHeight - winHeight)) * 100;
  return Math.min(100, Math.round(scrollPercent));
}

// スクロール率によるエグジットインテント制御
document.addEventListener('DOMContentLoaded', function() {
  let exitIntentEnabled = false;
  let popupShown = false;
  const minimumScrollPercent = 50; // 50%以上スクロールで有効化
  
  // スクロールイベントの監視
  window.addEventListener('scroll', function() {
    const currentScroll = getScrollPercent();
    
    if (currentScroll >= minimumScrollPercent && !exitIntentEnabled) {
      exitIntentEnabled = true;
      console.log('スクロール50%達成:エグジットインテント有効化');
    }
  });
  
  // エグジットインテント検知
  document.addEventListener('mouseleave', function(e) {
    if (e.clientY < 0 && exitIntentEnabled && !popupShown) {
      showPopup();
      popupShown = true;
    }
  });
});

段階的なスクロール条件の実装:

// スクロール深度による段階的制御
const ScrollBasedExitIntent = {
  scrollMilestones: {
    low: { percent: 25, message: '基本オファー', reached: false },
    medium: { percent: 50, message: 'プレミアムオファー', reached: false },
    high: { percent: 75, message: 'VIPオファー', reached: false }
  },
  
  currentMilestone: null,
  
  // スクロール監視
  trackScroll: function() {
    window.addEventListener('scroll', () => {
      const scrollPercent = getScrollPercent();
      
      // マイルストーンの更新
      if (scrollPercent >= 75 && !this.scrollMilestones.high.reached) {
        this.scrollMilestones.high.reached = true;
        this.currentMilestone = 'high';
      } else if (scrollPercent >= 50 && !this.scrollMilestones.medium.reached) {
        this.scrollMilestones.medium.reached = true;
        this.currentMilestone = 'medium';
      } else if (scrollPercent >= 25 && !this.scrollMilestones.low.reached) {
        this.scrollMilestones.low.reached = true;
        this.currentMilestone = 'low';
      }
    });
  },
  
  // マイルストーンに応じたポップアップ表示
  showAppropriatePopup: function() {
    const milestone = this.scrollMilestones[this.currentMilestone];
    if (milestone) {
      // マイルストーンに応じた内容を表示
      document.getElementById('popup-message').textContent = milestone.message;
      showPopup();
    }
  }
};

スクロール条件の活用シーン:

スクロール率ユーザー行動の推測表示する内容
0-25%ページを眺めている段階エグジットインテント無効
25-50%コンテンツに興味あり基本的なオファー
50-75%本格的に検討中割引クーポンなど
75-100%高い関心度限定特典やVIPオファー

スクロール率との組み合わせにより、ユーザーのエンゲージメントレベルに応じた最適なタイミングでポップアップを表示でき、コンバージョン率の向上が期待できます。

離脱率を15%改善を目指す!
DataPushデータプッシュを無料トライアル
■無料で利用可能 ■A/Bテスト可能 ■ノーコードで設定
今すぐ試す →

特定ページでのみ表示させる方法

すべてのページでエグジットインテントを表示するのではなく、商品ページやランディングページなど、特定のページだけで表示させることで、より戦略的なマーケティングが可能になります。

URLパスによる制御:

// 特定パスでのみエグジットインテント有効化
const ExitIntentPageControl = {
  // 表示を許可するページパスの配列
  allowedPaths: [
    '/product/',
    '/pricing/',
    '/landing-page/',
    '/special-offer/'
  ],
  
  // 除外するページパスの配列
  excludedPaths: [
    '/checkout/',
    '/cart/',
    '/thank-you/',
    '/account/'
  ],
  
  // 現在のパスをチェック
  isAllowedPage: function() {
    const currentPath = window.location.pathname;
    
    // 除外パスに含まれていないかチェック
    const isExcluded = this.excludedPaths.some(path => 
      currentPath.includes(path)
    );
    
    if (isExcluded) return false;
    
    // 許可パスに含まれているかチェック
    const isAllowed = this.allowedPaths.some(path => 
      currentPath.includes(path)
    );
    
    return isAllowed;
  },
  
  // 初期化
  init: function() {
    if (this.isAllowedPage()) {
      console.log('このページではエグジットインテント有効');
      initExitIntent(); // エグジットインテント初期化関数を呼び出し
    } else {
      console.log('このページではエグジットインテント無効');
    }
  }
};

// ページ読み込み時に実行
document.addEventListener('DOMContentLoaded', function() {
  ExitIntentPageControl.init();
});

ページタイプや属性による制御:

// data属性を使った制御
// HTML: <body data-exit-popup="enabled">

function shouldShowExitIntent() {
  const body = document.body;
  
  // data-exit-popup属性をチェック
  if (body.dataset.exitPopup === 'enabled') {
    return true;
  }
  
  // 特定のクラスをチェック
  if (body.classList.contains('product-page') || 
      body.classList.contains('landing-page')) {
    return true;
  }
  
  // meta tagをチェック
  const metaTag = document.querySelector('meta[name="exit-popup"]');
  if (metaTag && metaTag.content === 'true') {
    return true;
  }
  
  return false;
}

if (shouldShowExitIntent()) {
  initExitIntent();
}

カテゴリー別の表示制御:

// URLパラメータやカテゴリーによる制御
const CategoryBasedControl = {
  // カテゴリー別の設定
  categorySettings: {
    'electronics': {
      enabled: true,
      message: '電子機器が10%OFF!'
    },
    'clothing': {
      enabled: true,
      message: 'アパレル商品が送料無料!'
    },
    'books': {
      enabled: false
    }
  },
  
  // 現在のカテゴリーを取得
  getCurrentCategory: function() {
    // URLから取得する例
    const pathParts = window.location.pathname.split('/');
    return pathParts[2]; // /shop/electronics/product のような構造を想定
  },
  
  // カテゴリー設定を取得
  getCategoryConfig: function() {
    const category = this.getCurrentCategory();
    return this.categorySettings || { enabled: false };
  },
  
  // 初期化
  init: function() {
    const config = this.getCategoryConfig();
    if (config.enabled) {
      // カテゴリー別のメッセージを設定
      document.getElementById('popup-message').textContent = config.message;
      initExitIntent();
    }
  }
};

ページ制御の実装パターン:

制御方法メリットデメリット使用シーン
URLパスシンプルで明確URLが変わると動作しない静的なサイト構造
data属性HTML側で制御可能HTML修正が必要CMSサイト
クラス名CSSとの統一感命名規則の管理必要テンプレートベース
URLパラメータ動的な制御が可能パラメータ依存キャンペーンページ

特定ページでの表示制御により、サイト全体のユーザー体験を保ちながら、重要なページでのみエグジットインテントを活用できます。


モバイル対応のエグジットインテント実装

モバイルでのエグジットインテント検知の課題

モバイルデバイスでは、PCブラウザと異なりマウスカーソルが存在しないため、従来のmouseleaveイベントによるエグジットインテント検知が機能しません。モバイル特有の課題を理解し、適切な代替手段を実装する必要があります。

モバイルでの主な課題:

モバイルブラウザでは、ユーザーがページを離れる際の明確な「離脱の兆候」を検知することが困難です。PCのようにマウスがブラウザ上端に移動するという動作がないため、別のアプローチが必要になります。

PC環境モバイル環境影響
マウスカーソルで離脱意図を検知マウスカーソルが存在しないmouseleaveイベントが使えない
ブラウザ上端への移動で判定タップ操作のみ座標判定が不可能
複数のブラウザタブアプリの切り替え検知タイミングが異なる

モバイル環境で検知可能な離脱シグナル:

// モバイル環境の判定
function isMobileDevice() {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

// モバイルでの離脱シグナル例
const MobileExitSignals = {
  // バックボタンのタップ
  backButton: 'popstate',
  
  // ページの非表示(アプリ切り替え)
  pageHide: 'visibilitychange',
  
  // ブラウザのぼかし(フォーカス喪失)
  browserBlur: 'blur',
  
  // スクロールの停止(一定時間の非アクティブ)
  scrollStop: 'custom',
  
  // 画面上部へのスワイプ
  swipeUp: 'touchend'
};

モバイルでの代替戦略:

モバイル環境では、エグジットインテントの概念を「ユーザーのエンゲージメント低下」として捉え直す必要があります。以下のような代替指標を使用します。

  • 時間ベース: 一定時間(例:30秒)の非アクティブ状態
  • スクロール深度: ページの一定割合(例:70%)までスクロール後
  • タップ回数: 一定回数のインタラクション後
  • ページ遷移前: 別ページへのリンクタップ時

これらの指標により、モバイルでも効果的なタイミングでポップアップを表示することが可能になります。ただし、PCのエグジットインテントほど「離脱の瞬間」を正確に捉えることは困難なため、表示タイミングの最適化が重要です。

離脱防止ポップアップならのDataPush

✓ 設定わずか5分で完了
✓ クレジットカード登録不要
✓ シンプルなUIとシンプル管理が可能
✓ A/Bテストで計測可能

無料でも利用可能でシンプルな料金体系
DataPushを無料で試す →

touchendイベントを使った実装方法

モバイルデバイスでは、タッチイベントを活用することで、ユーザーの離脱意図を部分的に検知できます。特にtouchendイベントと画面の座標を組み合わせることで、画面上部へのスワイプ動作を検知する実装が可能です。

基本的なtouchendイベントの実装:

// モバイル向けエグジットインテント実装
document.addEventListener('DOMContentLoaded', function() {
  const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  
  if (isMobile) {
    let touchStartY = 0;
    let touchEndY = 0;
    let popupShown = false;
    
    // タッチ開始位置を記録
    document.addEventListener('touchstart', function(e) {
      touchStartY = e.touches[0].clientY;
    }, { passive: true });
    
    // タッチ終了時の処理
    document.addEventListener('touchend', function(e) {
      touchEndY = e.changedTouches[0].clientY;
      
      // 上方向へのスワイプを検知(50px以上)
      const swipeDistance = touchStartY - touchEndY;
      const isSwipeUp = swipeDistance > 50;
      const isNearTop = touchEndY < 100; // 画面上部100px以内
      
      if (isSwipeUp && isNearTop && !popupShown) {
        setTimeout(function() {
          showPopup();
          popupShown = true;
        }, 500); // 少し遅延させて自然な表示
      }
    }, { passive: true });
  }
});

スクロール位置を考慮した実装:

// より精度の高いモバイルエグジットインテント
const MobileExitIntent = {
  config: {
    swipeThreshold: 50,        // スワイプの最小距離(px)
    topAreaHeight: 100,        // 画面上部の判定範囲(px)
    scrollThreshold: 50,       // スクロール深度(%)
    inactivityTime: 20000,     // 非アクティブ時間(ms)
    enabled: false,
    popupShown: false
  },
  
  touchStartY: 0,
  touchEndY: 0,
  lastActivityTime: Date.now(),
  
  // 初期化
  init: function() {
    if (!this.isMobile()) return;
    
    this.setupTouchEvents();
    this.setupScrollTracking();
    this.setupInactivityTracking();
  },
  
  // モバイル判定
  isMobile: function() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  },
  
  // タッチイベントの設定
  setupTouchEvents: function() {
    document.addEventListener('touchstart', (e) => {
      this.touchStartY = e.touches[0].clientY;
      this.lastActivityTime = Date.now();
    }, { passive: true });
    
    document.addEventListener('touchend', (e) => {
      this.touchEndY = e.changedTouches[0].clientY;
      this.lastActivityTime = Date.now();
      this.checkSwipeGesture();
    }, { passive: true });
    
    document.addEventListener('touchmove', () => {
      this.lastActivityTime = Date.now();
    }, { passive: true });
  },
  
  // スワイプジェスチャーの判定
  checkSwipeGesture: function() {
    if (!this.config.enabled || this.config.popupShown) return;
    
    const swipeDistance = this.touchStartY - this.touchEndY;
    const isSwipeUp = swipeDistance > this.config.swipeThreshold;
    const isNearTop = this.touchEndY < this.config.topAreaHeight;
    
    if (isSwipeUp && isNearTop) {
      this.showPopup();
    }
  },
  
  // スクロール追跡
  setupScrollTracking: function() {
    window.addEventListener('scroll', () => {
      this.lastActivityTime = Date.now();
      const scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
      
      if (scrollPercent > this.config.scrollThreshold) {
        this.config.enabled = true;
      }
    }, { passive: true });
  },
  
  // 非アクティブ追跡
  setupInactivityTracking: function() {
    setInterval(() => {
      const inactiveTime = Date.now() - this.lastActivityTime;
      if (inactiveTime > this.config.inactivityTime && this.config.enabled && !this.config.popupShown) {
        this.showPopup();
      }
    }, 5000);
  },
  
  // ポップアップ表示
  showPopup: function() {
    if (this.config.popupShown) return;
    
    console.log('モバイルエグジットインテント発火');
    document.getElementById('exit-popup-overlay').style.display = 'flex';
    this.config.popupShown = true;
    
    // Cookie保存
    this.setCookie('mobileExitPopupShown', 'true', 7);
  },
  
  // Cookie管理
  setCookie: function(name, value, days) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`;
  }
};

// 初期化
document.addEventListener('DOMContentLoaded', function() {
  MobileExitIntent.init();
});

touchendイベントの注意点:

項目説明対策
誤検知通常のスクロールも検知されるスワイプ距離と位置の閾値を設定
パフォーマンスイベントが頻繁に発火passive: trueオプションを使用
ブラウザ互換性一部の古いブラウザで未対応フォールバック処理を実装

touchendイベントを使った実装により、モバイル環境でも一定のエグジットインテント機能を提供できますが、PC環境ほどの精度は期待できないため、他の条件と組み合わせた総合的なアプローチが推奨されます。

レスポンシブデザインの調整ポイント

エグジットインテントポップアップをモバイルで表示する際、デザインとUXの最適化が不可欠です。画面サイズが小さいモバイルデバイスでは、PCと同じデザインでは見づらく、操作しにくくなります。

モバイル向けCSSの実装:

/* 基本のポップアップスタイル */
#exit-popup-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;
}

#exit-popup-content {
  background: #fff;
  padding: 40px;
  border-radius: 12px;
  max-width: 500px;
  width: 90%;
  position: relative;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}

/* タブレット向け調整(768px以下) */
@media (max-width: 768px) {
  #exit-popup-content {
    padding: 30px 25px;
    max-width: 90%;
    border-radius: 10px;
  }
  
  #exit-popup-content h2 {
    font-size: 22px;
    margin-bottom: 12px;
  }
  
  #exit-popup-content p {
    font-size: 15px;
    line-height: 1.5;
  }
}

/* スマートフォン向け調整(480px以下) */
@media (max-width: 480px) {
  #exit-popup-overlay {
    align-items: flex-end; /* 下部に配置 */
    padding-bottom: 0;
  }
  
  #exit-popup-content {
    padding: 25px 20px;
    width: 100%;
    max-width: 100%;
    border-radius: 20px 20px 0 0; /* 上部のみ角丸 */
    margin: 0;
    animation: slideUpFromBottom 0.4s ease-out;
  }
  
  #exit-popup-content h2 {
    font-size: 20px;
    margin-bottom: 10px;
  }
  
  #exit-popup-content p {
    font-size: 14px;
    margin-bottom: 20px;
  }
  
  /* フォーム要素の調整 */
  #exit-popup-form input[type="email"] {
    padding: 14px 15px;
    font-size: 16px; /* iOS自動ズーム防止 */
    margin-bottom: 12px;
  }
  
  #exit-popup-form button[type="submit"] {
    padding: 16px;
    font-size: 16px;
    font-weight: 600;
  }
  
  /* 閉じるボタンの調整 */
  #exit-popup-close {
    top: 12px;
    right: 12px;
    font-size: 32px;
    padding: 5px;
    /* タップ領域を広げる */
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

/* 下からスライドアップのアニメーション */
@keyframes slideUpFromBottom {
  from {
    transform: translateY(100%);
  }
  to {
    transform: translateY(0);
  }
}

/* 横向き表示の調整 */
@media (max-width: 768px) and (orientation: landscape) {
  #exit-popup-content {
    max-height: 85vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
}

モバイルUXの最適化ポイント:

// モバイル向けのUX改善
const MobilePopupOptimizer = {
  // タップ領域の最小サイズ確保(44x44px推奨)
  optimizeTouchTargets: function() {
    const buttons = document.querySelectorAll('#exit-popup-content button, #exit-popup-content a');
    buttons.forEach(button => {
      const computed = window.getComputedStyle(button);
      const height = parseInt(computed.height);
      const width = parseInt(computed.width);
      
      if (height < 44 || width < 44) {
        button.style.padding = '12px 20px';
        button.style.minHeight = '44px';
        button.style.minWidth = '44px';
      }
    });
  },
  
  // iOS自動ズーム防止(font-size 16px以上)
  preventAutoZoom: function() {
    const inputs = document.querySelectorAll('#exit-popup-form input, #exit-popup-form textarea');
    inputs.forEach(input => {
      const fontSize = window.getComputedStyle(input).fontSize;
      if (parseInt(fontSize) < 16) {
        input.style.fontSize = '16px';
      }
    });
  },
  
  // スクロール位置の固定(背景のスクロール防止)
  lockScroll: function() {
    const scrollY = window.scrollY;
    document.body.style.position = 'fixed';
    document.body.style.top = `-${scrollY}px`;
    document.body.style.width = '100%';
  },
  
  // スクロール位置の復元
  unlockScroll: function() {
    const scrollY = document.body.style.top;
    document.body.style.position = '';
    document.body.style.top = '';
    window.scrollTo(0, parseInt(scrollY || '0') * -1);
  },
  
  // 初期化
  init: function() {
    if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
      this.optimizeTouchTargets();
      this.preventAutoZoom();
      
      // ポップアップ表示時にスクロールロック
      const popup = document.getElementById('exit-popup-overlay');
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.attributeName === 'style') {
            const display = popup.style.display;
            if (display === 'flex' || display === 'block') {
              this.lockScroll();
            } else if (display === 'none') {
              this.unlockScroll();
            }
          }
        });
      });
      observer.observe(popup, { attributes: true });
    }
  }
};

// ページ読み込み時に実行
document.addEventListener('DOMContentLoaded', function() {
  MobilePopupOptimizer.init();
});

レスポンシブ対応のチェックリスト:

項目PCタブレットスマートフォン
表示位置中央中央画面下部推奨
500px前後70-80%100%
閉じるボタンサイズ28px32px44x44px以上
フォントサイズ16px15px16px(自動ズーム防止)
タップ領域44x44px以上44x44px以上
アニメーションフェード/スライドフェード/スライド下からスライド

レスポンシブデザインの適切な実装により、デバイスを問わず快適なユーザー体験を提供し、コンバージョン率の向上につながります。


実装時の注意点とトラブルシューティング

ポップアップがブロックされる原因と対策

エグジットインテントポップアップは、ブラウザのポップアップブロッカーや広告ブロッカーによって表示がブロックされる可能性があります。ブロックを回避し、確実にユーザーに表示させるための対策を理解することが重要です。

ブロックされる主な原因:

ブラウザのポップアップブロッカーは、window.open()や別ウィンドウを開く動作を検知してブロックします。しかし、エグジットインテントポップアップは通常、同一ページ内のHTML要素を表示/非表示するだけなので、基本的にはブロックされません。

それでもブロックされる原因は以下の通りです。

原因説明発生頻度
広告ブロッカーAdBlock等がポップアップ要素を検知
CSSクラス名“popup”や”modal”などの名前が検知される
JavaScriptの動作外部ファイル読み込みや特定の関数名
サードパーティスクリプトトラッキングコードと誤認識

広告ブロッカー対策の実装:

// 広告ブロッカー検知とフォールバック
const AdBlockerHandler = {
  // 広告ブロッカーの検知
  detectAdBlocker: function() {
    // テスト用の要素を作成
    const testAd = document.createElement('div');
    testAd.innerHTML = '&nbsp;';
    testAd.className = 'adsbox ad-banner'; // 広告として検知されやすいクラス名
    testAd.style.height = '1px';
    document.body.appendChild(testAd);
    
    // 要素が非表示にされているかチェック
    setTimeout(() => {
      const isBlocked = testAd.offsetHeight === 0;
      document.body.removeChild(testAd);
      
      if (isBlocked) {
        console.log('広告ブロッカー検知');
        this.useAlternativeMethod();
      }
    }, 100);
  },
  
  // 代替手段の使用
  useAlternativeMethod: function() {
    // クラス名を広告ブロッカーが検知しにくいものに変更
    const popup = document.getElementById('exit-popup-overlay');
    if (popup) {
      popup.className = 'user-notification-overlay';
    }
  }
};

// 初期化時に実行
document.addEventListener('DOMContentLoaded', function() {
  AdBlockerHandler.detectAdBlocker();
});

ブロック回避のベストプラクティス:

<!-- 避けるべきクラス名/ID名 -->
<div id="ad-popup">❌</div>
<div class="advertisement-modal">❌</div>
<div class="popup-ad">❌</div>

<!-- 推奨されるクラス名/ID名 -->
<div id="user-notification">✅</div>
<div class="content-overlay">✅</div>
<div class="special-message">✅</div>
/* 避けるべきCSS */
.ad-container { }
.popup-advertisement { }
.sponsored-content { }

/* 推奨されるCSS */
.notification-wrapper { }
.message-container { }
.user-prompt { }

確実な表示を保証する実装:

// ポップアップ表示の確認と再試行
function showPopupWithRetry() {
  const popup = document.getElementById('exit-popup-overlay');
  popup.style.display = 'flex';
  
  // 100ms後に実際に表示されているか確認
  setTimeout(function() {
    const isVisible = popup.offsetHeight > 0 && 
                      window.getComputedStyle(popup).display !== 'none';
    
    if (!isVisible) {
      console.log('ポップアップがブロックされました');
      // フォールバック処理
      fallbackNotification();
    }
  }, 100);
}

// フォールバック通知
function fallbackNotification() {
  // ページ上部にバナー表示など
  const banner = document.createElement('div');
  banner.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    background: #4CAF50;
    color: white;
    padding: 15px;
    text-align: center;
    z-index: 10000;
  `;
  banner.innerHTML = '特別オファー:今なら10%OFF!<button onclick="this.parentElement.remove()" style="margin-left:20px;background:white;color:#4CAF50;border:none;padding:5px 15px;border-radius:5px;cursor:pointer;">閉じる</button>';
  document.body.insertBefore(banner, document.body.firstChild);
}

ブロッカー対策により、より多くのユーザーにポップアップを表示でき、エグジットインテントの効果を最大化できます。

UX/UIを損なわないデザイン設計

エグジットインテントポップアップは、適切にデザインしないとユーザー体験を大きく損ない、かえってサイトからの離脱を促進してしまいます。UX/UIを考慮したデザイン設計により、ユーザーに受け入れられるポップアップを実現できます。

UXを損なうポップアップの特徴:

ユーザーがストレスを感じるポップアップには、共通した問題点があります。これらを避けることが最優先です。

問題点説明ユーザーへの影響
閉じにくい×ボタンが小さい、または見つけにくいイライラして即離脱
過度な主張画面全体を覆う、派手すぎる色押し付けがましく感じる
コンテンツ過多長文や複雑なフォーム読む気が失せる
頻繁な表示何度も表示されるサイト自体への不信感
遅い表示アニメーションが長すぎる時間の無駄に感じる

UXを重視したデザイン原則:

/* 良いポップアップデザインの例 */
#exit-popup-overlay {
  /* 背景は暗すぎず、適度な透明度 */
  background-color: rgba(0, 0, 0, 0.6);
  
  /* スムーズだが速いアニメーション */
  animation: fadeIn 0.25s ease-out;
}

#exit-popup-content {
  /* 適切なサイズ:画面の60-70% */
  max-width: 500px;
  width: 85%;
  
  /* 読みやすい余白 */
  padding: 35px;
  
  /* 柔らかい角丸 */
  border-radius: 12px;
  
  /* 自然な影 */
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
}

/* 閉じるボタンは大きく、見つけやすく */
#exit-popup-close {
  position: absolute;
  top: 15px;
  right: 15px;
  width: 40px;
  height: 40px;
  font-size: 28px;
  background-color: rgba(0, 0, 0, 0.05);
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
}

#exit-popup-close:hover {
  background-color: rgba(0, 0, 0, 0.1);
  transform: scale(1.1);
}

/* コンテンツは簡潔に */
#exit-popup-content h2 {
  font-size: 24px;
  margin-bottom: 12px;
  color: #333;
  font-weight: 600;
}

#exit-popup-content p {
  font-size: 16px;
  line-height: 1.6;
  color: #666;
  margin-bottom: 20px;
}

コンテンツ設計のベストプラクティス:

<!-- 悪い例:情報過多 ❌ -->
<div class="popup-content">
  <h2>お待ちください!今すぐ登録すると以下の特典が...</h2>
  <ul>
    <li>特典1: ○○○が無料</li>
    <li>特典2: △△△のプレゼント</li>
    <li>特典3: □□□の割引</li>
    <li>特典4: ...</li>
  </ul>
  <form>
    <input type="text" placeholder="お名前">
    <input type="email" placeholder="メールアドレス">
    <input type="tel" placeholder="電話番号">
    <textarea placeholder="ご要望"></textarea>
    <button>今すぐ登録</button>
  </form>
</div>

<!-- 良い例:シンプルで明確 ✅ -->
<div class="popup-content">
  <h2>初回限定10%OFF!</h2>
  <p>メールアドレスを登録して、今すぐクーポンを受け取りましょう。</p>
  <form>
    <input type="email" placeholder="メールアドレス" required>
    <button>クーポンを受け取る</button>
  </form>
  <small>※いつでも配信停止できます</small>
</div>

ユーザー心理を考慮した設計:

// ユーザーフレンドリーな実装
const UXFriendlyPopup = {
  // 複数の閉じる方法を提供
  setupCloseOptions: function() {
    const popup = document.getElementById('exit-popup-overlay');
    
    // 1. 閉じるボタン
    document.getElementById('exit-popup-close').addEventListener('click', () => {
      this.closePopup();
    });
    
    // 2. オーバーレイクリック
    popup.addEventListener('click', (e) => {
      if (e.target === popup) {
        this.closePopup();
      }
    });
    
    // 3. ESCキー
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        this.closePopup();
      }
    });
  },
  
  // スムーズな閉じるアニメーション
  closePopup: function() {
    const popup = document.getElementById('exit-popup-overlay');
    popup.style.animation = 'fadeOut 0.2s ease-out';
    
    setTimeout(() => {
      popup.style.display = 'none';
      popup.style.animation = '';
    }, 200);
  },
  
  // 「後で」オプションの提供
  addRemindMeLater: function() {
    const remindButton = document.createElement('button');
    remindButton.textContent = '後で見る';
    remindButton.style.cssText = `
      background: transparent;
      color: #999;
      border: none;
      padding: 10px;
      cursor: pointer;
      font-size: 14px;
      margin-top: 10px;
    `;
    
    remindButton.addEventListener('click', () => {
      // 1時間後に再表示するCookieを設定
      const date = new Date();
      date.setTime(date.getTime() + (60 * 60 * 1000)); // 1時間
      document.cookie = `exitPopupReminder=${date.getTime()};expires=${date.toUTCString()};path=/`;
      this.closePopup();
    });
    
    document.querySelector('#exit-popup-content').appendChild(remindButton);
  }
};

UX/UIを重視したデザイン設計により、ユーザーに受け入れられやすいポップアップを実現し、コンバージョン率の向上とブランドイメージの維持を両立できます。

パフォーマンスへの影響と最適化

エグジットインテントの実装は、サイトのパフォーマンスに影響を与える可能性があります。特にイベントリスナーの頻繁な発火や、DOMの操作によってページの応答性が低下することがあります。適切な最適化により、ユーザー体験を損なわない実装が可能です。

パフォーマンスへの主な影響:

エグジットインテント実装で発生する可能性があるパフォーマンス問題とその影響を理解することが重要です。

問題原因影響度
イベント発火の頻度mouseleaveが頻繁に実行される
DOM操作のコストポップアップ表示/非表示の処理
Cookie/Storage読み書き毎回の状態確認
アニメーションの負荷複雑なCSS transitionやanimation
メモリリークイベントリスナーの適切な解除漏れ

デバウンス(Debounce)による最適化:

// デバウンス関数の実装
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// mouseleaveイベントにデバウンスを適用
const debouncedExitIntent = debounce(function(e) {
  if (e.clientY < 0 && !popupShown && exitIntentActive) {
    showPopup();
    popupShown = true;
  }
}, 200); // 200ms間隔で実行を制限

document.addEventListener('mouseleave', debouncedExitIntent);

スロットリング(Throttling)による最適化:

// スロットリング関数の実装
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// スクロールイベントにスロットリングを適用
const throttledScrollCheck = throttle(function() {
  const scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
  if (scrollPercent > 50) {
    exitIntentActive = true;
  }
}, 500); // 500msごとに実行

window.addEventListener('scroll', throttledScrollCheck, { passive: true });

DOM操作の最適化:

// 効率的なDOM操作
const OptimizedPopup = {
  popup: null,
  
  // 初期化時に要素を取得して保持
  init: function() {
    this.popup = document.getElementById('exit-popup-overlay');
    this.cacheElements();
  },
  
  // 頻繁に使う要素をキャッシュ
  cacheElements: function() {
    this.closeBtn = document.getElementById('exit-popup-close');
    this.content = document.getElementById('exit-popup-content');
  },
  
  // CSSクラスによる表示制御(display切り替えより高速)
  show: function() {
    // reflow/repaintを最小化
    requestAnimationFrame(() => {
      this.popup.classList.add('visible');
      document.body.classList.add('popup-open');
    });
  },
  
  hide: function() {
    requestAnimationFrame(() => {
      this.popup.classList.remove('visible');
      document.body.classList.remove('popup-open');
    });
  }
};

メモリリーク防止:

// 適切なイベントリスナーの管理
class ExitIntentManager {
  constructor() {
    this.boundHandlers = {
      mouseleave: this.handleMouseLeave.bind(this),
      click: this.handleClick.bind(this)
    };
    this.isActive = false;
  }
  
  init() {
    this.isActive = true;
    document.addEventListener('mouseleave', this.boundHandlers.mouseleave);
    document.addEventListener('click', this.boundHandlers.click);
  }
  
  destroy() {
    this.isActive = false;
    // イベントリスナーを確実に削除
    document.removeEventListener('mouseleave', this.boundHandlers.mouseleave);
    document.removeEventListener('click', this.boundHandlers.click);
  }
  
  handleMouseLeave(e) {
    if (!this.isActive) return;
    // 処理...
  }
  
  handleClick(e) {
    if (!this.isActive) return;
    // 処理...
  }
}

// 使用例
const exitIntent = new ExitIntentManager();
exitIntent.init();

// ページ離脱時やSPAでのルート変更時にクリーンアップ
window.addEventListener('beforeunload', () => {
  exitIntent.destroy();
});

パフォーマンス測定:

// パフォーマンスの測定
const PerformanceMonitor = {
  measureExitIntent: function() {
    performance.mark('exit-intent-start');
    
    // エグジットインテント処理
    showPopup();
    
    performance.mark('exit-intent-end');
    performance.measure('exit-intent-duration', 'exit-intent-start', 'exit-intent-end');
    
    const measure = performance.getEntriesByName('exit-intent-duration')[0];
    console.log(`エグジットインテント表示時間: ${measure.duration}ms`);
    
    // 16ms(60fps)以下が理想
    if (measure.duration > 16) {
      console.warn('パフォーマンスの最適化が必要です');
    }
  }
};

適切な最適化により、ユーザー体験を損なうことなく、効果的なエグジットインテント機能を実装できます。

よくあるエラーと解決方法

エグジットインテントの実装過程で発生しやすいエラーと、その解決方法を理解しておくことで、スムーズな開発とトラブルシューティングが可能になります。

主なエラーパターンと解決方法:

エラー1: ポップアップが表示されない

// 問題のコード ❌
document.addEventListener('mouseleave', function(e) {
  if (e.clientY < 0) {
    document.getElementById('exit-popup').style.display = 'block';
  }
});

// 原因診断と解決
console.log('要素の存在確認:', document.getElementById('exit-popup')); // null?
console.log('clientY値:', e.clientY); // 期待通りの値か?

// 解決策 ✅
document.addEventListener('DOMContentLoaded', function() {
  const popup = document.getElementById('exit-popup-overlay'); // 正しいID
  
  if (!popup) {
    console.error('ポップアップ要素が見つかりません');
    return;
  }
  
  document.addEventListener('mouseleave', function(e) {
    console.log('mouseleave発火, clientY:', e.clientY);
    if (e.clientY < 0) {
      popup.style.display = 'flex'; // blockではなくflex
    }
  });
});

エラー2: Cookieが保存されない

// 問題のコード ❌
function setCookie(name, value) {
  document.cookie = name + "=" + value;
  // 有効期限とpathが設定されていない
}

// 解決策 ✅
function setCookie(name, value, days) {
  const date = new Date();
  date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
  const expires = "expires=" + date.toUTCString();
  const path = "path=/"; // 重要:サイト全体で有効
  document.cookie = `${name}=${value};${expires};${path}`;
  
  // 確認
  console.log('Cookie設定完了:', document.cookie);
}

// SameSite属性も追加推奨
function setCookieSecure(name, value, days) {
  const date = new Date();
  date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
  const expires = "expires=" + date.toUTCString();
  const secure = location.protocol === 'https:' ? ';Secure' : '';
  document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax${secure}`;
}

エラー3: 複数回表示される

// 問題のコード ❌
document.addEventListener('mouseleave', function(e) {
  if (e.clientY < 0) {
    showPopup(); // フラグチェックがない
  }
});

// 解決策 ✅
let popupShown = false;

document.addEventListener('mouseleave', function(e) {
  if (e.clientY < 0 && !popupShown) {
    showPopup();
    popupShown = true; // フラグを設定
    
    // Cookie確認も追加
    if (getCookie('exitPopupShown')) {
      return; // 既に表示済み
    }
    setCookie('exitPopupShown', 'true', 7);
  }
});

エラー4: モバイルで動作しない

// 問題:PCのイベントのみ実装 ❌
document.addEventListener('mouseleave', function(e) {
  // モバイルではmouseleaveイベントが発火しない
});

// 解決策:デバイス判定と分岐 ✅
function initExitIntent() {
  const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  
  if (isMobile) {
    initMobileExitIntent(); // モバイル用の実装
  } else {
    initDesktopExitIntent(); // PC用の実装
  }
}

function initMobileExitIntent() {
  // タッチイベントや時間ベースの実装
  let touchStartY = 0;
  document.addEventListener('touchstart', (e) => {
    touchStartY = e.touches[0].clientY;
  }, { passive: true });
  
  document.addEventListener('touchend', (e) => {
    const touchEndY = e.changedTouches[0].clientY;
    if (touchStartY - touchEndY > 50 && touchEndY < 100) {
      showPopup();
    }
  }, { passive: true });
}

function initDesktopExitIntent() {
  document.addEventListener('mouseleave', (e) => {
    if (e.clientY < 0) {
      showPopup();
    }
  });
}

デバッグツールの活用:

// デバッグモードの実装
const DEBUG_MODE = true; // 本番環境ではfalse

function debugLog(message, data) {
  if (DEBUG_MODE) {
    console.log(`[Exit Intent Debug] ${message}`, data || '');
  }
}

// 使用例
document.addEventListener('mouseleave', function(e) {
  debugLog('mouseleave発火', { clientY: e.clientY, popupShown });
  
  if (e.clientY < 0) {
    debugLog('離脱意図検知');
    
    if (!popupShown) {
      debugLog('ポップアップ表示開始');
      showPopup();
      popupShown = true;
    } else {
      debugLog('既に表示済みのためスキップ');
    }
  }
});

よくあるエラーのチェックリスト:

エラー内容チェック項目解決方法
ポップアップ非表示要素IDの一致確認console.logで要素の存在確認
Cookie保存失敗有効期限とpath設定完全なCookie文字列を記述
複数回表示フラグ管理の実装popupShownフラグを追加
モバイル未対応デバイス判定の有無分岐処理を実装
イベント未発火DOMContentLoaded内かページ読み込み完了後に設定

これらのエラー対策により、確実に動作するエグジットインテント機能を実装できます。


より高度な実装テクニック

A/Bテストの実装方法

エグジットインテントポップアップの効果を最大化するには、A/Bテストによる継続的な改善が不可欠です。異なるデザイン、コピー、タイミングをテストすることで、最適なコンバージョン率を実現できます。

基本的なA/Bテストの実装:

// シンプルなA/Bテスト実装
const ExitIntentABTest = {
  // テストバリエーション定義
  variants: {
    A: {
      title: 'ちょっと待ってください!',
      description: '今なら初回限定10%OFFクーポンプレゼント',
      buttonText: 'クーポンを受け取る',
      buttonColor: '#4CAF50'
    },
    B: {
      title: 'お見逃しなく!',
      description: 'メール登録で次回使える15%OFF',
      buttonText: '今すぐ登録',
      buttonColor: '#FF5722'
    }
  },
  
  // ランダムにバリエーションを選択
  selectVariant: function() {
    // ユーザーごとに固定のバリエーションを割り当て
    let assignedVariant = localStorage.getItem('exitPopupVariant');
    
    if (!assignedVariant) {
      // 50:50で割り当て
      assignedVariant = Math.random() < 0.5 ? 'A' : 'B';
      localStorage.setItem('exitPopupVariant', assignedVariant);
    }
    
    return assignedVariant;
  },
  
  // ポップアップにバリエーションを適用
  applyVariant: function(variantKey) {
    const variant = this.variants[variantKey];
    
    document.getElementById('popup-title').textContent = variant.title;
    document.getElementById('popup-description').textContent = variant.description;
    document.getElementById('popup-button').textContent = variant.buttonText;
    document.getElementById('popup-button').style.backgroundColor = variant.buttonColor;
    
    // トラッキング用にdata属性を設定
    document.getElementById('exit-popup-overlay').dataset.variant = variantKey;
  },
  
  // 表示回数をトラッキング
  trackImpression: function(variantKey) {
    // Google Analytics への送信例
    if (typeof gtag !== 'undefined') {
      gtag('event', 'exit_popup_impression', {
        'event_category': 'Exit Intent',
        'event_label': `Variant ${variantKey}`,
        'value': 1
      });
    }
    
    // 内部トラッキング
    const impressions = JSON.parse(localStorage.getItem('abTestImpressions') || '{}');
    impressions[variantKey] = (impressions[variantKey] || 0) + 1;
    localStorage.setItem('abTestImpressions', JSON.stringify(impressions));
  },
  
  // コンバージョンをトラッキング
  trackConversion: function(variantKey) {
    if (typeof gtag !== 'undefined') {
      gtag('event', 'exit_popup_conversion', {
        'event_category': 'Exit Intent',
        'event_label': `Variant ${variantKey}`,
        'value': 1
      });
    }
    
    const conversions = JSON.parse(localStorage.getItem('abTestConversions') || '{}');
    conversions[variantKey] = (conversions[variantKey] || 0) + 1;
    localStorage.setItem('abTestConversions', JSON.stringify(conversions));
  },
  
  // 初期化
  init: function() {
    const variant = this.selectVariant();
    this.applyVariant(variant);
    
    // ポップアップ表示時にトラッキング
    const originalShowPopup = window.showPopup;
    window.showPopup = () => {
      originalShowPopup();
      this.trackImpression(variant);
    };
    
    // フォーム送信時にコンバージョントラッキング
    document.getElementById('exit-popup-form').addEventListener('submit', (e) => {
      this.trackConversion(variant);
    });
  }
};

// ページ読み込み時に初期化
document.addEventListener('DOMContentLoaded', function() {
  ExitIntentABTest.init();
});

多変量テスト(MVT)の実装:

// 複数要素を同時にテスト
const MultivariateTest = {
  // テスト要素の定義
  elements: {
    title: ['ちょっと待って!', 'お見逃しなく!', '特別オファー'],
    discount: ['10%OFF', '15%OFF', '送料無料'],
    buttonColor: ['#4CAF50', '#FF5722', '#2196F3']
  },
  
  // 組み合わせを生成
  generateCombination: function() {
    const combination = {};
    for (const [key, values] of Object.entries(this.elements)) {
      combination[key] = values[Math.floor(Math.random() * values.length)];
    }
    return combination;
  },
  
  // 組み合わせを適用
  applyCombination: function(combination) {
    document.getElementById('popup-title').textContent = combination.title;
    document.getElementById('popup-discount').textContent = combination.discount;
    document.getElementById('popup-button').style.backgroundColor = combination.buttonColor;
    
    // 組み合わせをIDとして保存
    const combinationId = `${combination.title}_${combination.discount}_${combination.buttonColor}`;
    localStorage.setItem('mvtCombination', combinationId);
    
    return combinationId;
  }
};

統計的有意性の判定:

// A/Bテスト結果の分析
const ABTestAnalyzer = {
  // コンバージョン率を計算
  calculateConversionRate: function(variantKey) {
    const impressions = JSON.parse(localStorage.getItem('abTestImpressions') || '{}');
    const conversions = JSON.parse(localStorage.getItem('abTestConversions') || '{}');
    
    const impressionCount = impressions[variantKey] || 0;
    const conversionCount = conversions[variantKey] || 0;
    
    return impressionCount > 0 ? (conversionCount / impressionCount) * 100 : 0;
  },
  
  // 結果を表示
  displayResults: function() {
    console.log('=== A/Bテスト結果 ===');
    
    for (const variant of ['A', 'B']) {
      const rate = this.calculateConversionRate(variant);
      const impressions = JSON.parse(localStorage.getItem('abTestImpressions') || '{}');
      const conversions = JSON.parse(localStorage.getItem('abTestConversions') || '{}');
      
      console.log(`バリエーション${variant}:`);
      console.log(`  表示回数: ${impressions[variant] || 0}`);
      console.log(`  コンバージョン数: ${conversions[variant] || 0}`);
      console.log(`  コンバージョン率: ${rate.toFixed(2)}%`);
    }
  },
  
  // 勝者を判定
  determineWinner: function() {
    const rateA = this.calculateConversionRate('A');
    const rateB = this.calculateConversionRate('B');
    
    if (rateA > rateB) {
      console.log(`勝者: バリエーションA (${rateA.toFixed(2)}% vs ${rateB.toFixed(2)}%)`);
      return 'A';
    } else if (rateB > rateA) {
      console.log(`勝者: バリエーションB (${rateB.toFixed(2)}% vs ${rateA.toFixed(2)}%)`);
      return 'B';
    } else {
      console.log('引き分け');
      return null;
    }
  }
};

// 結果確認用(開発者ツールコンソールで実行)
// ABTestAnalyzer.displayResults();
// ABTestAnalyzer.determineWinner();

A/Bテストの実装により、データに基づいた意思決定が可能になり、エグジットインテントのROIを最大化できます。

アニメーション効果の追加

適切なアニメーション効果は、ユーザーの注意を引きつけ、ポップアップの存在をより効果的にアピールできます。ただし、過度なアニメーションはユーザー体験を損なうため、バランスが重要です。

基本的なアニメーション実装:

/* フェードイン + スケールアップ */
@keyframes fadeInScale {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

#exit-popup-overlay {
  animation: fadeIn 0.3s ease-out;
}

#exit-popup-content {
  animation: fadeInScale 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

/* スライドアップ */
@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* バウンス効果 */
@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-20px);
  }
  60% {
    transform: translateY(-10px);
  }
}

#exit-popup-content.bounce {
  animation: bounce 0.8s;
}

高度なアニメーション実装:

// アニメーション制御クラス
class PopupAnimations {
  constructor(popupElement) {
    this.popup = popupElement;
    this.content = popupElement.querySelector('#exit-popup-content');
  }
  
  // アニメーションタイプを選択
  show(animationType = 'fadeScale') {
    this.popup.style.display = 'flex';
    
    switch(animationType) {
      case 'fadeScale':
        this.fadeScale();
        break;
      case 'slideUp':
        this.slideUp();
        break;
      case 'flip':
        this.flip();
        break;
      case 'bounce':
        this.bounce();
        break;
      default:
        this.fadeScale();
    }
  }
  
  // フェード + スケール
  fadeScale() {
    this.popup.style.animation = 'fadeIn 0.3s ease-out';
    this.content.style.animation = 'fadeInScale 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55)';
  }
  
  // スライドアップ
  slideUp() {
    this.content.style.animation = 'slideUp 0.5s ease-out';
  }
  
  // フリップ効果
  flip() {
    this.content.style.animation = 'flip 0.6s ease-out';
  }
  
  // バウンス効果
  bounce() {
    this.content.style.animation = 'fadeIn 0.3s ease-out, bounce 0.8s 0.3s';
  }
  
  // 閉じるアニメーション
  hide(callback) {
    this.popup.style.animation = 'fadeOut 0.2s ease-out';
    this.content.style.animation = 'scaleDown 0.2s ease-out';
    
    setTimeout(() => {
      this.popup.style.display = 'none';
      this.popup.style.animation = '';
      this.content.style.animation = '';
      if (callback) callback();
    }, 200);
  }
}

// 追加のCSS (JavaScriptで動的に追加)
const style = document.createElement('style');
style.textContent = `
  @keyframes flip {
    from {
      transform: perspective(400px) rotateY(90deg);
      opacity: 0;
    }
    to {
      transform: perspective(400px) rotateY(0deg);
      opacity: 1;
    }
  }
  
  @keyframes scaleDown {
    to {
      transform: scale(0.8);
      opacity: 0;
    }
  }
  
  @keyframes fadeOut {
    to {
      opacity: 0;
    }
  }
`;
document.head.appendChild(style);

// 使用例
const popup = document.getElementById('exit-popup-overlay');
const animator = new PopupAnimations(popup);

// 表示
animator.show('fadeScale'); // または 'slideUp', 'flip', 'bounce'

// 非表示
animator.hide(() => {
  console.log('ポップアップが閉じられました');
});

インタラクティブなアニメーション:

// マウス追従効果
class MouseFollowEffect {
  constructor(element) {
    this.element = element;
    this.setupMouseTracking();
  }
  
  setupMouseTracking() {
    this.element.addEventListener('mousemove', (e) => {
      const rect = this.element.getBoundingClientRect();
      const x = (e.clientX - rect.left) / rect.width;
      const y = (e.clientY - rect.top) / rect.height;
      
      // 微妙な傾き効果
      const tiltX = (y - 0.5) * 10; // -5deg ~ 5deg
      const tiltY = (x - 0.5) * -10;
      
      this.element.style.transform = `perspective(1000px) rotateX(${tiltX}deg) rotateY(${tiltY}deg)`;
    });
    
    this.element.addEventListener('mouseleave', () => {
      this.element.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
    });
  }
}

// 使用例
const content = document.getElementById('exit-popup-content');
new MouseFollowEffect(content);

パフォーマンスを考慮したアニメーション:

/* GPU加速を利用した最適化 */
#exit-popup-content {
  /* transformとopacityのみ使用(reflow/repaintを避ける) */
  transform: translateZ(0); /* GPU加速を強制 */
  will-change: transform, opacity; /* ブラウザに最適化のヒント */
}

/* スムーズなアニメーション */
@keyframes optimizedSlide {
  from {
    opacity: 0;
    transform: translate3d(0, 50px, 0); /* translate3dでGPU加速 */
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

#exit-popup-content.show {
  animation: optimizedSlide 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

適切なアニメーション効果により、ユーザーの注意を引きつけ、ポップアップのコンバージョン率を向上させることができます。

フォーム連携とリード獲得の最適化

エグジットインテントポップアップでフォームを使用する場合、入力の手間を最小限にし、スムーズなリード獲得を実現することが重要です。適切なフォーム設計と検証により、コンバージョン率を大幅に向上できます。

最適化されたフォーム実装:

<!-- シンプルで効果的なフォーム -->
<form id="exit-popup-form" class="popup-form">
  <div class="form-group">
    <input 
      type="email" 
      id="popup-email" 
      name="email"
      placeholder="メールアドレスを入力"
      required
      autocomplete="email"
      aria-label="メールアドレス"
    >
    <span class="error-message" id="email-error"></span>
  </div>
  
  <button type="submit" class="submit-button">
    <span class="button-text">クーポンを受け取る</span>
    <span class="button-loading" style="display:none;">送信中...</span>
  </button>
  
  <p class="privacy-note">
    <small>※いつでも配信停止できます。<a href="/privacy" target="_blank">プライバシーポリシー</a></small>
  </p>
</form>

リアルタイムバリデーション:

// 高度なフォームバリデーション
class PopupFormValidator {
  constructor(formId) {
    this.form = document.getElementById(formId);
    this.emailInput = this.form.querySelector('input[type="email"]');
    this.submitButton = this.form.querySelector('button[type="submit"]');
    this.errorElement = document.getElementById('email-error');
    
    this.setupValidation();
  }
  
  // バリデーションルール
  validateEmail(email) {
    // より厳密なメールアドレス検証
    const regex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
    return regex.test(email);
  }
  
  // 使い捨てメールアドレスのチェック(オプション)
  async checkDisposableEmail(email) {
    const domain = email.split('@')[1];
    const disposableDomains = ['tempmail.com', '10minutemail.com', 'guerrillamail.com'];
    return disposableDomains.includes(domain);
  }
  
  // リアルタイムバリデーション
  setupValidation() {
    this.emailInput.addEventListener('input', () => {
      this.clearError();
    });
    
    this.emailInput.addEventListener('blur', () => {
      const email = this.emailInput.value.trim();
      if (email && !this.validateEmail(email)) {
        this.showError('正しいメールアドレスを入力してください');
      }
    });
  }
  
  // エラー表示
  showError(message) {
    this.errorElement.textContent = message;
    this.errorElement.style.display = 'block';
    this.emailInput.classList.add('error');
  }
  
  // エラークリア
  clearError() {
    this.errorElement.textContent = '';
    this.errorElement.style.display = 'none';
    this.emailInput.classList.remove('error');
  }
  
  // フォーム送信処理
  async handleSubmit(e) {
    e.preventDefault();
    
    const email = this.emailInput.value.trim();
    
    // バリデーション
    if (!this.validateEmail(email)) {
      this.showError('正しいメールアドレスを入力してください');
      return;
    }
    
    // 使い捨てメールチェック(オプション)
    if (await this.checkDisposableEmail(email)) {
      this.showError('使い捨てメールアドレスは使用できません');
      return;
    }
    
    // 送信中状態
    this.setLoading(true);
    
    try {
      // APIへの送信
      const response = await this.submitToAPI(email);
      
      if (response.success) {
        this.handleSuccess();
      } else {
        this.showError('送信に失敗しました。もう一度お試しください。');
      }
    } catch (error) {
      console.error('フォーム送信エラー:', error);
      this.showError('エラーが発生しました。しばらくしてからお試しください。');
    } finally {
      this.setLoading(false);
    }
  }
  
  // API送信
  async submitToAPI(email) {
    const response = await fetch('/api/newsletter/subscribe', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: email,
        source: 'exit_intent_popup',
        timestamp: new Date().toISOString()
      })
    });
    
    return await response.json();
  }
  
  // ローディング状態の切り替え
  setLoading(isLoading) {
    const buttonText = this.submitButton.querySelector('.button-text');
    const buttonLoading = this.submitButton.querySelector('.button-loading');
    
    if (isLoading) {
      buttonText.style.display = 'none';
      buttonLoading.style.display = 'inline';
      this.submitButton.disabled = true;
      this.emailInput.disabled = true;
    } else {
      buttonText.style.display = 'inline';
      buttonLoading.style.display = 'none';
      this.submitButton.disabled = false;
      this.emailInput.disabled = false;
    }
  }
  
  // 成功時の処理
  handleSuccess() {
    // ポップアップ内容を成功メッセージに変更
    const popupContent = document.getElementById('exit-popup-content');
    popupContent.innerHTML = `
      <div class="success-message">
        <div class="success-icon">✓</div>
        <h2>登録完了!</h2>
        <p>クーポンコードをメールで送信しました。</p>
        <button onclick="document.getElementById('exit-popup-overlay').style.display='none'">
          閉じる
        </button>
      </div>
    `;
    
    // Google Analytics等へのトラッキング
    if (typeof gtag !== 'undefined') {
      gtag('event', 'conversion', {
        'event_category': 'Exit Intent',
        'event_label': 'Email Signup',
        'value': 1
      });
    }
    
    // Cookie設定
    this.setCookie('exitPopupConverted', 'true', 365);
  }
  
  setCookie(name, value, days) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`;
  }
}

// 初期化
document.addEventListener('DOMContentLoaded', function() {
  const validator = new PopupFormValidator('exit-popup-form');
  
  document.getElementById('exit-popup-form').addEventListener('submit', (e) => {
    validator.handleSubmit(e);
  });
});

フォームUXの最適化CSS:

/* フォームスタイリング */
.popup-form {
  width: 100%;
}

.form-group {
  position: relative;
  margin-bottom: 15px;
}

.popup-form input[type="email"] {
  width: 100%;
  padding: 14px 16px;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 16px;
  transition: border-color 0.3s;
  box-sizing: border-box;
}

.popup-form input[type="email"]:focus {
  outline: none;
  border-color: #4CAF50;
  box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
}

.popup-form input[type="email"].error {
  border-color: #f44336;
}

.error-message {
  display: none;
  color: #f44336;
  font-size: 13px;
  margin-top: 5px;
}

.submit-button {
  width: 100%;
  padding: 16px;
  background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s;
  position: relative;
}

.submit-button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
}

.submit-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.privacy-note {
  margin-top: 12px;
  text-align: center;
  color: #999;
}

.privacy-note a {
  color: #4CAF50;
  text-decoration: none;
}

/* 成功メッセージ */
.success-message {
  text-align: center;
  padding: 20px;
}

.success-icon {
  width: 60px;
  height: 60px;
  background: #4CAF50;
  color: white;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32px;
  margin: 0 auto 20px;
  animation: scaleIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

@keyframes scaleIn {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}

フォーム連携の最適化により、ユーザーのストレスを最小限に抑え、高いコンバージョン率を実現できます。

Google Analyticsとの連携方法

エグジットインテントの効果を正確に測定し、データに基づいた改善を行うには、Google Analyticsとの連携が不可欠です。適切なトラッキング実装により、ROIの算出やA/Bテストの分析が可能になります。

基本的なイベントトラッキング:

// Google Analytics 4 (GA4) イベントトラッキング
class ExitIntentAnalytics {
  // ポップアップ表示をトラッキング
  trackImpression() {
    if (typeof gtag === 'undefined') {
      console.warn('Google Analytics が読み込まれていません');
      return;
    }
    
    gtag('event', 'exit_intent_shown', {
      'event_category': 'Exit Intent',
      'event_label': window.location.pathname,
      'value': 1
    });
  }
  
  // ポップアップを閉じた時をトラッキング
  trackClose(method) {
    gtag('event', 'exit_intent_closed', {
      'event_category': 'Exit Intent',
      'event_label': method, // 'close_button', 'overlay', 'esc_key'
      'page_path': window.location.pathname
    });
  }
  
  // コンバージョンをトラッキング
  trackConversion(value = 1) {
    gtag('event', 'exit_intent_conversion', {
      'event_category': 'Exit Intent',
      'event_label': 'Email Signup',
      'value': value,
      'currency': 'JPY'
    });
    
    // eコマースイベント(オプション)
    gtag('event', 'generate_lead', {
      'currency': 'JPY',
      'value': value
    });
  }
  
  // クリックをトラッキング
  trackClick(element) {
    gtag('event', 'exit_intent_click', {
      'event_category': 'Exit Intent',
      'event_label': element,
      'page_path': window.location.pathname
    });
  }
  
  // 滞在時間をトラッキング
  trackEngagementTime(seconds) {
    gtag('event', 'exit_intent_engagement', {
      'event_category': 'Exit Intent',
      'event_label': 'Time Spent',
      'value': Math.round(seconds)
    });
  }
}

// 使用例
const analytics = new ExitIntentAnalytics();

// ポップアップ表示時
function showPopup() {
  document.getElementById('exit-popup-overlay').style.display = 'flex';
  analytics.trackImpression();
  
  // エンゲージメント時間の測定開始
  window.popupShowTime = Date.now();
}

// ポップアップを閉じる時
function closePopup(method) {
  document.getElementById('exit-popup-overlay').style.display = 'none';
  analytics.trackClose(method);
  
  // エンゲージメント時間を計算
  if (window.popupShowTime) {
    const engagementTime = (Date.now() - window.popupShowTime) / 1000;
    analytics.trackEngagementTime(engagementTime);
  }
}

// コンバージョン時
document.getElementById('exit-popup-form').addEventListener('submit', function(e) {
  e.preventDefault();
  analytics.trackConversion();
  // フォーム送信処理...
});

カスタムディメンションとメトリクス:

// より詳細なトラッキング
class AdvancedExitIntentAnalytics extends ExitIntentAnalytics {
  constructor() {
    super();
    this.sessionData = this.getSessionData();
  }
  
  // セッション情報を取得
  getSessionData() {
    return {
      variant: localStorage.getItem('exitPopupVariant') || 'default',
      visitCount: parseInt(localStorage.getItem('visitCount') || '1'),
      previousConversion: localStorage.getItem('exitPopupConverted') === 'true',
      deviceType: this.getDeviceType(),
      scrollDepth: this.getScrollDepth()
    };
  }
  
  // デバイスタイプを取得
  getDeviceType() {
    const ua = navigator.userAgent;
    if (/tablet|ipad/i.test(ua)) return 'tablet';
    if (/mobile|android|iphone/i.test(ua)) return 'mobile';
    return 'desktop';
  }
  
  // スクロール深度を取得
  getScrollDepth() {
    const scrollTop = window.scrollY;
    const docHeight = document.documentElement.scrollHeight - window.innerHeight;
    return Math.round((scrollTop / docHeight) * 100);
  }
  
  // 拡張トラッキング
  trackImpressionWithContext() {
    gtag('event', 'exit_intent_shown', {
      'event_category': 'Exit Intent',
      'event_label': window.location.pathname,
      'variant': this.sessionData.variant,
      'visit_count': this.sessionData.visitCount,
      'device_type': this.sessionData.deviceType,
      'scroll_depth': this.sessionData.scrollDepth,
      'previous_conversion': this.sessionData.previousConversion
    });
  }
  
  // ファネル分析用のトラッキング
  trackFunnelStep(step) {
    const steps = {
      1: 'popup_shown',
      2: 'form_focused',
      3: 'form_filled',
      4: 'form_submitted',
      5: 'conversion_complete'
    };
    
    gtag('event', 'exit_intent_funnel', {
      'event_category': 'Exit Intent Funnel',
      'event_label': steps[step],
      'funnel_step': step
    });
  }
}

// 実装例
const advancedAnalytics = new AdvancedExitIntentAnalytics();

// ポップアップ表示
advancedAnalytics.trackImpressionWithContext();
advancedAnalytics.trackFunnelStep(1);

// フォームにフォーカス
document.getElementById('popup-email').addEventListener('focus', () => {
  advancedAnalytics.trackFunnelStep(2);
});

// フォーム入力完了
document.getElementById('popup-email').addEventListener('blur', (e) => {
  if (e.target.value.length > 0) {
    advancedAnalytics.trackFunnelStep(3);
  }
});

コンバージョン率の計算と表示:

// GA4データを使用した分析ダッシュボード(管理画面用)
class ExitIntentDashboard {
  async fetchAnalyticsData() {
    // GA4 Data APIを使用してデータを取得
    // 実際の実装では認証が必要
    const data = {
      impressions: 1250,
      conversions: 185,
      closeRate: 72,
      averageEngagementTime: 12.5
    };
    
    return data;
  }
  
  calculateMetrics(data) {
    const conversionRate = (data.conversions / data.impressions) * 100;
    const engagementRate = 100 - data.closeRate;
    
    return {
      conversionRate: conversionRate.toFixed(2),
      engagementRate: engagementRate.toFixed(2),
      impressions: data.impressions,
      conversions: data.conversions,
      averageEngagementTime: data.averageEngagementTime
    };
  }
  
  displayMetrics(metrics) {
    console.log('=== エグジットインテント パフォーマンス ===');
    console.log(`表示回数: ${metrics.impressions}`);
    console.log(`コンバージョン数: ${metrics.conversions}`);
    console.log(`コンバージョン率: ${metrics.conversionRate}%`);
    console.log(`エンゲージメント率: ${metrics.engagementRate}%`);
    console.log(`平均滞在時間: ${metrics.averageEngagementTime}秒`);
  }
}

// 使用例(管理画面で実行)
const dashboard = new ExitIntentDashboard();
dashboard.fetchAnalyticsData().then(data => {
  const metrics = dashboard.calculateMetrics(data);
  dashboard.displayMetrics(metrics);
});

Google Analyticsとの連携により、エグジットインテントの効果を定量的に測定し、継続的な改善が可能になります。


エグジットインテント実装のベストプラクティス

コンバージョン率を高めるポップアップデザイン

エグジットインテントポップアップのコンバージョン率は、デザインの質に大きく左右されます。視覚的な訴求力と明確なメッセージにより、ユーザーの行動を効果的に促すことができます。

高コンバージョンを生むデザイン要素:

コンバージョン率の高いポップアップには、共通した成功パターンがあります。これらの要素を適切に組み合わせることで、最大限の効果を引き出せます。

<!-- 高コンバージョンポップアップの構造 -->
<div id="exit-popup-overlay" class="popup-overlay">
  <div id="exit-popup-content" class="popup-content">
    <!-- 1. 視覚的なアイコン/画像 -->
    <div class="popup-icon">
      🎁
    </div>
    
    <!-- 2. 明確で魅力的な見出し -->
    <h2 class="popup-headline">
      お待ちください!
    </h2>
    
    <!-- 3. 具体的なベネフィット -->
    <p class="popup-value-proposition">
      初回限定<strong class="highlight">10%OFF</strong>クーポンを<br>
      今すぐプレゼント!
    </p>
    
    <!-- 4. 社会的証明 -->
    <div class="social-proof">
      <span class="proof-icon">✓</span>
      <span class="proof-text">既に<strong>12,458人</strong>が登録済み</span>
    </div>
    
    <!-- 5. シンプルなCTA -->
    <form class="popup-form">
      <input 
        type="email" 
        placeholder="メールアドレスを入力"
        class="popup-input"
      >
      <button type="submit" class="popup-cta">
        クーポンを受け取る
      </button>
    </form>
    
    <!-- 6. 信頼性の保証 -->
    <p class="trust-badge">
      <small>
        🔒 安心・安全に保護されています<br>
        ※いつでも配信停止できます
      </small>
    </p>
    
    <!-- 7. 見つけやすい閉じるボタン -->
    <button class="popup-close" aria-label="閉じる">&times;</button>
  </div>
</div>

色彩心理学を活用したデザイン:

/* コンバージョンを高める色の使い方 */
.popup-content {
  background: #ffffff;
  /* 清潔感と信頼性 */
}

.popup-headline {
  color: #333333;
  /* 読みやすさ重視 */
}

.highlight {
  color: #FF6B6B;
  /* 注目を集める赤系 */
  font-weight: 700;
}

.popup-cta {
  background: linear-gradient(135deg, #FF6B6B 0%, #FF5252 100%);
  /* アクションを促す暖色系 */
  color: #ffffff;
  padding: 16px 32px;
  border-radius: 8px;
  font-size: 18px;
  font-weight: 700;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
}

.popup-cta:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4);
}

.social-proof {
  background: #F0F7FF;
  /* 信頼性を高める青系背景 */
  padding: 12px 20px;
  border-radius: 8px;
  margin: 15px 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.proof-icon {
  color: #4CAF50;
  /* 承認を示す緑色 */
  font-size: 18px;
}

.trust-badge {
  color: #666666;
  margin-top: 15px;
}

視覚階層を意識したレイアウト:

/* 視線の流れを誘導するデザイン */
.popup-content {
  max-width: 480px;
  padding: 40px;
  text-align: center;
  position: relative;
}

/* 1. アイコン(最初に目が行く) */
.popup-icon {
  font-size: 64px;
  margin-bottom: 20px;
  animation: bounce 1s ease infinite;
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

/* 2. 見出し(2番目に目立つ) */
.popup-headline {
  font-size: 32px;
  font-weight: 800;
  margin: 0 0 15px 0;
  line-height: 1.2;
}

/* 3. 価値提案(3番目) */
.popup-value-proposition {
  font-size: 18px;
  line-height: 1.6;
  margin: 0 0 20px 0;
  color: #555;
}

/* 4. フォーム(アクション誘導) */
.popup-form {
  margin: 25px 0;
}

.popup-input {
  width: 100%;
  padding: 16px;
  border: 2px solid #E0E0E0;
  border-radius: 8px;
  font-size: 16px;
  margin-bottom: 12px;
  transition: border-color 0.3s;
}

.popup-input:focus {
  outline: none;
  border-color: #FF6B6B;
  box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.1);
}

/* 5. 信頼要素(補足情報) */
.trust-badge {
  font-size: 13px;
  opacity: 0.8;
}

心理的トリガーの実装:

// 緊急性と希少性を演出
class PsychologicalTriggers {
  // カウントダウンタイマー
  addCountdown(minutes = 5) {
    const countdownElement = document.createElement('div');
    countdownElement.className = 'countdown-timer';
    countdownElement.innerHTML = `
      <p class="countdown-text">このオファーは<span id="countdown-time"></span>で終了</p>
    `;
    
    document.querySelector('.popup-content').insertBefore(
      countdownElement,
      document.querySelector('.popup-form')
    );
    
    let timeLeft = minutes * 60;
    const timer = setInterval(() => {
      const minutesLeft = Math.floor(timeLeft / 60);
      const secondsLeft = timeLeft % 60;
      
      document.getElementById('countdown-time').textContent = 
        `${minutesLeft}:${secondsLeft.toString().padStart(2, '0')}`;
      
      timeLeft--;
      
      if (timeLeft < 0) {
        clearInterval(timer);
        document.getElementById('countdown-time').textContent = '0:00';
      }
    }, 1000);
  }
  
  // 在庫表示(希少性)
  addStockIndicator(stock = 'limited') {
    const stockElement = document.createElement('div');
    stockElement.className = 'stock-indicator';
    stockElement.innerHTML = `
      <span class="stock-icon">⚠️</span>
      <span class="stock-text">残りわずか!</span>
    `;
    
    document.querySelector('.popup-value-proposition').appendChild(stockElement);
  }
  
  // リアルタイム通知(社会的証明)
  addRealtimeNotification() {
    const notification = document.createElement('div');
    notification.className = 'realtime-notification';
    notification.innerHTML = `
      <span class="notification-icon">👤</span>
      <span class="notification-text">たった今、東京都の方が登録しました</span>
    `;
    
    document.body.appendChild(notification);
    
    setTimeout(() => {
      notification.classList.add('show');
      setTimeout(() => {
        notification.classList.remove('show');
      }, 5000);
    }, 3000);
  }
}

// 使用例
document.addEventListener('DOMContentLoaded', () => {
  const triggers = new PsychologicalTriggers();
  
  // ポップアップ表示時に実行
  document.addEventListener('mouseleave', function(e) {
    if (e.clientY < 0) {
      showPopup();
      triggers.addCountdown(5);
      triggers.addRealtimeNotification();
    }
  });
});

A/Bテストで検証された要素:

要素パターンAパターンB勝者改善率
見出し「お待ちください!」「特別オファー!」A+12%
割引率10%OFF15%OFFB+18%
CTAボタン色緑(#4CAF50)赤(#FF6B6B)B+23%
CTAテキスト「登録する」「今すぐ受け取る」B+15%
入力フィールド名前+メールメールのみB+31%

これらのベストプラクティスを組み合わせることで、エグジットインテントポップアップのコンバージョン率を大幅に向上させることができます。

表示タイミングとユーザー体験のバランス

エグジットインテントの効果を最大化するには、適切な表示タイミングとユーザー体験の保護のバランスが重要です。過度な表示はブランドイメージを損ない、適切なタイミングはコンバージョンを向上させます。

最適な表示タイミングの判断基準:

ユーザーの行動パターンを分析し、最も効果的なタイミングでポップアップを表示する実装が必要です。

// インテリジェントなタイミング制御
class SmartExitIntentTiming {
  constructor() {
    this.userBehavior = {
      pageViewTime: 0,
      scrollDepth: 0,
      interactions: 0,
      mouseMovements: 0,
      engagementLevel: 'low' // low, medium, high
    };
    
    this.thresholds = {
      minTimeOnPage: 10, // 最低10秒滞在
      minScrollDepth: 30, // 最低30%スクロール
      minInteractions: 2 // 最低2回のインタラクション
    };
    
    this.isQualified = false;
    this.init();
  }
  
  init() {
    this.trackPageViewTime();
    this.trackScrollDepth();
    this.trackInteractions();
    this.evaluateEngagement();
  }
  
  // ページ滞在時間の追跡
  trackPageViewTime() {
    const startTime = Date.now();
    
    setInterval(() => {
      this.userBehavior.pageViewTime = Math.floor((Date.now() - startTime) / 1000);
      this.checkQualification();
    }, 1000);
  }
  
  // スクロール深度の追跡
  trackScrollDepth() {
    window.addEventListener('scroll', () => {
      const scrollTop = window.scrollY;
      const docHeight = document.documentElement.scrollHeight - window.innerHeight;
      const scrollPercent = (scrollTop / docHeight) * 100;
      
      this.userBehavior.scrollDepth = Math.max(
        this.userBehavior.scrollDepth,
        Math.round(scrollPercent)
      );
      
      this.checkQualification();
    }, { passive: true });
  }
  
  // ユーザーインタラクションの追跡
  trackInteractions() {
    const events = ['click', 'focus', 'input'];
    
    events.forEach(eventType => {
      document.addEventListener(eventType, () => {
        this.userBehavior.interactions++;
        this.checkQualification();
      }, { passive: true });
    });
  }
  
  // エンゲージメントレベルの評価
  evaluateEngagement() {
    setInterval(() => {
      const { pageViewTime, scrollDepth, interactions } = this.userBehavior;
      
      // スコア計算
      let score = 0;
      if (pageViewTime > 30) score += 3;
      else if (pageViewTime > 15) score += 2;
      else if (pageViewTime > 5) score += 1;
      
      if (scrollDepth > 70) score += 3;
      else if (scrollDepth > 40) score += 2;
      else if (scrollDepth > 20) score += 1;
      
      if (interactions > 5) score += 3;
      else if (interactions > 2) score += 2;
      else if (interactions > 0) score += 1;
      
      // エンゲージメントレベルの判定
      if (score >= 7) {
        this.userBehavior.engagementLevel = 'high';
      } else if (score >= 4) {
        this.userBehavior.engagementLevel = 'medium';
      } else {
        this.userBehavior.engagementLevel = 'low';
      }
    }, 5000);
  }
  
  // 表示資格の確認
  checkQualification() {
    const { pageViewTime, scrollDepth, interactions } = this.userBehavior;
    
    this.isQualified = 
      pageViewTime >= this.thresholds.minTimeOnPage &&
      scrollDepth >= this.thresholds.minScrollDepth &&
      interactions >= this.thresholds.minInteractions;
  }
  
  // エンゲージメントに応じたオファーの選択
  getAppropriateOffer() {
    const offers = {
      high: {
        discount: '15%OFF',
        message: '熱心にご覧いただきありがとうございます!特別に15%OFFをプレゼント'
      },
      medium: {
        discount: '10%OFF',
        message: '初回限定10%OFFクーポンをプレゼント'
      },
      low: {
        discount: '送料無料',
        message: '今なら送料無料でお届けします'
      }
    };
    
    return offers[this.userBehavior.engagementLevel];
  }
  
  // 表示可否の判定
  shouldShowPopup() {
    return this.isQualified && this.userBehavior.engagementLevel !== 'low';
  }
}

// 使用例
const timingController = new SmartExitIntentTiming();

document.addEventListener('mouseleave', function(e) {
  if (e.clientY < 0 && timingController.shouldShowPopup()) {
    const offer = timingController.getAppropriateOffer();
    
    // オファー内容を動的に設定
    document.getElementById('popup-discount').textContent = offer.discount;
    document.getElementById('popup-message').textContent = offer.message;
    
    showPopup();
  }
});

ページタイプ別の表示戦略:

// ページタイプに応じた表示ルール
const PageTypeStrategy = {
  strategies: {
    'homepage': {
      enabled: false,
      reason: 'サイト全体の印象を優先'
    },
    'product': {
      enabled: true,
      delay: 15,
      message: '商品に興味を持っていただきありがとうございます'
    },
    'cart': {
      enabled: true,
      delay: 5,
      message: 'カートに商品がありますよ!今なら送料無料'
    },
    'checkout': {
      enabled: false,
      reason: '購入プロセスの妨げにならないよう'
    },
    'blog': {
      enabled: true,
      delay: 20,
      message: '記事をお楽しみいただけましたか?最新情報を受け取りませんか'
    },
    'thank-you': {
      enabled: false,
      reason: 'コンバージョン後は表示不要'
    }
  },
  
  // 現在のページタイプを判定
  getCurrentPageType() {
    const path = window.location.pathname;
    
    if (path === '/' || path === '/index.html') return 'homepage';
    if (path.includes('/product/') || path.includes('/item/')) return 'product';
    if (path.includes('/cart')) return 'cart';
    if (path.includes('/checkout')) return 'checkout';
    if (path.includes('/blog/') || path.includes('/article/')) return 'blog';
    if (path.includes('/thank-you') || path.includes('/complete')) return 'thank-you';
    
    return 'other';
  },
  
  // 戦略を取得
  getStrategy() {
    const pageType = this.getCurrentPageType();
    return this.strategies[pageType] || { enabled: true, delay: 10 };
  },
  
  // 表示可否を判定
  shouldShow() {
    const strategy = this.getStrategy();
    return strategy.enabled === true;
  }
};

頻度制限の実装:

// 表示頻度の制御
class FrequencyController {
  constructor() {
    this.storageKey = 'exitPopupFrequency';
    this.limits = {
      maxPerDay: 1,      // 1日1回まで
      maxPerWeek: 3,     // 1週間3回まで
      cooldownHours: 24  // 最低24時間の間隔
    };
  }
  
  // 表示履歴を取得
  getHistory() {
    const data = localStorage.getItem(this.storageKey);
    return data ? JSON.parse(data) : { timestamps: [] };
  }
  
  // 表示履歴を保存
  saveHistory(timestamp) {
    const history = this.getHistory();
    history.timestamps.push(timestamp);
    
    // 古い履歴を削除(7日以上前)
    const weekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
    history.timestamps = history.timestamps.filter(ts => ts > weekAgo);
    
    localStorage.setItem(this.storageKey, JSON.stringify(history));
  }
  
  // 1日の表示回数をカウント
  getCountToday() {
    const history = this.getHistory();
    const dayAgo = Date.now() - (24 * 60 * 60 * 1000);
    return history.timestamps.filter(ts => ts > dayAgo).length;
  }
  
  // 1週間の表示回数をカウント
  getCountThisWeek() {
    const history = this.getHistory();
    const weekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
    return history.timestamps.filter(ts => ts > weekAgo).length;
  }
  
  // 最後の表示からの経過時間(時間単位)
  getHoursSinceLastShow() {
    const history = this.getHistory();
    if (history.timestamps.length === 0) return Infinity;
    
    const lastShow = Math.max(...history.timestamps);
    return (Date.now() - lastShow) / (60 * 60 * 1000);
  }
  
  // 表示可否の判定
  canShow() {
    const countToday = this.getCountToday();
    const countWeek = this.getCountThisWeek();
    const hoursSinceLast = this.getHoursSinceLastShow();
    
    // すべての条件を満たす必要がある
    return (
      countToday < this.limits.maxPerDay &&
      countWeek < this.limits.maxPerWeek &&
      hoursSinceLast >= this.limits.cooldownHours
    );
  }
  
  // 表示を記録
  recordShow() {
    this.saveHistory(Date.now());
  }
}

// 統合実装
const frequencyController = new FrequencyController();

document.addEventListener('mouseleave', function(e) {
  if (
    e.clientY < 0 &&
    timingController.shouldShowPopup() &&
    PageTypeStrategy.shouldShow() &&
    frequencyController.canShow()
  ) {
    showPopup();
    frequencyController.recordShow();
  }
});

ユーザー体験を保護する実装パターン:

保護策実装内容効果
最低滞在時間10秒以上ページに滞在したユーザーのみ即離脱ユーザーを除外
エンゲージメント判定スクロール+インタラクションで判断興味のあるユーザーに絞る
頻度制限24時間に1回までしつこい印象を回避
ページタイプ別制御重要ページでは非表示コンバージョンフローを保護
Cookie確認コンバージョン済みには非表示既存顧客への配慮

適切なタイミング制御により、ユーザー体験を損なわず、最大限の効果を引き出すエグジットインテント実装が実現できます。

GDPR/Cookie規制への対応方法

エグジットインテントの実装では、個人情報保護法やGDPR(EU一般データ保護規則)、Cookie規制への対応が必須です。適切な法令遵守により、ユーザーの信頼を得ながら、安全にマーケティング活動を行えます。

Cookie同意バナーの実装:

// GDPR準拠のCookie同意管理
class CookieConsentManager {
  constructor() {
    this.consentKey = 'cookie_consent';
    this.consentCategories = {
      necessary: true,        // 必須Cookie(常に有効)
      analytics: false,       // アナリティクスCookie
      marketing: false,       // マーケティングCookie
      preferences: false      // 設定Cookie
    };
  }
  
  // 同意状態を確認
  hasConsent(category = 'marketing') {
    const consent = this.getConsent();
    return consent && consent === true;
  }
  
  // 同意状態を取得
  getConsent() {
    const data = localStorage.getItem(this.consentKey);
    return data ? JSON.parse(data) : null;
  }
  
  // 同意状態を保存
  saveConsent(categories) {
    const consent = {
      ...categories,
      timestamp: new Date().toISOString(),
      version: '1.0'
    };
    localStorage.setItem(this.consentKey, JSON.stringify(consent));
  }
  
  // 同意バナーを表示
  showConsentBanner() {
    if (this.getConsent()) return; // 既に同意済み
    
    const banner = document.createElement('div');
    banner.className = 'cookie-consent-banner';
    banner.innerHTML = `
      <div class="consent-content">
        <h3>Cookieの使用について</h3>
        <p>
          当サイトでは、サイトの利便性向上のためにCookieを使用しています。
          <a href="/privacy-policy" target="_blank">プライバシーポリシー</a>
        </p>
        <div class="consent-buttons">
          <button id="accept-all" class="consent-btn primary">
            すべて同意
          </button>
          <button id="accept-necessary" class="consent-btn secondary">
            必須のみ
          </button>
          <button id="customize" class="consent-btn tertiary">
            カスタマイズ
          </button>
        </div>
      </div>
    `;
    
    document.body.appendChild(banner);
    this.setupConsentHandlers(banner);
  }
  
  // 同意ボタンのイベント設定
  setupConsentHandlers(banner) {
    document.getElementById('accept-all').addEventListener('click', () => {
      this.saveConsent({
        necessary: true,
        analytics: true,
        marketing: true,
        preferences: true
      });
      banner.remove();
      this.initializeServices();
    });
    
    document.getElementById('accept-necessary').addEventListener('click', () => {
      this.saveConsent({
        necessary: true,
        analytics: false,
        marketing: false,
        preferences: false
      });
      banner.remove();
    });
    
    document.getElementById('customize').addEventListener('click', () => {
      this.showCustomizeModal();
    });
  }
  
  // カスタマイズモーダルを表示
  showCustomizeModal() {
    const modal = document.createElement('div');
    modal.className = 'consent-modal';
    modal.innerHTML = `
      <div class="modal-content">
        <h3>Cookie設定のカスタマイズ</h3>
        <div class="consent-options">
          <label class="consent-option">
            <input type="checkbox" checked disabled>
            <div>
              <strong>必須Cookie</strong>
              <p>サイトの基本機能に必要なCookie(無効化不可)</p>
            </div>
          </label>
          
          <label class="consent-option">
            <input type="checkbox" id="analytics-consent">
            <div>
              <strong>アナリティクスCookie</strong>
              <p>サイトの利用状況を分析するためのCookie</p>
            </div>
          </label>
          
          <label class="consent-option">
            <input type="checkbox" id="marketing-consent">
            <div>
              <strong>マーケティングCookie</strong>
              <p>パーソナライズされた広告を表示するためのCookie</p>
            </div>
          </label>
          
          <label class="consent-option">
            <input type="checkbox" id="preferences-consent">
            <div>
              <strong>設定Cookie</strong>
              <p>ユーザー設定を記憶するためのCookie</p>
            </div>
          </label>
        </div>
        <div class="modal-buttons">
          <button id="save-preferences" class="consent-btn primary">
            設定を保存
          </button>
          <button id="cancel-customize" class="consent-btn secondary">
            キャンセル
          </button>
        </div>
      </div>
    `;
    
    document.body.appendChild(modal);
    
    document.getElementById('save-preferences').addEventListener('click', () => {
      this.saveConsent({
        necessary: true,
        analytics: document.getElementById('analytics-consent').checked,
        marketing: document.getElementById('marketing-consent').checked,
        preferences: document.getElementById('preferences-consent').checked
      });
      modal.remove();
      document.querySelector('.cookie-consent-banner').remove();
      this.initializeServices();
    });
    
    document.getElementById('cancel-customize').addEventListener('click', () => {
      modal.remove();
    });
  }
  
  // 同意に基づいてサービスを初期化
  initializeServices() {
    const consent = this.getConsent();
    
    if (consent.analytics) {
      this.initAnalytics();
    }
    
    if (consent.marketing) {
      this.initMarketingTools();
    }
    
    if (consent.preferences) {
      this.initPreferences();
    }
  }
  
  initAnalytics() {
    console.log('Google Analytics初期化');
    // GA4の初期化コード
  }
  
  initMarketingTools() {
    console.log('マーケティングツール初期化');
    // エグジットインテントの有効化
  }
  
  initPreferences() {
    console.log('設定Cookie初期化');
  }
}

// GDPR準拠のエグジットインテント実装
const consentManager = new CookieConsentManager();

document.addEventListener('DOMContentLoaded', () => {
  consentManager.showConsentBanner();
  
  // マーケティングCookieの同意がある場合のみエグジットインテント有効化
  if (consentManager.hasConsent('marketing')) {
    initExitIntent();
  }
});

プライバシー配慮のデータ管理:

// 個人情報保護に配慮したデータ処理
class PrivacyCompliantStorage {
  constructor() {
    this.encryptionKey = this.generateKey();
  }
  
  // 簡易的な暗号化キーの生成
  generateKey() {
    return btoa(Math.random().toString(36).substring(2));
  }
  
  // データの暗号化(簡易版)
  encrypt(data) {
    return btoa(JSON.stringify(data));
  }
  
  // データの復号化(簡易版)
  decrypt(encryptedData) {
    try {
      return JSON.parse(atob(encryptedData));
    } catch (e) {
      return null;
    }
  }
  
  // 個人情報を含まないデータのみ保存
  saveNonPersonalData(data) {
    // 個人を特定できる情報を除外
    const sanitized = {
      visitCount: data.visitCount,
      lastVisit: data.lastVisit,
      preferences: data.preferences,
      // メールアドレスなどは保存しない
    };
    
    const encrypted = this.encrypt(sanitized);
    localStorage.setItem('user_behavior', encrypted);
  }
  
  // データの取得
  getNonPersonalData() {
    const encrypted = localStorage.getItem('user_behavior');
    return encrypted ? this.decrypt(encrypted) : null;
  }
  
  // データ削除(ユーザーの権利)
  deleteAllData() {
    localStorage.clear();
    // サーバー側のデータも削除するAPIを呼び出す
    this.requestServerDataDeletion();
  }
  
  // サーバー側データの削除リクエスト
  async requestServerDataDeletion() {
    try {
      await fetch('/api/gdpr/delete-my-data', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
      });
      console.log('データ削除リクエスト送信完了');
    } catch (error) {
      console.error('データ削除エラー:', error);
    }
  }
  
  // データのエクスポート(ユーザーの権利)
  async exportMyData() {
    const localData = {
      localStorage: { ...localStorage },
      cookies: document.cookie
    };
    
    // サーバー側のデータも取得
    try {
      const serverData = await fetch('/api/gdpr/export-my-data').then(r => r.json());
      const allData = { ...localData, ...serverData };
      
      // JSONファイルとしてダウンロード
      const dataStr = JSON.stringify(allData, null, 2);
      const dataBlob = new Blob([dataStr], { type: 'application/json' });
      const url = URL.createObjectURL(dataBlob);
      const link = document.createElement('a');
      link.href = url;
      link.download = 'my-data.json';
      link.click();
    } catch (error) {
      console.error('データエクスポートエラー:', error);
    }
  }
}

const privacyStorage = new PrivacyCompliantStorage();

GDPR準拠のCSS:

/* Cookie同意バナー */
.cookie-consent-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #ffffff;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
  z-index: 10000;
  animation: slideUp 0.3s ease-out;
}

.consent-content {
  max-width: 1200px;
  margin: 0 auto;
}

.consent-content h3 {
  margin: 0 0 10px 0;
  font-size: 18px;
}

.consent-content p {
  margin: 0 0 15px 0;
  font-size: 14px;
  color: #666;
}

.consent-buttons {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.consent-btn {
  padding: 10px 20px;
  border-radius: 6px;
  border: none;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  transition: all 0.3s;
}

.consent-btn.primary {
  background: #4CAF50;
  color: white;
}

.consent-btn.secondary {
  background: #E0E0E0;
  color: #333;
}

.consent-btn.tertiary {
  background: transparent;
  color: #666;
  text-decoration: underline;
}

/* カスタマイズモーダル */
.consent-modal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10001;
}

.modal-content {
  background: white;
  padding: 30px;
  border-radius: 12px;
  max-width: 600px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
}

.consent-option {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 15px;
  border: 1px solid #E0E0E0;
  border-radius: 8px;
  margin-bottom: 12px;
  cursor: pointer;
}

.consent-option input[type="checkbox"] {
  margin-top: 3px;
}

.consent-option strong {
  display: block;
  margin-bottom: 5px;
}

.consent-option p {
  font-size: 13px;
  color: #666;
  margin: 0;
}

GDPR/Cookie規制への適切な対応により、法令遵守とユーザーの信頼を確保しながら、効果的なエグジットインテントマーケティングを実施できます。


よくある質問(FAQ)

エグジットインテントは全ページに設置すべき?

**結論から言うと、全ページに設置すべきではありません。**ページの目的や種類によって、エグジットインテントの設置可否を判断する必要があります。

設置を推奨するページ:

商品詳細ページやランディングページなど、コンバージョンを直接目的とするページでは、エグジットインテントが高い効果を発揮します。これらのページでは、ユーザーが購買意欲を持ちながらも何らかの理由で躊躇している場合が多く、適切なタイミングでのオファー提示が購入の後押しになります。

具体的には以下のようなページで効果的です。

  • 商品詳細ページ: 割引クーポンや送料無料オファーで購入を後押し
  • 価格表ページ: 無料トライアルや相談予約への誘導
  • ブログ記事: メルマガ登録やホワイトペーパーダウンロード
  • カートページ: カート放棄を防ぐための限定オファー

また、同一ユーザーに対して、同じセッション内で複数回表示することは避けるべきです。頻度制限を設けて、24時間に1回程度に抑えることで、ユーザー体験を保護しながら効果を最大化できます。

戦略的なページ選択により、サイト全体のコンバージョン率を向上させつつ、ユーザーの満足度も維持することが可能です。

モバイルとPCで表示内容を変えるべき?

**はい、モバイルとPCでは表示内容やデザインを最適化すべきです。**デバイスによってユーザーの行動パターンや画面サイズが大きく異なるため、それぞれに適した実装が必要です。

デバイス別の最適化ポイント:

モバイルデバイスでは、画面サイズが小さく、タッチ操作が主体となるため、PCとは異なるアプローチが求められます。

// デバイス判定と最適化
function optimizeForDevice() {
  const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  
  if (isMobile) {
    // モバイル最適化
    return {
      position: 'bottom', // 画面下部に配置
      layout: 'vertical', // 縦長レイアウト
      fontSize: '16px', // 自動ズーム防止
      buttonSize: '44px', // タップ領域を確保
      contentLength: 'short', // 簡潔なメッセージ
      animationType: 'slideUp' // 下からスライドアップ
    };
  } else {
    // PC最適化
    return {
      position: 'center', // 画面中央に配置
      layout: 'horizontal', // 横長レイアウト可
      fontSize: '14-16px', // 標準サイズ
      buttonSize: '36px', // 通常サイズ
      contentLength: 'medium', // 詳細な説明可
      animationType: 'fadeScale' // フェード+スケール
    };
  }
}

表示タイミングの違い:

モバイルでは従来のマウスベースのエグジットインテント検知ができないため、代替トリガーを使用します。

PC: マウスが画面上端に移動した瞬間を検知 モバイル: 以下の条件で表示

  • 30秒以上の非アクティブ状態
  • ページの70%以上をスクロール後
  • 戻るボタンのタップ時
  • 画面上部へのスワイプ動作

デバイス特性に応じた最適化により、コンバージョン率を最大20-30%向上させることができます。

ポップアップブロッカーへの対策は?

**エグジットインテントポップアップは、通常のポップアップブロッカーの影響を受けません。**ただし、広告ブロッカーによってブロックされる可能性があるため、適切な対策が必要です。

ポップアップブロッカーとの違い:

ブラウザ標準のポップアップブロッカーは、window.open()で新しいウィンドウを開く動作をブロックします。しかし、エグジットインテントは同一ページ内のHTML要素を表示/非表示するだけなので、基本的にはブロックされません。

広告ブロッカーへの対策:

広告ブロッカー(AdBlock、uBlock Origin等)は、特定のクラス名やID名を検知してブロックします。

// 広告ブロッカー検知コード
function detectAdBlocker() {
  const testElement = document.createElement('div');
  testElement.className = 'ads banner ad-container'; // 検知されやすい名前
  testElement.style.height = '1px';
  testElement.style.position = 'absolute';
  document.body.appendChild(testElement);
  
  setTimeout(() => {
    const isBlocked = testElement.offsetHeight === 0;
    document.body.removeChild(testElement);
    
    if (isBlocked) {
      console.log('広告ブロッカー検知:代替手段を使用');
      useAlternativeMethod();
    }
  }, 100);
}

function useAlternativeMethod() {
  // クラス名を変更
  const popup = document.getElementById('exit-intent-overlay');
  popup.className = 'user-notification-panel'; // 広告と誤認されない名前
}

ブロック回避のベストプラクティス:

避けるべき単語:

  • “ad”, “ads”, “advertisement”
  • “banner”, “popup”
  • “promo”, “sponsored”

推奨する単語:

  • “notification”, “message”
  • “modal”, “dialog”
  • “overlay”, “content”

フォールバック戦略:

ポップアップが表示できない場合の代替手段も用意しておくことが重要です。

// フォールバック実装
function showExitIntent() {
  const popup = document.getElementById('exit-popup-overlay');
  popup.style.display = 'flex';
  
  // 表示確認
  setTimeout(() => {
    if (popup.offsetHeight === 0) {
      // ブロックされた場合の代替手段
      showStickyBanner(); // 画面上部の固定バナー
    }
  }, 200);
}

function showStickyBanner() {
  const banner = document.createElement('div');
  banner.className = 'top-notification-bar';
  banner.innerHTML = '初回限定10%OFF!今すぐメール登録 <button>閉じる</button>';
  document.body.insertBefore(banner, document.body.firstChild);
}

適切な対策により、広告ブロッカーの影響を最小限に抑え、より多くのユーザーにポップアップを表示できます。

WordPressでの実装方法は?

**WordPressでは、プラグインを使った簡単な実装と、カスタムコードを使った柔軟な実装の2つの方法があります。**サイトの要件や技術レベルに応じて選択できます。

方法1: プラグインを使った実装(推奨初心者向け):

WordPressには、エグジットインテント機能を持つ複数のプラグインがあります。

主なプラグイン:

  • OptinMonster: 最も人気のあるポップアッププラグイン
  • Popup Maker: 無料で高機能
  • Thrive Leads: マーケティングに特化
  • Hustle: 無料でWordPress公式

これらのプラグインを使えば、コーディング不要でエグジットインテントを実装できます。

方法2: カスタムコードでの実装:

より柔軟な制御が必要な場合は、テーマファイルにカスタムコードを追加します。

<?php
// functions.phpに追加
function add_exit_intent_popup() {
    // 特定のページでのみ表示
    if (is_singular('product') || is_page('pricing')) {
        ?>
        <script>
        document.addEventListener('DOMContentLoaded', function() {
            let popupShown = false;
            
            document.addEventListener('mouseleave', function(e) {
                if (e.clientY < 0 && !popupShown) {
                    document.getElementById('exit-popup').style.display = 'flex';
                    popupShown = true;
                }
            });
        });
        </script>
        
        <div id="exit-popup" style="display:none;">
            <div class="popup-content">
                <h2><?php echo get_option('exit_popup_title', 'お待ちください!'); ?></h2>
                <p><?php echo get_option('exit_popup_message', '今なら10%OFFクーポンプレゼント'); ?></p>
                <?php echo do_shortcode('[contact-form-7 id="123"]'); ?>
            </div>
        </div>
        <?php
    }
}
add_action('wp_footer', 'add_exit_intent_popup');

Contact Form 7との連携:

// Contact Form 7のフォームをポップアップ内に表示
function exit_popup_with_cf7() {
    ?>
    <div id="exit-popup-overlay" class="popup-overlay">
        <div class="popup-content">
            <button class="popup-close">&times;</button>
            <h2>特別オファー!</h2>
            <p>メール登録で10%OFFクーポンプレゼント</p>
            <?php echo do_shortcode('[contact-form-7 id="456" title="Exit Popup Form"]'); ?>
        </div>
    </div>
    <?php
}
add_action('wp_footer', 'exit_popup_with_cf7');

WordPressでの実装のポイント:

WordPressの豊富なプラグインエコシステムとフックシステムを活用することで、効率的にエグジットインテントを実装できます。


まとめ:エグジットインテント実装で離脱率を改善しよう

エグジットインテントのJavaScript実装について、基本的なコード例から高度なカスタマイズ方法まで、包括的に解説してきました。本記事の重要なポイントをまとめます。

エグジットインテント実装の核心:

エグジットインテントは、ユーザーがページを離れようとする瞬間を検知し、最後のチャンスとしてオファーを提示する強力なマーケティング手法です。適切に実装すれば、カート放棄率を10〜20%削減し、コンバージョン率を最大46%向上させることが可能です。

実装の基本ステップ:

  1. HTMLでポップアップ構造を作成 – 見出し、説明文、フォーム、閉じるボタンを配置
  2. JavaScriptでmouseleaveイベントを検知 – clientYが負の値になったらポップアップ表示
  3. CSSでデザインを最適化 – レスポンシブ対応とアニメーション効果
  4. Cookie/LocalStorageで表示制御 – 1回のみ表示、または一定期間後に再表示

成功のカギとなる要素:

  • 適切なタイミング: ページ滞在10秒以上、スクロール50%以上などの条件設定
  • 魅力的なオファー: 割引、送料無料、限定コンテンツなどの具体的なベネフィット
  • シンプルなデザイン: 情報過多を避け、1つの明確なCTAに集中
  • モバイル最適化: タッチイベントや時間ベースのトリガーで代替実装
  • 頻度制限: 24時間に1回程度に抑えてユーザー体験を保護

より高度な実装のために:

A/Bテストによる継続的な改善、Google Analyticsとの連携による効果測定、GDPR/Cookie規制への適切な対応が、長期的な成功には不可欠です。また、ページタイプに応じた戦略的な設置場所の選定により、サイト全体のパフォーマンスを最適化できます。

コード実装が難しい場合の選択肢:

本記事で紹介したコードをコピー&ペーストすれば、基本的なエグジットインテント機能は今すぐ実装できます。しかし、より高度な機能やノーコードでの実装を希望する場合は、専門的なツールの活用も検討価値があります。

エグジットインテント実装により、サイトを訪れたユーザーの貴重な機会を逃さず、コンバージョン率の向上とビジネス成長を実現しましょう。今日から実装を始めて、効果を測定し、継続的に改善していくことが成功への道です。


外部参考、引用記事

MDN Web Docs – mouseleave イベント

MDN Web Docs – Cookie の使用

Google Analytics 4 – イベントトラッキング

関連記事

DataPush – 離脱防止ポップアップサービス

ポップアップとは?意味・種類・効果と正しい活用法をわかりやすく解説

【2026年最新】ポップアップツール完全ガイド:失敗しないツール選びと無料A/Bテストで最適解を見つける方法

ポップアップの最適なタイミング設定|CVR向上とエグジットインテント活用法

WordPressでカート放棄ポップアップを設定する方法【完全ガイド】の記事作成

離脱防止ポップアップ完全導入・運用マニュアル|設計・実装・改善まで徹底解説【2026年版】

離脱率を15%改善を目指す!
DataPushデータプッシュを無料トライアル
■無料で利用可能 ■A/Bテスト可能 ■ノーコードで設定
今すぐ試す →