2026年7月14日

2026年7月14日

WordPressのPWAインストールプロンプトをカスタマイズする方法(beforeinstallprompt対応)

PWA化したWordPressサイトで「アプリとしてインストール」を案内したいなら、ブラウザ任せの表示を止めて、beforeinstallprompt イベントを保存し、読者が興味を持ったタイミングで自前のボタンから呼び出すのが基本の形です。この記事では、インストール可能になる条件の確認、子テーマでのボタンとスクリプトの組み込み、表示タイミングと「閉じた人に何度も出さない」制御、iPhone・iPadなど beforeinstallprompt が無い環境への案内までを順に解説します。

先に知っておきたい前提

インストールプロンプトのカスタマイズは、ブラウザごとに仕組みが違います。

  • Chrome・Edge・Samsung InternetなどChromium系: サイトがインストール条件を満たすと beforeinstallprompt イベントが発生します。これを使うと、ブラウザ標準の案内を抑えて、好きな場所・好きなタイミングでインストールダイアログを開けます。
  • Safari(iOS・iPadOS・macOS): beforeinstallprompt はありません。iOSでは共有メニューの「ホーム画面に追加」、macOSでは「Dockに追加」から読者自身が操作します。サイト側ができるのは、操作手順を案内することだけです。
  • Firefox: デスクトップ版のPWAインストール対応は限定的で、Android版はメニューからホーム画面に追加する形です。

つまり「カスタマイズ」と言っても、ダイアログそのものの見た目は変えられません。変えられるのはいつ・どこで・どんな文言で案内するかです。ブラウザの対応状況は更新が続いているため(2026年10月現在)、実装前にMDNやCan I useで beforeinstallprompt の対応表を確認してください。

インストール可能になる条件を満たす

beforeinstallprompt は、サイトがインストール条件を満たしていないと発生しません。ボタンが出ないときの原因の多くはここにあります。Chromeの主な条件は次のとおりです。

  • HTTPSで配信されている(localhost は例外として扱われます)
  • Webアプリマニフェストがあり、name または short_name、192pxと512pxのアイコン、start_url、display(standalone など)が入っている
  • 読者がページを一定時間閲覧し、クリックやタップなどの操作をしている
  • すでにインストール済みではない

以前のChromeは「fetch を処理するService Workerがあること」も条件にしていましたが、現在は条件から外れています。ただしオフライン対応などPWAらしい体験にはService Workerが欠かせないため、併せて用意しておくのが一般的です。条件の細部はChromeのバージョンで変わることがあるので、web.devやChrome for Developersの最新の説明も確認してください。

マニフェストは子テーマに manifest.webmanifest として置き、wp_head で読み込むのが手軽です。WordPressをサブディレクトリに置いている場合は、start_url と scope をそのパスに合わせます。

{
  "name": "ワードプレス博士",
  "short_name": "WP博士",
  "id": "/wordpress/",
  "start_url": "/wordpress/?source=pwa",
  "scope": "/wordpress/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#1e73be",
  "icons": [
    { "src": "/wordpress/wp-content/themes/my-child/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/wordpress/wp-content/themes/my-child/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
<?php
// 子テーマの functions.php
add_action( 'wp_head', function () {
	$manifest = get_stylesheet_directory_uri() . '/manifest.webmanifest';
	echo '<link rel="manifest" href="' . esc_url( $manifest ) . '">' . "\n";
	echo '<meta name="theme-color" content="#1e73be">' . "\n";
} );

Chromeのデベロッパーツールの「Application」パネルにある「Manifest」の項目を開くと、マニフェストの読み込み結果と、インストールできない理由が表示されます。最初にここでエラーが無いことを確かめてから、次の手順に進みます。

なお、PWA化そのものをプラグインで行っている場合(Super Progressive Web Appsなど)は、マニフェストをプラグインが出力しています。二重に出力しないよう、上のコードは使わずにプラグインの設定側で調整してください。プラグインによってはインストール案内の機能を持つものもあり、機能の範囲はバージョンで変わります。

案内ボタンを用意する

ボタンは最初は隠しておき、beforeinstallprompt が来たときだけ表示します。イベントが来ない環境で押しても何も起きないボタンを出さないためです。

<?php
// 子テーマの functions.php(続き)
add_action( 'wp_enqueue_scripts', function () {
	wp_enqueue_script(
		'my-install-prompt',
		get_stylesheet_directory_uri() . '/js/install-prompt.js',
		array(),
		filemtime( get_stylesheet_directory() . '/js/install-prompt.js' ),
		array( 'strategy' => 'defer', 'in_footer' => true )
	);
} );

add_action( 'wp_footer', function () {
	?>
	<div id="pwa-install" class="pwa-install" hidden>
		<p class="pwa-install__text"><?php echo esc_html__( 'ホーム画面に追加すると、次回からすぐに開けます。', 'my-child' ); ?></p>
		<button type="button" class="pwa-install__ok"><?php echo esc_html__( 'インストール', 'my-child' ); ?></button>
		<button type="button" class="pwa-install__close"><?php echo esc_html__( '今はしない', 'my-child' ); ?></button>
	</div>
	<?php
} );

wp_enqueue_script の5番目の引数に strategy を渡す書き方はWordPress 6.3以降で使えます。それより古い環境では true(フッターで読み込む)を渡してください。

案内は画面下に固定表示する形にすると、本文のレイアウトを押し下げず、CLS(レイアウトのずれ)を起こしません。アニメーションを付けるなら、prefers-reduced-motion: reduce の読者には動きを止めます。

.pwa-install {
	position: fixed; left: 1rem; right: 1rem; bottom: 1rem; z-index: 1000;
	display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
	padding: .75rem 1rem; background: #fff; border: 1px solid #ccc; border-radius: 8px;
	box-shadow: 0 2px 8px rgba(0,0,0,.15);
	animation: pwa-in .3s ease-out;
}
.pwa-install[hidden] { display: none; }
@keyframes pwa-in { from { transform: translateY(1rem); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .pwa-install { animation: none; } }

beforeinstallpromptを保存して後から呼び出す

スクリプトの要点は3つです。イベントで preventDefault() を呼んで標準の案内を抑えること、イベントを変数に保存すること、ボタンが押されたときに prompt() を呼ぶことです。

// 子テーマの js/install-prompt.js
(() => {
  const box = document.getElementById('pwa-install');
  if (!box) return;

  const KEY = 'pwaInstallDismissedAt';
  const WAIT_DAYS = 14;
  let deferred = null;
  let armed = false; // スクロール監視を二重に付けない

  const isStandalone = () =>
    window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;

  const recentlyDismissed = () => {
    try {
      const t = Number(localStorage.getItem(KEY));
      return t && Date.now() - t < WAIT_DAYS * 86400000;
    } catch (e) {
      return false;
    }
  };

  window.addEventListener('beforeinstallprompt', (event) => {
    event.preventDefault();          // 標準の案内を出さない
    deferred = event;                // 後で使うために保存
    if (armed || isStandalone() || recentlyDismissed()) return;
    armed = true;
    // すぐには出さず、記事を読み進めた人にだけ出す
    const onScroll = () => {
      const ratio = window.scrollY / (document.body.scrollHeight - window.innerHeight);
      if (ratio > 0.5) {
        box.hidden = false;
        window.removeEventListener('scroll', onScroll);
      }
    };
    window.addEventListener('scroll', onScroll, { passive: true });
  });

  box.querySelector('.pwa-install__ok').addEventListener('click', async () => {
    if (!deferred) return;
    box.hidden = true;
    deferred.prompt();
    const { outcome } = await deferred.userChoice; // 'accepted' または 'dismissed'
    if (outcome === 'dismissed') {
      try { localStorage.setItem(KEY, String(Date.now())); } catch (e) {}
    }
    deferred = null; // 同じイベントの prompt() は1回しか呼べない
  });

  box.querySelector('.pwa-install__close').addEventListener('click', () => {
    box.hidden = true;
    try { localStorage.setItem(KEY, String(Date.now())); } catch (e) {}
  });

  window.addEventListener('appinstalled', () => {
    box.hidden = true;
    deferred = null;
  });
})();

つまずきやすい点を挙げておきます。

  • **prompt() はボタン操作の中で呼ぶ**: ページ読み込み直後などに自動で呼ぶことはできず、読者のクリックやタップに応じて呼ぶ必要があります。
  • **prompt() は1回きり**: 一度呼ぶと、同じイベントは再利用できません。断られた後に再度案内できるのは、ブラウザが次の beforeinstallprompt を発生させたときです。
  • 断られた記録を残す: ここでは localStorage に断った日時を保存し、14日間は案内を出さないようにしています。毎回出る案内は読者の離脱につながります。

表示タイミングの考え方

インストール案内は、読者がサイトを「また使いたい」と感じた後に出すほうが受け入れられやすくなります。例として次のようなきっかけが考えられます。

  • 記事を半分以上読み進めた(上のコードの方式)
  • 2回目以降の訪問である(訪問回数を localStorage に数える)
  • 「お気に入り」や「後で読む」など、継続利用を示す操作をした

反対に、トップページを開いた直後や、ログイン画面・購入手続きの途中で出すのは避けます。管理画面には出さない(wp_footer は管理画面では動かないので、上のコードはその点で問題ありません)、ログイン中の編集者には出さないといった条件も、必要に応じて is_user_logged_in() で分けてください。

iPhone・iPad向けには手順を案内する

iOSとiPadOSのSafariには beforeinstallprompt が無いため、上のボタンは表示されません。iPhoneの読者にもインストールを勧めたい場合は、「共有ボタンから『ホーム画面に追加』を選んでください」という短い案内を別に出します。

判定はユーザーエージェントの文字列に頼ることになり、iPadOSはデスクトップ版Safariと同じ表記になる場合もあるため、正確に見分けるのは難しいと考えてください。案内文は「Safariをお使いの場合は」という書き方にして、誤判定しても読者が混乱しないようにしておくのが無難です。すでにホーム画面から開いている場合は navigator.standalone が true になるので、そのときは案内を出しません。メニュー名はOSのバージョンで変わることがあるため、OSの大きな更新のたびに案内文を見直してください。

動作を確認する

  1. Chromeでサイトを開き、デベロッパーツールの「Application」パネルでマニフェストにエラーが無いことを確認します。
  2. ページを操作して記事を半分以上スクロールし、案内が表示されるか確かめます。条件を満たしてもイベントが来ない場合は、すでにインストール済みでないか(アドレスバーやアプリ一覧)を確認します。
  3. 「今はしない」を押した後に再読み込みし、案内が出ないことを確かめます。テストを繰り返すときは「Application」パネルの「Local storage」から該当の値を消します。
  4. 実機のAndroid端末でも確認します。デスクトップとモバイルでは標準の案内の出方が違うためです。

インストールの数を把握したい場合は、userChoice の結果や appinstalled をアクセス解析のイベントとして送ると、案内の文言やタイミングを比べられます。

まとめ

  • ダイアログの見た目は変えられず、カスタマイズできるのは案内のタイミング・場所・文言です。
  • beforeinstallprompt はChromium系だけのイベントで、インストール条件(HTTPS、マニフェスト、アイコンなど)を満たしたときに発生します。
  • イベントを preventDefault() して保存し、読者のボタン操作で prompt() を1回だけ呼びます。
  • 断った読者には一定期間出さない、読み進めた読者にだけ出すなど、押し付けない設計にします。
  • Safari向けには「ホーム画面に追加」の手順を案内し、標準の案内が無いことを前提に作ります。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲