# 読んでいる途中や入力中に勝手に開いてしまうポップアップ

Source: https://easeweb.dev/learn/timed-pop-ups
Topics: ダイアログとオーバーレイ > ポップアップや更新を後回しにできる (WCAG 2.2.4)
The fix: 特典がいつ出るかは、利用者が決められるようにします。方法は2つあります。タイマーをやめ、ページ内の「初回のご注文が10%オフ」ボタンから同じポップアップを開く方法と、タイマーは残したまま、ページの先頭にポップアップをオフにする設定を置き、その選択を覚えておく方法です。
Test with: keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/interruptions.html https://www.w3.org/WAI/WCAG22/Techniques/general/G75 https://www.w3.org/WAI/WCAG22/Techniques/general/G76 https://www.w3.org/WAI/WCAG22/Techniques/client-side-script/SCR14 https://www.w3.org/WAI/WCAG22/Techniques/failures/F41 https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

「Fernleaf Paper」は、グリーティングカードを販売し、代わりに投函もしてくれるお店です。初めて訪れた人のメールアドレスを集めたいと考え、丁寧にデザインしたポップアップを用意しています。暗くしたページの上に柔らかなカードが重なり、「初回のご注文が10%オフ」の見出し、メールアドレス欄が1つ、「登録する」ボタン、隅に×があります。ページを開いたとたんに出てこないよう、お店が使うマーケティングツールで、ページを開いてから10秒後に表示する設定にしています。

野の花のバースデーカードのページで、ある人がカードに書くメッセージを入力しています。「お誕生日おめでとう！すてきな」まで入力したところで10秒がたちます。ポップアップがページの上に開き、フォーカスがメールアドレス欄へ移るため、続けて入力した「一年に」はカードではなくメールアドレス欄に入ります。カードの説明を読んでいたスクリーンリーダーの利用者には、代わりにポップアップの見出しと入力欄が読み上げられます。注意を保つのが難しい人の目の前には、書きかけのメッセージではなく特典が広がっています。ポップアップは×とEscapeキーで閉じられますが、現れる前に後回しにしたり、出ないようにしたりする手段はページのどこにもありません。

## 確認できる問題

達成基準2.2.4「割り込み」（レベルAAA）は、緊急の場合を除いて、割り込みを利用者が延期または抑制できることを求めています。Understandingのページは、その意図を、作成者やサーバーからの更新を利用者がオフにできるようにすることと説明しています。例として挙げられているのは1つだけで、セッションが終わるまですべての更新と通知を延期するオプションを設定ページに用意するというものです。W3Cの達成方法G75では、設定を用意するか、更新の直前に知らせて利用者が抑制できるようにします。G76では、自動で更新する代わりに、利用者が更新を求められるようにします。

このポップアップには、そのどちらもありません。×があり、Escapeキーでも閉じますが、どちらも割り込みが起きた後の操作です。押せるようになったときには、文はもう途中で切られています。現れる前に断ったり、後にしてもらったりする手段はありません。

ブラウザーの側に、ポップアップを待たせる理由はありません。タイマーは、誰かが作業の途中かどうかに関係なく動きます。`showModal()` はモーダルダイアログとして正しく動きます。ダイアログをページの上に出し、背後のページを操作できなくして、フォーカスをダイアログの中（ここでは `autofocus` の付いた入力欄）へ移します。ポップアップの作りはどこも正しく、問題は開くタイミングです。

## 利用者への影響

10秒のタイマーは、訪れた瞬間にポップアップを出さないために選ばれています。ただ10秒あれば、たいていの人は何かを始めています。そのためポップアップは、目的の作業をしている最中の人の上に開き、ゆっくり読んだり入力したりする人ほど、その途中で出会うことになります。

## 自動チェックとAIの限界

マークアップに誤りはなく、ページを読み込んで数秒で離れるスキャンは、ポップアップが開く前に終わってしまいます。この診断では、利用者が操作する前にページが始めるタイマーを見張り、コールバックが `showModal()` か、`openPopup()` のような名前の関数を呼んでいるものを `ew:timed-popup-manual-check` として確認に回します。ポップアップをオフにする設定がすでにあるのか、緊急のものなのかは、人が見て確かめるしかないためです。マーケティングツールのポップアップは管理画面で設定され、診断が見分けられないコードで読み込まれることが多く、チャットのあいさつはチャット側で設定されるため、どちらも診断では見つかりません。管理画面を人が確かめる必要があります。AIのコーディングツールに「10秒後にニュースレターのポップアップを出して」と頼むと、修正前の例のような `setTimeout` を書きます。

## 修正前：10秒後に開く登録のポップアップ

```html
<main>
  <h1>Wildflower birthday card</h1>
  <form class="card-form">
    <label for="card-message">Message inside the card</label>
    <textarea id="card-message" name="message" rows="4"></textarea>
    <button>Add to basket</button>
  </form>
</main>
<dialog id="signup" aria-labelledby="signup-title">
  <button type="button" class="signup-close" aria-label="Close">×</button>
  <h2 id="signup-title">Get 10% off your first order</h2>
  <p>Sign up for news of new designs and sales.</p>
  <form method="dialog">
    <label for="signup-email">Email address</label>
    <input id="signup-email" name="email" type="email" autocomplete="email" autofocus required>
    <button>Sign up</button>
  </form>
</dialog>
```

```js
const signup = document.getElementById('signup');
signup.querySelector('.signup-close').addEventListener('click', () => signup.close());

// ページを開いて10秒後に登録のポップアップを開く。
setTimeout(() => signup.showModal(), 10000);
```

ポップアップそのものは正しく作られています。HTML標準のダイアログで、×は名前のあるボタンです。Escapeキーで閉じ、閉じるとフォーカスは元の位置に戻ります。問題は開くタイミングにあります。

- 利用者がそのとき何をしていても、ページを開いて10秒後に開きます。
- フォーカスがメールアドレス欄へ移るため、カードに書くつもりの文字がポップアップに入り、スクリーンリーダーはページの読み上げを止めてポップアップを読みます。
- 現れる前に後回しにしたり、出ないようにしたりする手段がページにありません。

## 修正後：特典を開くボタン

```html
<p class="offer-bar">
  New here?
  <button type="button" id="offer-open">Get 10% off your first order</button>
</p>
<main>
  <h1>Wildflower birthday card</h1>
  <form class="card-form">
    <label for="card-message">Message inside the card</label>
    <textarea id="card-message" name="message" rows="4"></textarea>
    <button>Add to basket</button>
  </form>
</main>
<dialog id="signup" aria-labelledby="signup-title">
  <button type="button" class="signup-close" aria-label="Close">×</button>
  <h2 id="signup-title">Get 10% off your first order</h2>
  <p>Sign up for news of new designs and sales.</p>
  <form method="dialog">
    <label for="signup-email">Email address</label>
    <input id="signup-email" name="email" type="email" autocomplete="email" autofocus required>
    <button>Sign up</button>
  </form>
</dialog>
```

```js
const signup = document.getElementById('signup');
signup.querySelector('.signup-close').addEventListener('click', () => signup.close());

// 登録のポップアップは、利用者が求めたときに開く。
document.getElementById('offer-open').addEventListener('click', () => signup.showModal());
```

1. タイマーを削除し、ポップアップが勝手に開かないようにします。
2. ページの先頭に、何を開くかを示すボタン「初回のご注文が10%オフ」を置きます。特典はページを開いた時点からすべての訪問者の目に入り、どのページでも同じ場所にあります。
3. そのボタンから同じポップアップを開きます。フォーカスがメールアドレス欄へ移るのは利用者がボタンを押したときだけで、閉じるとボタンに戻ります。

修正前の問題が解消し、2.2.4を満たします。求められるまで何も開かないので、後回しにするものがありません。ポップアップの見た目はそのままで、変わるのは、最初からページの先頭にある細いバーだけです。

## 修正後：ポップアップをオフにする設定

```html
<p class="offer-setting">
  <input type="checkbox" id="offer-popups" checked>
  <label for="offer-popups">Show offers in pop-ups</label>
</p>
<main>
  <h1>Wildflower birthday card</h1>
  <form class="card-form">
    <label for="card-message">Message inside the card</label>
    <textarea id="card-message" name="message" rows="4"></textarea>
    <button>Add to basket</button>
  </form>
</main>
<dialog id="signup" aria-labelledby="signup-title">
  <button type="button" class="signup-close" aria-label="Close">×</button>
  <h2 id="signup-title">Get 10% off your first order</h2>
  <p>Sign up for news of new designs and sales.</p>
  <form method="dialog">
    <label for="signup-email">Email address</label>
    <input id="signup-email" name="email" type="email" autocomplete="email" autofocus required>
    <button>Sign up</button>
  </form>
</dialog>
```

```js
const signup = document.getElementById('signup');
signup.querySelector('.signup-close').addEventListener('click', () => signup.close());

const offerPopups = document.getElementById('offer-popups');
offerPopups.checked = localStorage.getItem('offer-popups') !== 'off';
offerPopups.addEventListener('change', () => {
  localStorage.setItem('offer-popups', offerPopups.checked ? 'on' : 'off');
});

// ページを開いて10秒後に開く。ただし、ポップアップがオフなら開かない。
setTimeout(() => {
  if (offerPopups.checked) signup.showModal();
}, 10000);
```

1. ページの先頭、主要な内容の前に、チェックボックス「特典をポップアップで表示する」を置きます。ポップアップより先に、利用者がたどり着けます。
2. タイマーが動いたとき、チェックがまだ入っている場合だけポップアップを開きます。
3. 選択を保存し、ページを開くときに読み戻します。ポップアップをオフにした人は、次のページでも次の訪問でも出会いません。

修正前の問題が解消し、2.2.4の文言も満たします。2.2.4は、利用者が抑制できる割り込みなら認めているためです。ただし、W3Cの達成方法G75のテストは満たしません。G75は、ポップアップを利用者がオンにするまでオフにしておくか、開く直前に予告して止められるようにすることを求めています。ただし、オフにされるまでポップアップは出続けます。カードのページから見始めた人や、リンクからメッセージ欄へ直接来た人は、チェックボックスを見つける前に、作業の途中でポップアップに割り込まれます。

## どの方法を選ぶか

ボタンは達成方法G76のとおりに2.2.4を満たし、設定は達成基準の文言を満たしますが、G75のテストは満たしません。おすすめはボタンです。誰の作業も遮らないので、先に設定を探す必要がありません。設定だけでは足りません。設定をまだ見つけていない人には、誰にでも作業の途中でポップアップが開き続けるからです。ボタンにすると、ポップアップを目の前に出されなければ登録しなかった人の分だけ、お店の登録は減ります。どのページでも先頭にバーを置けば、誰の作業も止めずに、特典を目に入る場所に置いておけます。設定は、時間で開くポップアップをどうしても残す場合だけにし、ページの最初に置いて、選択を覚えておきます。

## 実装の判断

**誰も押していないのに開く、ほかのきっかけ。** マーケティングツールには、ページの途中までスクロールしたとき、サイトにしばらくいたとき、立ち去ろうとするようにポインターがアドレスバーへ向かったときに開く設定もあります。どれも求められないまま作業の途中に開くもので、直し方も同じです。ボタンから開くか、設定でオフにできるようにします。

**閉じたことを覚えておく。** マーケティングツールでは、一度閉じられたポップアップを何日か出さないように設定できます。2回目の割り込みを防げるので、やる価値はあります。ただ、1回目は変わらず現れ、その前に断る手段もないので、それだけでは修正前の問題は解消しません。

**自分からあいさつしてくるチャット。** チャットツールには、訪れてから決まった時間がたつとあいさつを送り、それと一緒にチャットのウィジェットを開く機能があります。Zendeskの既定のトリガー「Proactive Greeting」は、サイトが有効にするまでは動きませんが、新しい訪問者が来てから30秒後にメッセージを送ります。これも同じ問題です。あいさつはランチャーからパネルが開かれたときに出すか、同じ設定でオフにできるようにします。ランチャーとパネルの操作は[キーボードで開けず、閉じるボタンにも名前がないチャット](/ja/learn/chat-launchers)で、メッセージの読み上げは[メッセージを読み上げないチャットウィジェット](/ja/learn/chat-widgets)で扱います。

**更新の案内。** 新しいバージョンが出たWebアプリは、作業中の画面にダイアログを重ねるのではなく、「新しいバージョンがあります。再読み込み」のように、1行やボタンで控えめに知らせて待つことができます。再読み込みは、利用者が押したときに行います。

**緊急時は割り込んでよい。** WCAGの緊急事態は、健康、安全、財産を守るためにすぐ行動が必要な、突然の予期しない状況を指し、Understandingのページは、データの損失や接続の切断を警告するメッセージもそこに含めています。セッションが切れて注文が失われそうだという警告は割り込んでよく、むしろ割り込むべきです。詳しくは[警告も延長もないセッションタイムアウト](/ja/learn/time-limits)で説明しています。今夜で終わるセールは緊急事態ではありません。こうしたメッセージの読み上げ方と、定型の知らせをスクリーンリーダーの読み上げの後に回す理由は、[定型の通知でもVoiceOverの読み上げを遮るアラート](/ja/learn/urgent-alerts)で扱います。

**自分で再読み込みするページ。** `<meta http-equiv="refresh">` のあるページは再読み込みによって割り込み、W3Cの失敗例F41は、これを2.2.1と3.2.5に加えて2.2.4の失敗としています。[時間がたつと自動で再読み込みや転送をするページ](/ja/learn/meta-refresh)のように削除します。タイマーで書き換わる一覧は[読んでいる最中に自動更新で書き換わってしまう新着一覧](/ja/learn/auto-updating-feeds)で扱います。

**ポップアップそのもの。** このガイドで扱うのは、ポップアップが開くタイミングです。開いた後には、[キーボードで閉じられないポップアップ](/ja/learn/closing-pop-ups)のようにキーボードで閉じられることと、[背後にフォーカスが残るモーダル](/ja/learn/modal-focus-management)のようにフォーカスを中に留め、閉じたら戻すことが、別に必要です。

## 修正の確認

1. スクリプトから、ダイアログを開いたりオーバーレイを表示したりする `setTimeout` と `setInterval` のコールバック、scroll、`mouseout`、`visibilitychange` のハンドラーを探します。どれも、利用者がオフにできる設定を最初に確かめているか、そもそも存在しません。
2. マウスから手を離してページを開き、1分間そのままにします。ポップアップは開かないか、設定がある場合は、設定がオンの間だけ開きます。
3. ページを開き直し、入力欄にメッセージを1分間入力します。文字はすべてその欄に入ります。設定がある場合は、時間がたつ前にチェックを外してからページを再読み込みし、チェックが外れたままで、何も開かないことを確かめます。
4. スクリーンリーダーでページを先頭から1分間読みます。読み上げの位置がポップアップへ移ることはなく、ボタンや設定は何をするものかがはっきり分かります。
5. マーケティングツールとチャットツールの管理画面で、すべてのポップアップ、スライドイン、あいさつを確かめます。どれもクリックで開くか、設定でオフにできるようになっています。

OS、ブラウザー、支援技術のバージョン、ビルド、日付、各手順の実際の結果を記録してください。ポップアップや、それを表示するツール、そのキャンペーンを変えたら、確認をやり直してください。

## 制限

これは出発点となる実装例で、すべてのアプリケーションでの動作を保証するものではありません。扱うのは、タイマーで開く登録のポップアップ1つです。「登録する」がメールアドレスをどう扱うかは、サンプルでは省いています。マーケティングツールやチャットツールの設定やキャンペーンは、サイト側のデプロイなしに変わるため、変更のたびにポップアップを確かめ直す必要があります。ある割り込みが緊急かどうかはサービスについての判断で、コードでは決められません。訪問者が使う支援技術で、一連の操作を通して確認してください。

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
