【PWA】シリーズPWA (1) はじめ一歩
このシリーズPWAは個人的な調査メモをもとにしたものです。
巷の情報が散在して体系的にまとまった書籍もない中、PWAについて全く知識がない状態から実装レベルまでなんとか知識を引き上げようともがいた痕跡です。
なので記事の引用や、実装するためのソースも多く含まれますがご容赦ください。
PWAとは何か?どんなもの?
PWAは「Progressive Web Apps」の略称で、モバイル向けWebサイトをGooglePlayストアなどで見かけるスマートフォン向けアプリのように使える仕組みのことです。PWAはそれ自体が何か特殊な一つの技術、というわけではありません。 レスポンシブデザイン、HTTPS化など、Googleが定める要素を備えたWebサイトであり、オフラインやプッシュ通知に対応するためのブラウザAPI(Service Workerなど)を利用しているWebサイトをPWAと呼びます。
定義がつかみにくかったので、改めて再定義すると、PWAはアプリではありません。 2019年時点では 特定の条件を満たした(挙動がアプリっぽい)webサイトのことを指します。もう少し厳密にいうと、裏側でService Workerというブラウザとは独立した環境によって制御されるwebサイトです。Service Workerは、ブラウザとインターネットの間に存在して動くプロキシのようなイメージが近いかもしれません。なんのこっちゃですが、とりあえずブラウザの裏側にService Workerというものがいること、そしてServiceWorkerがブラウザを制御しているのだということ覚えておいてください。Service Workerはブラウザから独立しているのでオフラインでも動きます。なんならサーバーがダウンしていても動きます。PWA化されていないページを閲覧していても動いています。そういった性質を利用してwebだけどプッシュ通知を受け取ったり、オフラインでも閲覧できたり、高速化を実現できたりします。(ちなみにServiceWorkerそのものについて説明をするとそれだけで本が書けてしまうと思うのでここでは割愛します。興味のあるかたはぜひMDNなどのリファレンスを合わせて参照いただけるとより理解が深まると思います)
ただし今後はアプリでもwebでもないものに進化してゆく予定のものです。
そのため、設計時や開発時に通常のweb制作とは異なる方法論や考え方が必要になる場面がありますので、ご注意を。
PWAを実装することでプッシュ通知やホーム画面へのアイコン追加など、アプリの特徴的な機能をWebサイトに持たせる事ができます。これにより、UX向上やユーザーエンゲージメントの改善にもつながるとして注目されています。
モバイル端末のホーム画面にアイコンを設置できるため、ユーザーはアイコンをタッチするだけでWebサイトを閲覧することが出来ます。 似た機能に「ウェブページのショートカットアイコンをモバイル端末のホーム画面に追加する」という既存機能があげられますが、PWAは単にショートカットをホーム画面に設置した場合と異なり、後に紹介するプッシュ通知やキャッシュの利用などの機能が備わっています。また見かけ上でも、PWAのアイコンから起動したWebサイトはURLバーもなくフルスクリーンで表示出来ますし、起動時のスプラッシュスクリーンも設定できます。
検索結果やURLから、通常のサイトと同じようにアクセスすることができる アプリ的な機能が取り上げられがちですが、もともとはWebサイトなので、通常のWebサイトと同じようにアクセスすることが出来ます。知人とページをシェアするときはアプリとは違いURLを送ればページを共有出来ますし、検索エンジンからのPWA対応のサイトを見つけることも出来ます。
ただし注意しなければいけないのは、キャッシュやプッシュ通知はPWAの機能でもなく、”ServiceWorkerができること” です。
ServiceWorkerにこの機能が備わっているわけではなく、ブラウザ( ≒ GoogleChrome)に備わっているCache APIやPush APIをServiceWorkerが操作できるに過ぎません。
導入事例
- TwitterLite https://lite.twitter.com/
- 日経電子版 https://r.nikkei.com/
- SUUMO https://smp.suumo.jp/
- trivago https://www.trivago.jp/
- Qiita https://qiita.com/
著名なものを挙げてみました。後のシリーズで詳述しますが、PWAになっているサイトを見分けるのは比較的容易です。
何を備えていればPWAと呼んでよいのか
何ができていれば最低限PWAと呼んで良いのか。
上記3ついずれも必須ですが、裏を返せば、通常のWebサイトでもこの条件さえ満たせればPWAとすることができます。
SSL対応はサーバーの設定によるものなので、基本的には開発者サイドはあまり気にしなくてよいです。
manifest.jsonはテンプレート通りに記述すればよく、各項目は理解が難しいものではないです。
ServiceWorkerを登録するJSと、ServiceWorkerにさせたいこと(キャッシュ制御やプッシュ通知など)を記述したJSが必要で、開発者にとってもっとも理解力と技術力が求められるもの。Web制作のそれとは異なる方法論を求められたり、新しい概念が多いので、慣れるまで時間がかかる(情報も点在していますし)。
また、Googleが推し進めているものなので、基本的にはAndroid優位です。ですが最近はiOSでも(まだできないことが多いですが)徐々に対応が進んでいます。
できることできないこと / 誤解されていること などなど
- PWA = 軽量 ではない。キャッシュストレージというものを利用して初めて速くなる。キャッシュストレージを利用しないと、パフォーマンスはブラウザでのそれと大差ありません。
- 認証のかかっている環境下ではmanifest.jsonを読めないことがあるので注意。認証を通すための属性を追加するか、認証の外れたディレクトリを作成するか。
- cookieは使えない(表示のたびにリセットされる)。WebStorageは大丈夫なよう
- 今のところ ChromeにログインしているGoogleアカウントに紐づくような挙動などはない模様
- iOSではできないことはこちらを参照。まだけっこうありそう。
https://medium.com/@takeshiamano/ios%E3%81%AE11-3%E3%81%8B%E3%82%89%E3%81%AEpwa%E5%AF%BE%E5%BF%9C%E3%81%A7%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8-313f638a172b - iOSはスプラッシュスクリーンのカスタマイズはできない。真っ白くなる。端末ごとのサイズの画像をmetaで登録するという方法もあるが、apple-touch-icon同様に認証のかかっている環境下では確認できない。
- iOSではブラウザからアプリへの値の引き継ぎはできません。
- iOSではミニ情報バーも表示されません。
- iOSのみPWA化させないことは可能。(JSでmanifest.jsonを読んでいるmetaタグを削除する)
- iOSはPWA化していない実例はいくつかある。PWA化しているものはスプラッシュスクリーンが白くなっても違和感のないデザインのものが多い
- start_urlにパラメータを付与すれば、アプリからの流入も計測できる。
- ブラウザからアプリに値を引き継ぐ際にWebStorageを利用することができる。しかしiOSはこれができない。効果測定の際に不便。
次回予告
次回は、PWAのもっとも基礎的な実装である、ミニ情報バーの表示について方法をまとめます。
ページ離脱防止機能を実装する
beforeunloadイベントを利用して実装
先日、とあるCMSの管理画面にて、記事入力途中・画像入力途中でのページ離脱防止機能の実装を行いました。
要件としては、Google Chromeのみで、離脱時にアラートを表示するというものでした。
ページ遷移前に発火するbeforeunloadというイベントを利用した実装を行なったのですが、僕自身このイベントに真正面から向かい合って実装に取り組んだことがなかったので、ところどころ小さな落とし穴があったため、その備忘録としてこの記事を書かせていただきます。
ちなみに、beforeunloadイベントの詳細はこちらを参照ください。
テストは必ず入力フィールドのあるページで
とても基本的な落とし穴ですがテストは必ず入力フィールドのあるページで行なってください。
Google Chrome公式でもRequire user gesture for beforeunload dialogs
と記載されています。
文字入力や画面になんらかのアクションがないと、離脱防止は動きません。開いて何もせずに「戻る」を押してしまうと、何も起きずに離脱してしまいます。
https://donow.jp/skillup/?p=3271
僕はこれに気づかず、入力フィールドのないページでなんどもブラウザバックボタンを押しては、何も起こらないことに首をひねってしまいました。
window.onbeforeunloadイベントプロパティに関数を登録
イベントハンドリングにはaddEventListenerメソッドやonメソッド(jQuery)などの方法がありますが、後述する理由によりwindow.onbeforeunloadイベントプロパティに関数を登録する方法を採用しています。
Chromeとそれ以外のブラウザとで若干方法が異なりますが、必ずreturnをするかe.returnValueを指定してください。
同時に指定することも可能ですが、とにかくreturnをしないと離脱防止は動きません。
window.onbeforeunload = function(e){ return "このページを離れますか?"; // Google Chrome以外 e.returnValue = "このページを離れますか?"; // Google Chrome }
また、Google Chromeのみ、カスタムメッセージ(つまりreturnValueやreturnに渡している文字列)は設定できず、ブラウザデフォルトのメッセージしか表示ができない仕様になっているそうです。
この件についてはこちらの記事をご参照ください。
フォームのsubmit時は無効にする
beforeunloadイベントはすべてのページ遷移時に発火するので、当然ながらフォームのsubmit時も発火します。
ユーザビリティのため、フォームのsubmit時は無効にするのが好ましいでしょう。
方法としてはフォームのsubmit時にbeforeunloadイベントとそれに対応する関数との紐づけを削除するのですが、removeEventListenerメソッドでもoffメソッド(jQuery)でもその紐づけを切ることができませんでした。
そこで、window.onbeforeunloadプロパティにnullを代入することで対応しました。
$('form').on('submit', function(e){ e.preventDefault(); window.onbeforeunload = null; // 関数を削除 var $this = $(this); var isPassed = confirm('記事を登録しますか?'); if (isPassed) $this.submit(); });
記事の登録時には確認ダイアログを表示してほしいとの要件であったため、このような実装になっています。
これらをまとめると、以下のようになります。
window.onbeforeunload = function(e){ return "このページを離れますか?"; // Google Chrome以外 e.returnValue = "このページを離れますか?"; // Google Chrome } $('form').on('submit', function(e){ e.preventDefault(); window.onbeforeunload = null; // 関数を削除 var $this = $(this); var isPassed = confirm('記事を登録しますか?'); if (isPassed) $this.submit(); });
ファイル名一覧から新規ファイルを作成
例えば、以下のようなテキストファイル(filelist.txt)があるとします。
aaa.html bbb.html ccc.html ddd.html eee.html
このリストをもとに新規ファイルを作成するには、毎回無題の新規ファイルを作成しそのたびにリネームをするという非効率な作業が必要になりますが、 xargsというコマンドを利用することで短時間で解決することができます。
cat filelist.txt | xargs -n 1 touch
xargsコマンドの使い方は以下を参照ください。
今回のような使い方のほかに、特定のファイルを検索し、そのファイルに対してリネームや削除を行うなどといったケースでも利用されます。
また、上記の例ではシンプルに空のファイルを作成するのみですが、cpコマンドのように引数を複数とる場合は、「-Jオプション」または「-iオプション」を使用して任意の文字列に置換をします。
下記は、新規作成ではなくテンプレートファイル(__temp__.html)をコピーしつつ、そのファイル名をファイルリストから命名するコマンドになります。
このとき、ファイルリストから取得したファイル名は「%」という文字列に置換されています。
cat filelist.txt | xargs -n 1 -J% cp __temp__.html %
Canvasでの認証エラー
toDataURL, getImageDataなど、Canvasで画像のピクセルデータを取得したり保持しているピクセルデータを出力する際、画像を格納しているディレクトリにBASIC認証がかかっていると認証エラーを起こします。
その際はCanvasで操作するimageオブジェクトにcrossorigin 属性を指定します。
(変数imageには画像のパスを与えられたimageオブジェクトが格納されているとします)
image.crssorigin = "use-credentials";
基本的には同一ドメイン間での画像の操作を前提としているため、このような一手間が必要になるそうです。
ちなみに単純に別ドメインの画像を操作する際は以下で可能になります。
image.crssorigin = "annonymus";
この属性についての詳細は下記をご参照ください。
Photoshopのような画像合成はフロントエンドでどこまで可能か
モダンブラウザに限定すれば実装は容易
Photoshopには描画モードという機能があり、例えば「乗算」「オーバーレイ」「スクリーン」などの描画モードを用いることでデザインの表現を大きく広げることが可能になります。
これまで描画モードを利用した表現はブラウザで再現することはできなかったため、たとえば描画モードの設定値に若干の修正を加えた時は毎回画像を切り出し直さなければならず、手間がかかっていました。
なぜかというとPhotoshopでの描画モードはいわゆる画像合成そのものであり、これまでのブラウザの技術はそれを実現するほどの水準に達していませんでした。
しかし現在ではブラウザの技術が大幅に進化し、画像合成がCSSまたはJavascriptで行うことができるようになりました。
mix-blend-mode
先に結論から申し上げますと、モダンブラウザに至ってはCSSのみで合成が可能です。
しかも非常に実装は容易です。
CSS3で登場したmix-bled-modeというプロパティを用いることで、Photoshopに用意されている描画モードの多くをカバーできます。
以下は、モノクロ画像と7色のグラデーションを、描画モード「スクリーン」で合成したデモです。
ソースをご覧いただければ、たった一行の指定で合成を実現できることが分かります。
See the Pen Composition Demo - 01 by Shin Imae (@shinimae) on CodePen.
より詳しく知りたい方や、他のブレンドモードのデモをご覧になりたい方は、以下をご参照ください。
記述も簡潔で、実装の垣根がぐんと下がっていますが、残念ながら冒頭で申し上げた通りIE/Edgeはこのプロパティに未対応です。
以下が各ブラウザに置けるmix-blend-modeの対応状況一覧ですが、Edgeですら対応はままならない状況です。
Can I use... Support tables for HTML5, CSS3, etc
ではIE/Edgeでは合成が無理なのかというと決してそんなことはなく、Canvasを利用するのが最も近道になります
globalCompositOperation
Canvas APIには二つの要素を重ねて描画した際に、その重なり方を制御できるglobalCompositOperationというプロパティが存在します。
こちらは非常に使い勝手がよく、要素を重ねる際にこのプロパティに重なり方を指定する任意の値を代入するだけで、合成をしてくれます。
以下はglobalCompositOperationを用いてスクリーン合成を行なったデモです。
Canvasのスケーリングこそ行なっていますが全体的には比較的シンプルなコードで実装できていることが分ります。
See the Pen Composition Demo - 02 by Shin Imae (@shinimae) on CodePen.
globalCompositOperationのリファレンスを以下にご紹介しますが、こちらを参照いただくとmix-blend-modeと同様の合成方法がAPI側ですでに用意されていることが分ります。
しかし残念なことに、このうちいくつかはIE/Edgeは未対応です。
今回実例に用いたスクリーン合成もその一つですので、上記のデモをIE/Edgeでご覧いただくと合成できずにグラデーションで塗りつぶされてしまいます。
また振り出しに戻ってしまいました。
ビットマップ演算
IEも含めた全ての環境で同様の表示を実現するには、globalCompositOperationが内部的に行なっているビットマップ演算を行うしかありません。
1ピクセルごとの色の情報を取得し、それを操作して新しくイメージデータを生成します。 まさしくPhotoshopの内部で行われていることをそのまま実装する形になります。
ややとっつきにくさがあるかもしれませんが、いくつかのポピュラーな合成アルゴリズムについて下記で解説されています。
合成と聞くととても複雑なことをしているかのようですが、実際のところは二つの色情報を四則演算しているに過ぎず、その実態はとてもシンプルです。 乗算や加算をはじめ(列挙されていませんが)グレースケールやネガポジ反転などは、今回取り上げているスクリーン合成に比べると非常に初歩的なアルゴリズムとなっています。
話を戻して、スクリーン合成のアルゴリズムについても上記で解説がされているため、それを参考に合成した結果が以下になります。
See the Pen Composition Demo - 03 by Shin Imae (@shinimae) on CodePen.
mix-blend-mode対応の判別
@supportsルールが使えるならばとても楽なのですが、例のごとくIE/Edgeが対応していないため、Javascriptにて対応状況を判別する必要があります。
if (typeof window.getComputedStyle(document.body).mixBlendMode !== 'undefined') { // supported. }else{ // not supported. }
出典はこちら。
おわりに
最終的にCanvasによるビットマップ演算という結論に達しましたが、とりわけモバイルブラウザを念頭に入れると、このCanvasを用いたレンダリングがとても高負荷なものであることは言うまでもありません。
前セクションで紹介したmix-blend-modeを利用した実装であればある程度のの負荷にも耐えうるものの、IEを対象に含めると先述したような(決しては容易とは言い難い)ビットマップ演算が必要不可欠なものになることは忘れてはいけません。
ページロード時に一度だけ合成を行う場合はこれで十二分に対応ができますが、例えばグラデーションの開始位置が徐々に変わるなど、合成具合が連続的に変化する場合(つまりms単位でCanvasで合成を繰り返さなければいけない場合)は、環境によってはCanvasの最適化を行なってもなお致命的にパフォーマンスが落ちることがあるので、注意が必要です。
フロントエンドでの画像合成をどこまで実現するか、その対応範囲を制作前に規定する作業はまだしばらく必要になりそうです
Windows環境でWebフォントが崩れる
ブラウザを問わずWindowsでのみ崩れる
Webフォントサービスを利用していた際、Windows環境にてブラウザを問わずフォントが崩れるという現象がありました。
▼Windows環境での表示
▼本来の表示
遠目にはわかりにくいのですが、太字の部分においてWindowsの環境では若干文字のベースラインがずれてしまいガタガタとした表示になっています。
Windowsではフォントによっては滑らかに表示できないという癖があるようです。
そんな時は(本来CSSアニメーションを滑らかにするハックですが)以下のスタイルを指定することで解決します。
transform: rotate(0.1deg)
若干ボケてしまったり、フォントが太って見える場合もありますが、少なくともガタガタとした表示だけは回避することができます。
必要があればユーザーエージェントをもとにWindows環境にのみ上記のスタイルを設定するとベターです。