PWAの作り方をサクッと学ぶ - 「ホーム画面に追加」「キャッシュ操作」「プッシュ通知」の実装

PWAをテーマにしたコミュニティ「PWA Night」を運営する菅家大地さんが、既存のWebアプリをPWA化する簡単な実装方法を解説します。

PWAの作り方をサクッと学ぶ - 「ホーム画面に追加」「キャッシュ操作」「プッシュ通知」の実装

はじめまして、菅家(@kan_dai)といいます。普段は株式会社TAMという会社でフロントエンドをメインに、クライアントのWebサイト制作やWebサービスの開発をしています。PWA(Progressive Web Apps)をテーマにしたコミュニティ「PWA Night」の運営もしています。

さて、2018年ごろからPWAという言葉を聞く機会が多くなってきました。2019年現在、毎月コンスタントにPWAに関する仕事の相談を受けるようになっており、PWAへの関心の高まりを感じます。日本経済新聞やスマートフォン版Yahoo! Japanといった有名サービスでの導入事例も確実に増えつつあります。

現時点ではPWAの導入に際して、プッシュ通知やインストールを促すミニバナーの表示など、一部機能がiOSでは使えず、iPhoneのシェアが高い日本では“様子見”といった印象もあります。しかし、iOSの対応が進むことによって、PWAの事例がどんどん増えていく1のではないかと予想しており、今後、PWAとそれに付随する知識やスキルが重要になっていくはずです。

これらを知るためには、実際に作ってみるのが一番良いと思います。本稿では、PWAでサンプルアプリを開発するための簡単な実装方法と、今後の動向予測をお伝えしたいと思います。

サンプル開発を通して学ぶPWA

「何をもってPWAといえるのか」という定義は個人的には難しい点だと考えています。サンプルアプリを作る前に、Googleが提供しているPWAのチェックリストを見てみましょう。PWAに必要なベースラインの項目と、さらに一歩前進するための項目がたくさん並んでいます。

1Progressive Web App Checklist  |  Google Developers

長大なリストですが、全てやらないといけないわけではありません。Progressive(プログレッシブ)という言葉の通り、要素技術を段階的に導入できるのがPWAの良いところだと考えています。

既存のWebアプリをPWA化してみよう

上記の「段階的に導入できる」という特長を生かし、PWAを学ぶには、既存のWebアプリをPWA化するのが一番簡単な方法だと考えています。

今回は、筆者が以前作った「10秒間で何回タップできるか」で遊ぶゲーム「TAP10」を題材に、PWA化していく過程を紹介します。本記事では、既存のWebアプリを「インストール可能にする」「Service Workerを使ってキャッシュする」「Webプッシュの導入」までをターゲットにします。

2

コードの詳しい説明は割愛させていただきますが、このWebアプリは以下のような3ファイルで構成された簡単なものになっています。

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TAP10</title>
<link rel="stylesheet" type="text/css" href="app.css">
</head>
<body>

<section class="container">
  <h1 class="title">10秒で何回タップできるか</h1>
  <button id="js-tapBtn" class="btn-tap" disabled>
    <span id="js-count"></span><br>タップ!!
  </button>
  <div class="time">残り時間 <span id="js-time"></span> 秒</div>
  <button id="js-startBtn" class="btn">START</button>
</section>

<script src="app.js"></script>
</body>
</html>
app.css
@charset 'UTF-8';

html {
  font-family: sans-serif;
  touch-action: manipulation;
}
.container {
  padding: 15px;
  max-width: 400px;
  margin: 0 auto;
  text-align: center;
  font-size: 16px;
}
.title {
  font-size: 20px;
  margin: 10px 0 20px;
}
.btn-tap {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  color: #fff;
  background-color: #a0ce00;
  border: none;
  outline: none;
  font-size: 20px;
  margin-bottom: 30px;
  cursor: pointer;
}
.btn-tap:disabled {
  background-color: #ddd;
  color: #aaa;
}
.btn-tap span {
  font-size: 60px;
}
.btn {
  display: inline-block;
  margin: 30px 0 0;
  padding: 15px 60px;
  border: none;
  border-radius: 10px;
  background-color: #54bbc1;
  font-size: 18px;
  color: #fff;
  font-weight: bold;
  cursor: pointer;
}
.time span {
  display: inline-block;
  width: 50px;
  font-size: 20px;
  font-weight: bold;
}
app.js
// 変数定義
let isPlaying = false
let tapCount, time = 0
const tapBtn    = document.getElementById('js-tapBtn')
const startBtn  = document.getElementById('js-startBtn')
const countText = document.getElementById('js-count')
const timeText  = document.getElementById('js-time')

// ゲームの初期値設定
const setGame = () => {
  tapCount = 0
  time = 10000
  countText.innerText = tapCount
  timeText.innerHTML = time / 1000
}
setGame()

// タップした時にカウントを増やす
tapBtn.addEventListener('click', () => {
  if (!isPlaying) return false
  tapCount++
  countText.innerText = tapCount
})

// STARTボタンを押してゲームをスタートさせる
startBtn.addEventListener('click', () => {
  setGame()
  isPlaying = true
  tapBtn.disabled = false
  startBtn.style.display = 'none'

  const timer = setInterval( () => {
    time -= 10
    timeText.innerHTML = (time / 1000).toFixed(2)

    if (time === 0) {
      clearInterval(timer)
      isPlaying = false
      startBtn.style.display = 'inline-block'
      startBtn.innerText = 'もう一回'
    }
  }, 10)
})

PWA化の第一歩! インストール可能にする

スマートフォンの「ホーム画面に追加」(インストール)できるようになる機能をAdd to Home Screen、通称A2HSといいます。個人的にはこの機能こそが一番PWAらしい機能であり、PWA化への第一歩かと思っています。

「ホーム画面に追加」をすると以下のことができるようになります。

  • アドレスバーなどを表示しないネイティブアプリのようなUIを実現できる(以下画像左)
  • アプリの切り替え画面でアプリとして認識される(以下画像中央)
  • アプリのドロワーやアプリの管理に追加される(以下画像右)

3

実装方法はiOSとAndroidで違いがあるので、それぞれ説明します。ただし、iOSの場合、「ホーム画面に追加」ができるのはSafariだけなのでご注意ください。

iOSの場合はHTMLのhead内に以下のような記述を追記します。これだけです。

<!-- アドレスバー等のブラウザのUIを非表示 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- default(Safariと同じ) / black(黒) / black-translucent(ステータスバーをコンテンツに含める) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- ホーム画面に表示されるアプリ名 -->
<meta name="apple-mobile-web-app-title" content="TAP10">
<!-- ホーム画面に表示されるアプリアイコン -->
<link rel="apple-touch-icon" href="images/icons/icon-152x152.png">

Androidの場合は、ウェブアプリマニフェストと呼ばれるjsonファイルを読み込み、Service Workerを登録することで実装できます。Service Workerについてはこの後に詳しく説明します。

また、Service WorkerはHTTPSの環境でしか動かないため(例外的にlocalhostでは稼働します)、「HTTPS環境」でのサイト配信が必要になります。

HTMLファイルに、ウェブアプリマニフェストの読み込みとService Workerを登録する記述を記載します。

<!-- ウェブアプリマニフェストの読み込み -->
<link rel="manifest" href="manifest.json">
<!-- ServiceWorkerの登録 -->
<script>
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js')
      .then((reg) => {
        console.log('Service worker registered.', reg);
      });
}
</script>

manifest.jsonの中身は以下のような記述になっています。この中でアプリの名前やホーム画面用のアイコン、スプラッシュスクリーンの背景色などを設定しています。

{
  "name":"TAP10",
  "short_name":"TAP10",
  "icons": [{
      "src": "/icons/icon-192x192.png",
      "sizes":"192x192",
      "type": "image/png"
    }, {
      "src": "/icons/icon-512x512.png",
      "sizes":"512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html?utm_source=homescreen",
  "display": "standalone",
  "background_color": "#FFFFFF",
  "theme_color": "#FFFFFF"
}

アイコンにはいろいろなサイズの画像を設定できますが、少なくとも192x192pxのアイコンと512x512pxのアイコンを用意する必要があります。192pxのアイコンがあれば、最も大きなAndroid端末でもアイコンが適切に使用されます。

4Add to Home Screen  |  Web Fundamentals  |  Google Developers

5

この中の display の項目で standalone を設定することで、ホーム画面から起動した時に、ブラウザのURLバーなどが表示されないネイティブアプリのようなUIで起動できます。

詳しい設定や他の設定項目は、MDN(Mozilla Developer Network)に詳しく記載されています。実装時の参考にしていただければと思います。

6プログレッシブウェブアプリ | MDN

sw.js というファイルにService Workerの処理を書いていきますが、インストール可能にするだけであれば記述は空でも大丈夫です。

ただ、AndroidでMini-infobarと呼ばれるインストールを促すバナーを表示するためには fetch のイベントを登録する必要があります。最低限、以下のような記述があればOKです。

self.addEventListener('fetch', function(e) {
  // ここは空でもOK
})

ここまで実装すると、Mini-infobarが表示されてインストールできるようになります(メニューからもインストール可能です)。iOSの場合はこの機能が提供されていないため、ユーザー自身がメニューを表示して、ホーム画面に追加を行う必要があります。

7
左:Android / 右:iOS

しかし、突然バナーが表示されてもインストールしないというユーザーが大半でしょう。2019年8月開催の「PWA Night vol.7」で紹介していただいた「いこレポ」(アクトインディ運営)の例では、beforeInstallprompt というイベントを使うことで、インストールを促すポップアップを表示するというUIを実現していました。この実装についてはブログでも詳しく紹介されています。

workbox を導入してServiceWorkerによるキャッシュを実装した話 - アクトインディ開発者ブログ

また、将来的にGoogle Chromeでは、ホーム画面に追加可能なPWAの場合、オムニボックスと呼ばれるURLバーの部分にアイコンを表示する仕様に変わるという予測もあります。PC版のChromeでは既にそうなっています。(下図参照)

9
いこレポのポップアップ(参照:https://speakerdeck.com/hero/ikorehotefalseworkboxdao-ru-shi-li-at-pwa-night-number-7

Service Workerを使ったキャッシュで処理速度アップ

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く