ECサイトを運営していると、「この商品は単体では使えません」「別の商品と一緒に購入してください」といった注意事項をお客様に伝えたい場面があります。

例えば:

  • プリンターのインクカートリッジ(対応機種の確認)
  • 浄水器の交換用カートリッジ(本体のスパウトが必要)
  • スマホのケース(機種の確認)

今回は、アプリ不要・無料で、Liquidテーマのカスタマイズだけで確認ポップアップを実装する方法を解説します。


    完成イメージ

    実装すると、以下のような動作になります。

    1. お客様が「カートに追加」ボタンをクリック
          ↓
    2. 確認ポップアップが表示される
          ↓
    3-A.「はい」→ カートに商品が追加される
    3-B.「本体ページを見る」→ 別の商品ページへ遷移
    

    ポップアップの機能

    • 特定の商品ページでのみ表示
    • 「はい」でカート追加を実行
    • 「本体ページを見る」で別ページへ遷移
    • 「今後このメッセージを表示しない」チェックボックス(localStorage使用)
    • ESCキーで閉じる
    • オーバーレイ背景クリックで閉じる
    • レスポンシブ対応(モバイル対応)

    実装手順

    手順1: テーマのコード編集画面を開く

    1. Shopify管理画面にログイン
    2. 左メニュー「オンラインストア」→「テーマ」
    3. 現在のテーマの「」をクリック
    4. コードを編集」を選択

    手順2: snippetファイルを作成

    1. 左サイドバーの「snippets」フォルダを開く
    2. 新しいsnippetを追加」をクリック
    3. ファイル名を popup-caution と入力(拡張子.liquidは自動付与)
    4. 「作成」をクリック

    手順3: ポップアップのコードを貼り付け

    作成した popup-caution.liquid に以下のコードを貼り付けます。

    {% comment %}
      カートリッジ商品用 確認ポップアップ
      - 「本体ページを見る」→ 本体ページへ遷移
      - 「今後このメッセージを表示しない」チェックで次回以降スキップ
      - ESCキーで閉じる
    {% endcomment %}
    
    <style>
    .caution-popup-overlay {
    	position: fixed;
    	inset: 0;
    	display: none;
    	opacity: 0;
    	pointer-events: none;
    	transition: opacity 0.3s ease;
    	z-index: 999999;
    	background: rgba(0, 0, 0, 0.5);
    }
    
    .caution-popup-overlay.is-open {
    	display: block;
    	opacity: 1;
    	pointer-events: auto;
    }
    
    .caution-popup-container {
    	position: absolute;
    	top: 50%;
    	left: 50%;
    	transform: translate(-50%, -50%);
    	max-width: 90%;
    	background: #fff;
    	border-radius: 0.5rem;
    	/* padding  上  横  下 */
    	padding: 3rem 3rem 2rem;
    	text-align: center;
    	box-shadow: 0 0.75rem 1.875rem rgba(0, 0, 0, 0.2);
    }
    
    .caution-popup-close {
    	position: absolute;
    	top: 0.25rem;
    	right: 0.5rem;
    	font-size: 2rem;
    	background: none;
    	color: #666;
    	border: none;
    	cursor: pointer;
    }
    
    .caution-popup-close:hover {
    	color: #000;
    }
    
    .caution-popup-title {
    	font-size: 1.5rem;
    	font-weight: bold;
    	color: #333;
    	margin-bottom: 1rem;
    }
    
    .caution-popup-content {
    	font-size: 1rem;
    	color: #666;
    	margin-bottom: 1.5rem;
    	line-height: 1.6;
    }
    
    .caution-popup-buttons {
    	display: flex;
    	justify-content: center;
    	gap: 1rem;
    	flex-wrap: wrap;
    }
    
    .caution-popup-yes,
    .caution-popup-no {
    	cursor: pointer;
    	border: none;
    	border-radius: 0.375rem;
    	padding: 0.8rem 1.5rem;
    	font-weight: 600;
    	font-size: 1rem;
    	transition: all 0.2s ease;
    }
    
    .caution-popup-yes {
    	color: #fff;
    	background: #333;
    }
    
    .caution-popup-yes:hover {
    	background: #555;
    }
    
    .caution-popup-no {
    	color: #333;
    	background: #ddd;
    }
    
    .caution-popup-no:hover {
    	background: #ccc;
    }
    
    /* チェックボックス */
    .caution-popup-remember {
    	margin-top: 1.25rem;
    }
    
    .caution-popup-remember-label {
    	display: inline-flex;
    	align-items: center;
    	gap: 0.5rem;
    	cursor: pointer;
    	font-size: 0.875rem;
    	color: #666;
    }
    
    .caution-popup-remember-label input[type="checkbox"] {
    	width: 1rem;
    	height: 1rem;
    	cursor: pointer;
    }
    
    @media (max-width: 744px) {
    	.caution-popup-title {
    		font-size: 1.125rem;
    	}
    
    	.caution-popup-content {
    		font-size: 0.875rem;
    	}
    
    	.caution-popup-yes,
    	.caution-popup-no {
    		font-size: 0.875rem;
    		padding: 0.7rem 1.2rem;
    	}
    
    	.caution-popup-remember-label {
    		font-size: 0.8125rem;
    	}
    }
    </style>
    
    <!-- オーバーレイ -->
    <div class="caution-popup-overlay" id="caution-popup-overlay">
      <div class="caution-popup-container">
        <button type="button" class="caution-popup-close" id="caution-popup-close" aria-label="閉じる">×</button>
        <div class="caution-popup-title">カートリッジだけではご利用できません。</div>
        <div class="caution-popup-content">本体はお持ちですか?</div>
        <div class="caution-popup-buttons">
          <button type="button" class="caution-popup-yes" id="caution-popup-yes">はい</button>
          <button type="button" class="caution-popup-no" id="caution-popup-no">本体ページを見る</button>
        </div>
        <div class="caution-popup-remember">
          <label class="caution-popup-remember-label">
            <input type="checkbox" id="caution-popup-remember-checkbox">
            <span>今後このメッセージを表示しない</span>
          </label>
        </div>
      </div>
    </div>
    
    <script>
    (function() {
      const STORAGE_KEY = 'caution_popup_skip';
      
      // ★ ポップアップを表示する商品ページのURLに含まれる文字列
      // 複数指定する場合は配列に追加
      const TARGET_URL_PATTERNS = ['cartridge'];
      
      // ★ 「いいえ」ボタンで遷移する先のURL
      const REDIRECT_URL = '/products/starterset';
      
      // 対象ページかどうか判定
      function isTargetPage() {
        return TARGET_URL_PATTERNS.some(pattern => 
          window.location.href.includes(pattern)
        );
      }
      
      // 対象ページでなければ何もしない
      if (!isTargetPage()) return;
      
      // 「表示しない」設定済みならスキップ
      function shouldSkipPopup() {
        return localStorage.getItem(STORAGE_KEY) === 'true';
      }
    
      let currentAtcButton = null;
      const overlay = document.getElementById('caution-popup-overlay');
    
      function openPopup() {
        overlay.classList.add('is-open');
      }
    
      function closePopup() {
        overlay.classList.remove('is-open');
      }
    
      // 「はい」クリック時、実際にカートに追加
      function confirmAddToCart() {
        if (!currentAtcButton) return;
        
        // チェックボックスがONなら記憶
        const checkbox = document.getElementById('caution-popup-remember-checkbox');
        if (checkbox && checkbox.checked) {
          localStorage.setItem(STORAGE_KEY, 'true');
        }
        
        // overrideフラグを立てて再クリック
        currentAtcButton.dataset.override = 'true';
        currentAtcButton.click();
        currentAtcButton.dataset.override = '';
        
        currentAtcButton = null;
        closePopup();
      }
    
      // ボタンのclickイベントをキャプチャフェーズでインターセプト
      document.addEventListener('click', function(e) {
        // スキップ設定済みなら何もしない
        if (shouldSkipPopup()) return;
        
        const btn = e.target.closest('button[type="submit"], .product-form__submit, [name="add"]');
        if (!btn) return;
        
        const form = btn.closest('form[action*="/cart/add"]');
        if (!form) return;
    
        // overrideフラグがあればスキップ(2回目のクリック)
        if (btn.dataset.override === 'true') {
          return;
        }
    
        // ポップアップを出してカート追加をブロック
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        
        currentAtcButton = btn;
        openPopup();
      }, true);
    
      // フォームのsubmitもブロック
      document.addEventListener('submit', function(e) {
        // スキップ設定済みなら何もしない
        if (shouldSkipPopup()) return;
        
        const form = e.target;
        if (!form.action || !form.action.includes('/cart/add')) return;
        
        const btn = form.querySelector('button[type="submit"], .product-form__submit, [name="add"]');
        if (btn && btn.dataset.override === 'true') {
          return;
        }
    
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
      }, true);
    
      // ESCキーでポップアップを閉じる
      document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape' && overlay.classList.contains('is-open')) {
          closePopup();
        }
      });
    
      // DOMContentLoaded後にボタンのイベント設定
      document.addEventListener('DOMContentLoaded', function() {
        document.getElementById('caution-popup-close').addEventListener('click', closePopup);
        document.getElementById('caution-popup-yes').addEventListener('click', confirmAddToCart);
        document.getElementById('caution-popup-no').addEventListener('click', function() {
          window.location.href = REDIRECT_URL;
        });
        overlay.addEventListener('click', function(e) {
          if (e.target === overlay) closePopup();
        });
      });
    })();
    </script>
    

    手順4: theme.liquidでsnippetを読み込む

    1. 左サイドバーの「Layout」フォルダを開く
    2. theme.liquid をクリックして開く
    3. </body> タグの直前に以下のコードを追加:
      ...
      {% render 'popup-caution' %}
    </body>
    </html>
    

    手順5: 保存して動作確認

    1. 右上の「保存」ボタンをクリック
    2. 対象の商品ページにアクセス
    3. 「カートに追加」ボタンをクリック
    4. ポップアップが表示されることを確認

    カスタマイズポイント

    対象ページの変更

    URLに含まれる文字列でフィルタリングしています。

    // ★ ポップアップを表示する商品ページのURLに含まれる文字列
    const TARGET_URL_PATTERNS = ['cartridge'];
    

    複数パターンを指定する場合:

    const TARGET_URL_PATTERNS = ['cartridge', 'refill', 'ink'];
    

    遷移先URLの変更

    「本体ページを見る」ボタンの遷移先を変更:

    // ★ 「いいえ」ボタンで遷移する先のURL
    const REDIRECT_URL = '/products/starterset';
    

    ポップアップのテキスト変更

    HTML部分を編集:

    <div class="caution-popup-title">ここにタイトル</div>
    <div class="caution-popup-content">ここに説明文</div>
    

    ボタンのラベル:

    <button ... id="caution-popup-yes">はい</button>
    <button ... id="caution-popup-no">本体ページを見る</button>
    

    デザインの変更

    CSSの主要なカラー設定:

    /* 「はい」ボタン */
    .caution-popup-yes { color: #fff; background: #333; }
    
    /* 「いいえ」ボタン */
    .caution-popup-no { color: #333; background: #ddd; }
    
    /* オーバーレイ背景 */
    .caution-popup-overlay { background: rgba(0,0,0,0.5); }
    

    「今後表示しない」機能を無効にする

    チェックボックスが不要な場合は、HTML部分を削除:

    <!-- この部分を削除 -->
    <div class="caution-popup-remember">
      ...
    </div>
    

    技術的な解説

    なぜイベントのキャプチャフェーズを使うのか

    Shopifyの多くのテーマ(Horizon、Dawnなど)は、カート追加処理をJavaScriptで非同期に行います。通常のイベントリスナーでは、テーマのJavaScriptより先にイベントをキャッチできません。

    // 第3引数を true にするとキャプチャフェーズで実行
    document.addEventListener('click', function(e) {
      // ...
    }, true);
    

    overrideフラグの仕組み

    「はい」ボタンクリック時、再度カート追加ボタンをクリックしますが、その際にポップアップが再表示されないよう、data-override 属性でフラグ管理しています。

    // 「はい」クリック時
    currentAtcButton.dataset.override = 'true';
    currentAtcButton.click();  // 再クリック
    currentAtcButton.dataset.override = '';
    
    // イベントリスナー内
    if (btn.dataset.override === 'true') {
      return;  // フラグがあればスキップ
    }
    

    localStorageでの記憶

    「今後このメッセージを表示しない」にチェックを入れると、localStorageに保存されます。

    localStorage.setItem('caution_popup_skip', 'true');
    

    ブラウザのキャッシュをクリアするか、開発者ツールで削除するまで有効です。


    トラブルシューティング

    ポップアップが表示されない

    1. theme.liquid{% render 'popup-caution' %} が追加されているか確認
    2. 対象URLのパターン(TARGET_URL_PATTERNS)が正しいか確認
    3. ブラウザの開発者ツールでJavaScriptエラーがないか確認

    「はい」を押してもカートに追加されない

    テーマによってカート追加ボタンのセレクタが異なります。以下の部分を調整:

    const btn = e.target.closest('button[type="submit"], .product-form__submit, [name="add"]');
    

    「今後表示しない」が効かない

    localStorageが無効になっていないか確認。プライベートブラウズモードでは動作しない場合があります。


    まとめ

    Shopifyで商品カート追加時に確認ポップアップを表示する方法を解説しました。

    • snippetファイルにポップアップのHTML/CSS/JSをまとめる
    • theme.liquidで読み込む
    • イベントのキャプチャフェーズでカート追加をインターセプト
    • localStorageで「今後表示しない」を記憶

    アプリを使わずに実装できるので、興味のある方は試してみてください。


    参考リンク