[PWA/A2HS]Webアプリをインストールしてホーム画面へ追加できるようにする

- この記事は[MDCアドベントカレンダー](https://qiita.com/advent-calendar/2020/mdc)1日目の記事です

## はじめに

- PWAと言われたときにホームスクリーンへ追加できるWebアプリのことと思う方も多いと思います（PWAの定義についてはここではふれません）
- 最近だとライブラリやフレームワークに乗っかることで簡単にホームスクリーンへの追加を実現できるケースが多いですが今回は自前で実装する方法を整理します

## 機能の概要

- Webアプリをスマホアプリのように端末にインストールしホーム画面にアイコンを設置できます
- ホーム画面のアイコンからアプリを開くとスプラッシュ画面が表示され画面上部にURLバーがない状態で全画面で表示することができます

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/f6e8fac4-93a6-1105-54e4-dfac80a5f1a2.gif)


## 手順

- ホームスクリーンへ追加の仕組みを入れるためには以下の3つの要件があります
    - ServiceWorkerが有効化されていること
    - manifestファイルが読み込まれていてそこに設定が記載されていること
    - HTTPSで配信されていること

### ServiceWorkerが有効化されていること

- ホームスクリーンへ追加の機能を使うためにはServiceWorkerが有効化されている必要があります
- オフラインキャッシュなど他の機能を使わないのであれば細かい実装は不要で読み込むだけでOKです

```js:sw.js
self.addEventListener('fetch', function (event) {});
```

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Hello PWA</title>
  </head>
  <body>
    <h1>Hello PWA</h1>
    <script>
      window.addEventListener('load', () => {
        if ('serviceWorker' in navigator) {
          navigator.serviceWorker
            .register('sw.js')
            .then(registration => console.log('registered', registration))
            .catch(error => console.log('error', error));
        }
      });
    </script>
  </body>
</html>
```


### manifestファイルが読み込まれていてそこに設定が記載されていること

- manifest.webmanifestというファイルを作成してそこにホームスクリーンへ追加の設定をしていきます

```json:manifest.webmanifest
{
  "name": "Hello PWA",
  "short_name": "Hello",
  "start_url": ".",
  "display": "standalone",
  "background_color": "#4E526F",
  "theme_color": "#4E526F",
  "icons": [
    {
      "src": "images/icons-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "images/icons-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}
```

- nameとshort_nameはアプリ名を記載します
    - ホーム画面のアイコンの下に表示される名前などスペースが狭い場面ではshort_nameが使われます
- start_urlはホーム画面から起動したときにどのページを開くかを指定します
- displayは表示モードの指定でfullscreen、standalone、minimal-uiの3つから選びます
    - [詳細はこちら](https://web.dev/add-manifest/#display)
- background_colorはスプラッシュ画面の背景色をtheme_colorはツールバーの色を指定します
- iconsにはアプリのアイコンを設定します
    - スマホ用とデスクトップ用で192x192と512x512の画像を設定しておけばOKです
    - 192x192のアイコンは円形にしておかないと新し目のAndroidではアイコンが小さくなってしまいます
    - 小さくなってる例
    - ![スクリーンショット 2020-11-30 19.31.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/44789bc5-c043-1864-8ee8-fd0fa4ab29f2.png)
- 作成したmanifestファイルをhtml側で読み込めばOKです

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Hello PWA</title>
    <!-- ↓manifestファイルの読み込みを追加↓ -->
    <link rel="manifest" href="manifest.webmanifest" />
  </head>
  <body>
    <h1>Hello PWA</h1>
    <script>
      window.addEventListener('load', () => {
        if ('serviceWorker' in navigator) {
          navigator.serviceWorker
            .register('sw.js')
            .then(registration => console.log('registered', registration))
            .catch(error => console.log('error', error));
        }
      });
    </script>
  </body>
</html>
```

#### iPhone対応

- AndroidだけであればこれでいいのですがiPhone向けには専用の設定が必要です
- ホーム画面に追加されるアイコンとスプラッシュ画面の画像を設定します

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- ↓iPhone用にURLバーを表示しない設定を追加↓ -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <title>Hello PWA</title>
    <link rel="manifest" href="manifest.webmanifest" />
    <!-- ↓iPhone用にアイコン設定を追加↓ -->
    <link rel="apple-touch-icon" href="images/icons-192.png" />
    <!-- ↓iPhone用にスプラッシュ画面の設定を追加↓ -->
    <link
      rel="apple-touch-startup-image"
      href="images/launch-640x1136.png"
      media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-750x1334.png"
      media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-1080x1920.png"
      media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-1125x2436.png"
      media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-828x1792.png"
      media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-1242x2688.png"
      media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-1170x2532.png"
      media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
    />
    <link
      rel="apple-touch-startup-image"
      href="images/launch-1284x2778.png"
      media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
    />
  </head>
  <body>
    <h1>Hello PWA</h1>
    <script>
      window.addEventListener('load', () => {
        if ('serviceWorker' in navigator) {
          navigator.serviceWorker
            .register('sw.js')
            .then(registration => console.log('registered', registration))
            .catch(error => console.log('error', error));
        }
      });
    </script>
  </body>
</html>
```

- これでコーディングは完了です！

### HTTPSで配信されていること

- 最後は実装ではなく配信方法の話です
- 最近だと[Netlify](https://www.netlify.com/)や[Vercel](https://vercel.com)など無料でHTTPSでホスティングできるサービスがあるので趣味アプリであっても気軽に要件を満たせます

## 動作確認

- 今回紹介したコードは[こちらのサンプルリポジトリ](https://github.com/ozaki25/advent-calendar-2020-pwa-sample)に公開してあります
- このコードをGitHub Pagesで公開しています
    - https://ozaki25.github.io/advent-calendar-2020-pwa-sample/

### Android/Chrome

![Screenshot_20201130-194441.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/a761759c-55a7-92ed-d72a-5180dbf1df8e.png)![Screenshot_20201130-194500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/94503c4d-1000-9c93-b16a-c7370ebcd2e8.png)![Screenshot_20201130-194527.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/8ec1ce07-2037-6924-10a9-bfc0105b6fac.png)![Screenshot_20201130-194539.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/b07198b5-e774-66be-a911-b7f32b993b5d.png)![Screenshot_20201130-194548.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/84772cd7-015a-d6e1-9c04-bd52863da4d7.png)

### iPhone/Safari

![iOS の画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/c4260086-7bc1-c09f-0ea3-041165ab64ba.png)![iOS の画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/cf18fb55-4ac1-950c-f108-a83a0ddb567d.png)![iOS の画像3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/180b94af-6d55-66fb-dc57-2071aba2cf92.jpeg)![iOS の画像4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/778b0475-14f1-614f-0150-698aca9f180d.png)![iOS の画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/33f55ac6-585c-743b-23c8-f1dffc0963d8.png)



### Mac/Chrome

![スクリーンショット 2020-11-30 20.04.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/9765200e-1e80-d48e-4f1f-45f0d0bd9dc2.png)
![スクリーンショット 2020-11-30 20.05.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/9f007a24-b99d-7c90-1d99-c5e084bdc20d.png)
![スクリーンショット 2020-11-30 20.05.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/0dd81961-dcd8-eadb-69f7-a49ac0a654bf.png)
![スクリーンショット 2020-11-30 20.05.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/ac331ec6-4aef-4730-6458-eca9e75aa9df.png)



## まとめ

- ホームスクリーンへ追加の機能がほしいだけであれば自前でも簡単に実装できることがわかりました
- AndroidとiPhoneで仕様が共通化されてないのがちょっと面倒ですね
- iPhoneのスプラッシュ画面はデバイスサイズこどに用意しないといけないのでそこが一番大変でした

## 参考サイト

- https://web.dev/app-shortcuts/
- https://web.dev/install-criteria/
- https://web.dev/add-manifest/
- https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Add_to_home_screen
- https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium
- https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
- https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/