ComplianzプラグインがCocoonのスマホメニューを壊す。この競合は設定では直せなかったので、自作GDPRバナーで解決しました。Consent Mode対応・GeoIP判定付きで、コードはこの記事からコピペで使えます。
GDPR対応でComplianzを入れた直後、スマホのハンバーガーメニューが壊れました。メニューを開いてリンクをタップし、ブラウザの戻るボタンを押すと、メニューが開いたままページが表示される。iPhone SafariでもAndroid Chromeでも同じ症状が出て、Complianzを無効化すると即座に直る。原因はComplianzとCocoonの競合でした。
ホワイトリスト設定やScript Center(有料版限定)を試しても解決せず、代替プラグインもPV制限や有料アドオンの壁にぶつかり、最終的に「自分で作ればいい」という結論に至りました。この記事では、原因の調査過程から自作バナーの完成コードまでを全部公開します。
図.メニューが開いたままの状態
図.Complianz無効化で正常に閉じている状態
発生した症状と再現条件
Complianzが有効な状態でのみ、ブラウザバック時にCocoonのハンバーガーメニューが閉じなくなります。ブラウザの種類に関係なく再現するので、iOS/Android固有の問題ではなくWordPress側の競合です。
通常の動作なら、メニューを開いてリンクをタップし、ブラウザバックで戻ると、メニューは閉じた状態で前のページが表示されます。しかしComplianzが有効だと、メニューが画面を覆ったまま戻ってくる。コンテンツが読めないので、ユーザーは毎回メニューを手動で閉じなければなりません。
再現条件は以下の通りです。
- テーマ:Cocoon
- プラグイン:Complianz GDPR/CCPA Cookie Consent(無料版)
- デバイス:スマートフォン(iPhone Safari、Android Chrome 両方で再現)
- 操作:ハンバーガーメニュー → リンクタップ → ブラウザバック
原因の調査:何が競合しているのか
犯人はComplianzプラグイン本体です。自作コードの無効化では直らず、Complianzの無効化で即座に直ったことで確定しました。
最初に疑ったのは自分が追加したGeoIP判定のカスタムコードでした。Code Snippetsで追加していたスニペットを無効化してみましたが、症状は変わらず。次にComplianzプラグイン自体を無効化したところ、メニューは正常に動作しました。
ただし、Complianzは世界中で使われている定番プラグインです。こんな致命的な問題があれば大騒ぎになっているはず。つまりこれは単体のバグではなく、CocoonとComplianzの組み合わせで発生する競合と考えるのが妥当です。
競合の原因として考えた3つの仮説
両方のソースコードを完全に読み解く時間はなかったので、仮説ベースで対策を試しました。
仮説1:JavaScriptの競合。ComplianzはGDPR対応のためにGoogle Analyticsなどのスクリプトをブロックする機能を持っています。この「スクリプトブロック」がCocoonのメニュー制御用JavaScriptにまで影響している可能性。Cocoonはメニューのopen/closeをJavaScriptで管理しているので、これが干渉されればメニューの状態がおかしくなるのは当然です。
仮説2:CSSの競合。Complianzが読み込むバナー/モーダル用のCSSが、z-indexやposition、displayプロパティあたりでCocoonのメニューに干渉している可能性。
仮説3:bfcacheとの相性。ブラウザの「戻る」操作を高速化するbfcache(Back-Forward Cache)がページ状態をメモリに保持する際、ComplianzがJavaScriptの状態復元に悪影響を与えている可能性。
試した対策と、すべて失敗した記録
ホワイトリスト設定、Script Center、無料版で使える設定の全調整——どれも効果はありませんでした。
対策1:Complianzのホワイトリスト設定
仮説1(JS競合)が正しいなら、Cocoon関連のスクリプトをブロック対象から除外すれば直るはず。cmplz_whitelisted_script_tagsフィルターフックを使って試しました。
|
1 2 3 4 5 6 |
add_filter('cmplz_whitelisted_script_tags', function($tags) { $tags[] = 'cocoon'; $tags[] = 'javascript.js'; $tags[] = 'jquery'; return $tags; }); |
キーワードを何パターンか変えて試しましたが、効果なし。
対策2:Script Centerでの制御
Complianzには「Script Center」という機能があり、スクリプトの細かい除外制御ができるという情報を見つけました。設定画面を開いてみると——タブが見当たらない。
調べてみると、Script Centerは有料版(Premium)限定の機能でした。年間のライセンス料を考えると、この一つの問題のために支払うのは躊躇われます。
対策3:無料版の設定を片っ端から調整
統合機能(Integration)の無効化、スクリプトブロック設定の変更、バナー表示タイミングの調整、キャッシュ関連の設定——使える設定はすべて試しましたが、どれも効果なし。この時点でComplianzでの解決は諦めました。
代替プラグインもすべて壁にぶつかった
CookieYesはPV制限、GDPR Cookie Complianceは有料アドオン、Cookiebotも有料——しかもどれもCocoonとの相性は未知数。
CookieYes:Google Site Kit公式ドキュメントで推奨されていて信頼性は高い。しかし無料プランは月間5,000PVまでという制限があり、私のブログでは足りません。
GDPR Cookie Compliance:Site Kitとの統合機能がありますが、それは有料アドオン。無料では基本機能のみ。
Cookiebot:企業向けのしっかりしたサービスでConsent Mode対応。ただしフル機能は有料プラン。
どのプラグインも無料では制限がある。そして何より、別のプラグインを入れてもCocoonとの相性問題が起きない保証はどこにもない。ここで発想を転換しました。
自作GDPRバナーで根本解決する
自分で書いたコードならCocoonとの競合リスクはゼロ。PV制限もなく、ランニングコストもかかりません。
考えてみれば、GDPRのクッキー同意バナーに必要な機能はそこまで多くありません。
- EU圏からのアクセスを判定する(GeoIP)
- バナーを表示して同意/拒否を選択させる
- 選択結果をCookieに保存する
- Google Consent Modeに同意状態を伝える
自作ならスクリプトブロック機能もないので、テーマの動作に干渉しません。軽量で高速、デザインも文言も自由にカスタマイズできる。問題が起きても原因特定が容易です。
図.自作バナーの表示(モバイル)
図.Cookie Settingsボタン
完成コード
functions.phpまたはCode Snippetsプラグインに追加して使えます。GeoIP判定(フォールバック付き)、Google Consent Mode連携、WP Consent API対応、設定変更機能を含む完全版です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
// ===================================================== // GDPR自作バナー(Site Kit Consent Mode対応) // ===================================================== // 1. バナーHTML + CSS add_action('wp_footer', function() { ?> <div id="gdpr-banner" style="display:none; position:fixed; bottom:0; left:0; right:0; background:#1a1a2e; color:#fff; padding:20px; z-index:999999; box-shadow:0 -2px 10px rgba(0,0,0,0.3);"> <div style="max-width:1200px; margin:0 auto; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:15px;"> <p style="margin:0; flex:1; min-width:280px; font-size:14px; line-height:1.6;"> We use cookies for analytics and advertising. By continuing to browse, you consent to our use of cookies. <a href="/privacy-policy-en/" style="color:#6eb5ff; text-decoration:underline;">Privacy Policy</a> </p> <div style="display:flex; gap:10px; flex-wrap:wrap;"> <button id="gdpr-accept" style="background:#4CAF50; color:#fff; border:none; padding:12px 24px; cursor:pointer; border-radius:4px; font-size:14px; font-weight:bold;">Accept</button> <button id="gdpr-decline" style="background:#666; color:#fff; border:none; padding:12px 24px; cursor:pointer; border-radius:4px; font-size:14px;">Decline</button> </div> </div> </div> <div id="gdpr-settings-link" style="display:none; position:fixed; bottom:20px; left:20px; z-index:999998;"> <button id="gdpr-open-settings" style="background:#1a1a2e; color:#fff; border:none; padding:10px 16px; cursor:pointer; border-radius:20px; font-size:12px; box-shadow:0 2px 8px rgba(0,0,0,0.3);"> Cookie Settings </button> </div> <?php }, 5); // 2. JavaScript(国判定 + 同意管理) add_action('wp_footer', function() { ?> <script> (function() { const euCountries = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE','IS','LI','NO','GB','CH']; const banner = document.getElementById('gdpr-banner'); const acceptBtn = document.getElementById('gdpr-accept'); const declineBtn = document.getElementById('gdpr-decline'); const settingsLink = document.getElementById('gdpr-settings-link'); const openSettingsBtn = document.getElementById('gdpr-open-settings'); let isEuUser = false; function updateConsent(granted) { const status = granted ? 'granted' : 'denied'; if (typeof gtag === 'function') { gtag('consent', 'update', { 'analytics_storage': status, 'ad_storage': status, 'ad_user_data': status, 'ad_personalization': status }); } if (typeof wp_set_consent === 'function') { wp_set_consent('statistics', granted ? 'allow' : 'deny'); wp_set_consent('marketing', granted ? 'allow' : 'deny'); } } function saveConsent(granted) { const value = granted ? 'granted' : 'denied'; document.cookie = "gdpr_consent=" + value + ";path=/;max-age=31536000;SameSite=Lax"; updateConsent(granted); banner.style.display = 'none'; if (isEuUser) { settingsLink.style.display = 'block'; } } function getConsent() { const match = document.cookie.match(/gdpr_consent=(granted|denied)/); return match ? match[1] : null; } function showBanner() { banner.style.display = 'block'; settingsLink.style.display = 'none'; } acceptBtn.addEventListener('click', () => saveConsent(true)); declineBtn.addEventListener('click', () => saveConsent(false)); openSettingsBtn.addEventListener('click', showBanner); const hasNonEu = document.cookie.includes('geo_region=non-eu'); const hasEu = document.cookie.includes('geo_region=eu'); const existingConsent = getConsent(); if (hasNonEu) { if (!existingConsent) { saveConsent(true); } else { updateConsent(existingConsent === 'granted'); } return; } if (hasEu) { isEuUser = true; if (existingConsent) { updateConsent(existingConsent === 'granted'); settingsLink.style.display = 'block'; } else { showBanner(); } return; } function tryGeoIP() { fetch('https://ipwho.is/') .then(r => r.json()) .then(data => { handleCountry(data.country_code); }) .catch(() => { fetch('https://api.country.is/') .then(r => r.json()) .then(data => { handleCountry(data.country); }) .catch(() => { isEuUser = true; showBanner(); }); }); } function handleCountry(country) { if (!country) { isEuUser = true; showBanner(); return; } country = country.trim().toUpperCase(); if (euCountries.includes(country)) { document.cookie = "geo_region=eu;path=/;max-age=86400;SameSite=Lax"; isEuUser = true; showBanner(); } else { document.cookie = "geo_region=non-eu;path=/;max-age=86400;SameSite=Lax"; saveConsent(true); } } tryGeoIP(); })(); </script> <?php }, 999); |
コードの仕組み
GeoIP判定でEU圏かどうかを判定し、EU圏のユーザーにだけバナーを表示。同意状態はCookieとGoogle Consent Modeの両方に反映されます。
バナーのHTML構造
CSSはインラインスタイルで記述しています。Cocoonのスタイルシートとの競合を避けるためです。レイアウトはFlexboxで、flex-wrap: wrapを指定しているのでスマートフォンでも自動的に折り返されます。初期状態はdisplay: noneで、JS側で国判定した後にEU圏なら表示します。
国判定の仕組み(フォールバック付き)
国判定には2つの無料GeoIP APIを使っています。メインAPI(ipwho.is)が失敗したら自動でフォールバックAPI(api.country.is)に切り替わり、両方失敗した場合は安全側に倒してバナーを表示します。
判定結果はgeo_region Cookieに保存し、有効期限は24時間。同じユーザーが何度ページを閲覧しても、APIへのリクエストは初回の1回だけで済みます。
EU圏の判定対象
|
1 |
const euCountries = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE','IS','LI','NO','GB','CH']; |
EU加盟27ヶ国に加え、EEA加盟3ヶ国(アイスランド、リヒテンシュタイン、ノルウェー)、イギリス(Brexit後もUK-GDPRあり)、スイス(GDPR準拠の独自法あり)を含めています。
同意状態の管理
2つのCookieで管理しています。
geo_region:”eu” または “non-eu”。有効期限24時間。gdpr_consent:”granted” または “denied”。有効期限1年。
EU圏外からのアクセスでは自動的にgdpr_consent=grantedを設定します。GDPR対象外なので、明示的な同意なしにトラッキングしても問題ありません。
Google Consent Mode対応
gtag('consent', 'update', ...)で同意状態をGoogleに伝えています。ユーザーが同意した場合のみデータ収集が有効になり、拒否した場合は停止します。
|
1 2 3 4 5 6 7 8 9 10 11 |
function updateConsent(granted) { const status = granted ? 'granted' : 'denied'; if (typeof gtag === 'function') { gtag('consent', 'update', { 'analytics_storage': status, 'ad_storage': status, 'ad_user_data': status, 'ad_personalization': status }); } } |
Site KitのConsent Mode機能を使う場合は、WP Consent APIプラグインとの連携も必要です。コード内のwp_set_consent関数がその橋渡しをしています。
設定変更機能
GDPRではユーザーがいつでも同意を撤回できることが求められています。同意/拒否を選択した後も「Cookie Settings」ボタン(EU圏ユーザーにのみ表示)からバナーを再表示し、設定を変更できるようにしています。
Site Kitの設定
Site Kit側でConsent Modeを有効化し、WP Consent APIプラグインをインストールすれば連携完了です。
- WordPress管理画面 →「Site Kit」→「Settings」→「Admin Settings」
- 「Consent Mode」を有効化
- 「WP Consent API」プラグインをインストール・有効化
これで、自作バナーでの同意状態がSite Kit経由でGoogle Analyticsに伝わります。
動作確認の手順
日本からとEU圏から(VPN使用)の2パターンで確認してください。テストのたびに必ずCookieをクリアすること。
日本からの確認
- ブラウザのCookieをクリア(特に
geo_regionとgdpr_consent) - サイトにアクセス
- バナーが表示されないことを確認
- DevTools(F12)のConsoleで
document.cookie.includes('gdpr_consent')を実行し、trueと表示されることを確認
EU圏からの確認(VPN使用)
実際にヨーロッパに行くわけにはいかないので、VPNでEU圏のサーバーに接続してテストします。ProtonVPN(無料プランあり)やWindscribe(月間10GBまで無料)が使えます。
- 必ずCookieをクリアしてからテスト開始
- VPNでEU圏(ドイツ、イタリアなど)に接続
- サイトにアクセスし、バナーが表示されることを確認
- 「Accept」→ バナーが消え、左下に「Cookie Settings」が表示される
- 「Cookie Settings」→ バナーが再表示される
- 「Decline」で拒否できることを確認
VPNで国を切り替えたのにバナーが出ない場合は、前回のgeo_region=non-euがCookieに残っているのが原因です。以下をConsoleで実行してからリロードしてください。
|
1 2 3 |
document.cookie = "geo_region=;path=/;max-age=0"; document.cookie = "gdpr_consent=;path=/;max-age=0"; location.reload(); |
カスタマイズ例
バナーの色、文言、対象国はコード内の該当箇所を書き換えるだけで変更できます。
バナーの色を変更:background:#1a1a2e(バナー背景)、background:#4CAF50(Acceptボタン)、background:#666(Declineボタン)をそれぞれ好きな色に変更。
プライバシーポリシーのURL:href="/privacy-policy-en/" を自サイトのURLに差し替え。
対象国の変更:イギリスを外したい場合は配列から'GB'を削除。他の国を追加したい場合は国コードを追加。
この方法のデメリット
外部APIへの依存、レート制限、メンテナンスの手間——この3つは自作のトレードオフです。
外部APIへの依存:国判定にipwho.isとapi.country.isを使っています。サービス停止や仕様変更があればコード修正が必要。ただしフォールバック構成にしているので、1つがダウンしても別のAPIで継続でき、全部失敗しても安全側(バナー表示)に倒れます。
APIのレート制限:判定結果はCookieにキャッシュされるので実際のAPI呼び出しはユニークユーザー数に近い値になりますが、高トラフィックサイトでは制限に達する可能性があります。
メンテナンスの責任:GDPRの要件変更やバグ修正は自分で対応する必要があります。プラグインなら開発者がアップデートを提供してくれますが、自作にはその保証がありません。
まとめ
ComplianzとCocoonの競合は設定では解決できませんでした。自作GDPRバナーで、メニューの問題もGDPR対応も同時に解決できます。
Complianzのホワイトリスト設定、Script Center(有料版限定)、無料版の全設定調整を試しましたが効果なし。CookieYes、GDPR Cookie Compliance、Cookiebotといった代替プラグインもPV制限や有料アドオンの壁がありました。
自作バナーなら、Cocoonとの競合ゼロ、PV制限なし、Consent Mode対応済み。コードはこの記事からコピペで使えます。デメリットは外部API依存とメンテナンスの手間ですが、プラグインの競合に何時間も悩み続けるよりは、自作して仕組みを完全に把握している方が健全です。
参考リンク
本記事で参照したドキュメントと、使用しているAPIの公式サイトです。












コメント