knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

【PWA】ミニ情報バーを表示する

まず、PWAについての概要については後ほど記事にしてゆきたいと思います。

今回は、PWAの機能の中でもっともシンプルなミニ情報バーの表示をしたいと思います。

ミニ情報バーとは

PWAをブラウザで表示した時に画面下部に現れる、ホーム画面へのインストールを促すバナーのことです。

このバナーは正しい設定のもとPWA化されているWEBサイトであれば必ず表示されます。

下記はPWA対応しているサイトの実例としてよく挙げられるsuumo.jp(SP実機表示)のものです。赤く網かけした部分がそれにあたります。

 

f:id:ShinImae:20190217002024p:plain

ちなみに、2019年2月現在この機能があるのは、 Androidの対応ブラウザのみです。iOSのブラウザは PWAには対応していても表示はされません。

medium.com

Web App Bannerを表示させるための最低限の条件は(PWAとして成立させるための条件と若干重複しますが)以下の通り。

  1. manifest.jsonを読み込んでいること
  2. サイトがSSL対応していること
  3. サイトにServiceWorkerが登録されていること

エンゲージメントについて言及されている記事もありますが、2019年2月現在仕様が変わったようで、初回訪問時には必ず表示されるようです。

www.suzukikenichi.com

manifest.json

manifest.jsonについては公式リファレンスを見ながら作成でも良いですが、ジェネレータも多数存在しますので、そちらを利用してもよいでしょう。

Web App Bannerを表示させるために必須となる項目がありますので、以下は必ずmanifest.jsonにて指定してください。

  • short_name は必須
  • nameは必須
  • iconsは必須(192×192のpng画像であること)
  • 読み込み先の start_url は必須
  • displayの値は「standalone」または「fullscreen」

認証がかかっているテスト環境などでは、このmanifest.jsonをHTMLが読み込むことができずエラーとなってしまうので、その場合は下記のいずれかにて読み込めるようになります。

  • .htaccessでmanifest.jsonのみ認証を解除する
  • manifest.jsonを読み込んでいるmeta要素に「crossorigin="use-credentials"」を指定

htaccessで認証を解除する場合

<files manifest.json>
Satisfy any
order allow,deny
allow from all
</files>

crossorigin属性を指定する場合

<link rel="manifest" href="manifest.json" crossorigin="use-credentials">

github.com

確認方法

ChromeDevToolにて、「Applicationタブ > Manifest」にて指定した値が正しく表示されているか確認できます。

f:id:ShinImae:20190217002014p:plain

「Add to home screen」というリンクをクリックすることで、PC上でもWeb App Bannerに相当するバナーを表示することができます。

ServiceWorkerの登録

ServiceWorkerを登録するためのJSと、ServiceWorker本体となるJSを用意します。

ファイル名はもちろん任意ですが、ここでは前者をregisterServiceWorker.js、後者をserviceworker.jsとします。

HTML側で読み込みが必要なのは前者のみです(ただし後者も記述のない空のファイルですが用意はしなければいけません)。

Web App Bannerの表示にはまずServiceWorkerの登録のみで大丈夫です。原則は専用となるAPIの記述は必要ありません。

if ('serviceWorker' in navigator) {
	// ServiceWorkerを登録
	navigator.serviceWorker.register('serviceworker.js', {
		scope: './',
	}).then(function(registration) {
		// 登録成功時
		console.log('登録成功です');
	}).catch(function(error) {
		// 登録失敗時
		console.log('登録できませんでした');
		console.log(error)
	});
}

ChromeDevToolにて、「Applicationタブ > Service Workers」の内容をみて、正しく登録されているか確認しましょう。

f:id:ShinImae:20190217002020p:plain

基本的にはこれだけで表示されることになっているのですが、実際に確認するとエラーが表示されてしまいインストールはおろか表示もできませんでした。

そのため、serviceworker.jsに以下を記述します。

self.addEventListener('fetch', function(event) {});

詳細はこちらを参照ください。

pawafuru.com

基本的には以上でWebAppBannerを表示することができるようになります。

ミニ情報バー対応状況の判定

Android版PWAは対応していますが、2019年2月現在、iOS版PWAは対応していません。

Google公式によると、対応ブラウザにはWeb App Bannerが表示される時に発行される「beforeinstallprompt」イベントと、バナーを表示する「installPromptEvent.prompt」メソッドがAPIとして用意されているようです。

developer.mozilla.org

そのAPIの有無を利用することで、Web App Bannerへの対応状況を判定することができます。

しばしばタッチデバイスを判定する方法と同様に、以下の条件で判定します。

"onbeforeinstallprompt" in window

対応している場合は「true」、対応していない場合は「false」が返ってきます。

こちらをもとに、ServiceWorkerの対応とWeb App Bannerの対応を組み合わせることができます。

if ('serviceWorker' in navigator) {
	// ServiceWorkerを登録
	navigator.serviceWorker.register('serviceworker.js', {
		scope: './',
	}).then(function(registration) {
		// 登録成功時
		console.log('ServiceWorker登録成功です');

		if ('onbeforeinstallprompt' in window) {
			// Web App Banner対応
			console.log('Web App Banner に対応しています');
		} else {
			// Web App Banner未対応
			console.log('Web App Banner 未対応');
		}
	}).catch(function(error) {
		// 登録失敗時
		console.log('ServiceWorker登録失敗です');
		//console.log(error);
	});
} else {
	console.log('ServiceWorker 未対応です')l
}

jQueryオブジェクトかどうかの判定方法

instanceof演算子を利用します。 変数objにはjQueryオブジェクトもしくはVanillaなDOMオブジェクトが入っています。

obj instanceof jQuery

jQueryオブジェクトであれば戻り値はtrueになります。「instanceof XXX」の「XXX」部は文脈によってjQueryそのものが代入されている変数に変更してください。

ページ離脱防止機能を実装する

beforeunloadイベントを利用して実装

先日、とあるCMSの管理画面にて、記事入力途中・画像入力途中でのページ離脱防止機能の実装を行いました。

要件としては、Google Chromeのみで、離脱時にアラートを表示するというものでした。

ページ遷移前に発火するbeforeunloadというイベントを利用した実装を行なったのですが、僕自身このイベントに真正面から向かい合って実装に取り組んだことがなかったので、ところどころ小さな落とし穴があったため、その備忘録としてこの記事を書かせていただきます。

ちなみに、beforeunloadイベントの詳細はこちらを参照ください。

developer.mozilla.org

テストは必ず入力フィールドのあるページで

とても基本的な落とし穴ですがテストは必ず入力フィールドのあるページで行なってください。

Google Chrome公式でもRequire user gesture for beforeunload dialogsと記載されています。

文字入力や画面になんらかのアクションがないと、離脱防止は動きません。開いて何もせずに「戻る」を押してしまうと、何も起きずに離脱してしまいます。

https://donow.jp/skillup/?p=3271

www.chromestatus.com

僕はこれに気づかず、入力フィールドのないページでなんどもブラウザバックボタンを押しては、何も起こらないことに首をひねってしまいました。

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に渡している文字列)は設定できず、ブラウザデフォルトのメッセージしか表示ができない仕様になっているそうです。

この件についてはこちらの記事をご参照ください。

qiita.com

フォームの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コマンドの使い方は以下を参照ください。

itpro.nikkeibp.co.jp

今回のような使い方のほかに、特定のファイルを検索し、そのファイルに対してリネームや削除を行うなどといったケースでも利用されます。

また、上記の例ではシンプルに空のファイルを作成するのみですが、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";

この属性についての詳細は下記をご参照ください。

reference.hyper-text.org

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.

より詳しく知りたい方や、他のブレンドモードのデモをご覧になりたい方は、以下をご参照ください。

www.webcreatorbox.com

記述も簡潔で、実装の垣根がぐんと下がっていますが、残念ながら冒頭で申し上げた通り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は未対応です

developer.mozilla.org

今回実例に用いたスクリーン合成もその一つですので、上記のデモをIE/Edgeでご覧いただくと合成できずにグラデーションで塗りつぶされてしまいます。

また振り出しに戻ってしまいました。

ビットマップ演算

IEも含めた全ての環境で同様の表示を実現するには、globalCompositOperationが内部的に行なっているビットマップ演算を行うしかありません。

ピクセルごとの色の情報を取得し、それを操作して新しくイメージデータを生成します。 まさしくPhotoshopの内部で行われていることをそのまま実装する形になります。

ややとっつきにくさがあるかもしれませんが、いくつかのポピュラーな合成アルゴリズムについて下記で解説されています。

www.cg-ya.net

合成と聞くととても複雑なことをしているかのようですが、実際のところは二つの色情報を四則演算しているに過ぎず、その実態はとてもシンプルです。 乗算や加算をはじめ(列挙されていませんが)グレースケールやネガポジ反転などは、今回取り上げているスクリーン合成に比べると非常に初歩的なアルゴリズムとなっています。

話を戻して、スクリーン合成のアルゴリズムについても上記で解説がされているため、それを参考に合成した結果が以下になります。

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環境での表示

f:id:ShinImae:20170730224920p:plain

▼本来の表示

f:id:ShinImae:20170730225030p:plain

遠目にはわかりにくいのですが、太字の部分においてWindowsの環境では若干文字のベースラインがずれてしまいガタガタとした表示になっています。

Windowsではフォントによっては滑らかに表示できないという癖があるようです。

そんな時は(本来CSSアニメーションを滑らかにするハックですが)以下のスタイルを指定することで解決します。

transform: rotate(0.1deg)

若干ボケてしまったり、フォントが太って見える場合もありますが、少なくともガタガタとした表示だけは回避することができます。

必要があればユーザーエージェントをもとにWindows環境にのみ上記のスタイルを設定するとベターです。