コンテンツへスキップ

video/audioタグの映像を記録するRecording a media elementの紹介

現在のHTML5ではvideoやaudioタグを使って動画、音楽などを再生できるようになっています。しかし、これらのデータはストリーミングで流したり、getUserMediaを使って取得することしかできませんでした。

そこで登場したのが Recording a media elementです。これは video/audioタグの内容をレコーディングし、ファイルとしてダウンロードもできるAPIになります。

使い方

まずHTMLを記述します。videoタグを使います。 #startButton を押すとWebカメラの映像をストリーミングで流します。


<div class="left">
<button id="startButton">
開始
</button>
<h2>プレビュー</h2>
<video id="preview" width="160" height="120" autoplay muted></video>
</div>

view raw

index.html

hosted with ❤ by GitHub

さらに記録した内容を表示するDOMを用意します。


<div class="right">
<button id="stopButton">
停止
</button>
<h2>録画した内容</h2>
<video id="recording" width="160" height="120" controls></video>
<button id="downloadButton">
Download
</button>
</div>

view raw

index.html

hosted with ❤ by GitHub

JavaScriptの処理

まず必要なDOMを変数化します。最後の recordingTimeMS は録画を行うタイミングで、今回は5秒ごとにしています。細かければ動画が滑らかになりますが、CPU負荷も大きくなります。


let preview = document.getElementById("preview");
let recording = document.getElementById("recording");
let startButton = document.getElementById("startButton");
let stopButton = document.getElementById("stopButton");
let downloadButton = document.getElementById("downloadButton");
let logElement = document.getElementById("log");
let recordingTimeMS = 5000;

view raw

index.js

hosted with ❤ by GitHub

開始した際の処理

まず開始時の処理について解説します。最初はgetUserMediaを使ってWebカメラにアクセスします。そして、映像を #preview に表示します。そして、 preview.captureStream を使って記録を開始します。


// 開始ボタンを押した時の処理
startButton.addEventListener("click", function() {
// Webカメラにアクセス
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
// アクセスが許可された場合
preview.srcObject = stream; // プレビューにWebカメラの映像を表示
downloadButton.href = stream; // ダウンロードボタンにも配置
// 表示内容をキャプチャする
preview.captureStream = preview.captureStream || preview.mozCaptureStream;
// プレビューが表示開始するタイミングをPromiseで取得
return new Promise(resolve => preview.onplaying = resolve);
})
// 表示が開始したらレコーディングを開始
.then(() => startRecording(preview.captureStream(), recordingTimeMS))
// レコーディングが終了時の処理
.then (recordedChunks => {
let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
recording.src = URL.createObjectURL(recordedBlob);
downloadButton.href = recording.src;
downloadButton.download = "RecordedVideo.webm";
})
.catch((err) => console.log(err));
}, false);

view raw

index.js

hosted with ❤ by GitHub

レコーディング中は非同期処理になるので startRecording という関数の中で処理を行っています。 MediaRecorder のインスタンスにWebカメラの映像を指定し、 start メソッドを実行するとレコーディングが開始されます。レコーディング内容は ondataavailable メソッドで呼び出されるので、これを変数に残し続けます。

レコーディングが終わると onstop が呼ばれます。今回は lengthInMS で自動的に停止するようにしています。


function startRecording(stream, lengthInMS) {
let recorder = new MediaRecorder(stream);
let data = [];
recorder.ondataavailable = event => data.push(event.data);
recorder.start();
let stopped = new Promise((resolve, reject) => {
recorder.onstop = resolve;
recorder.onerror = event => reject(event.name);
});
let recorded = new Promise((resolve, reject) => {
setTimeout(() => {
recorder.state == "recording" && recorder.stop()
resolve();
}, lengthInMS);
});
return Promise.all([
stopped,
recorded
])
.then(() => data);
}

view raw

index.js

hosted with ❤ by GitHub

また、プレビューを停止する場合には次のように実装することもできます。


stopButton.addEventListener("click", function() {
preview.srcObject.getTracks().forEach(track => track.stop());
}, false);

view raw

index.

hosted with ❤ by GitHub

デモ

ここまでの実装をJSFiddleにてデモできます。なお、執筆時点ではChromeとMozillaでしか動作しません。


動画リソースはWebカメラに限らず使えるでしょう。そうした時に特定の場所だけキャプチャするといった用途でも使えそうです。動画を編集するという目的にしてはCPU負荷が大きそうですが、動画コンテンツをより扱いやすくしてくれそうです。

Recording a media element – Web APIs | MDN

PWAの基礎知識(その10)「Service Workerとのメッセージ送受信」

PWAは幾つかの技術要素で構成されますが、その基本とも言えるのがService Workerです。Service Workerがなければオフライン対応もアプリとしてのインストール、プッシュ通知もできません。なお、Service WorkerはWebブラウザとは別なプロセスのJavaScriptとして実行されます。

今回はそんなService Workerとフロントエンド側とでメッセージを送受信する方法を紹介します。

WebブラウザからService Workerを呼び出し

Service WorkerはWebサイトにアクセス時にインストールされます。次に訪問した際に更新されているかチェックするのですが、1日程度はキャッシュされるようです。そのためキャッシュする内容を逐次変えたいと言った時にハードコーディングされていると不便です。

そこでWebブラウザからメッセージを送る方法を覚えておきましょう。port2に送るのがコツです。


// メッセージ送信
const channel = new MessageChannel();
navigator.serviceWorker.controller
.postMessage('Hello, world', [channel.port2]);
});

view raw

index.js

hosted with ❤ by GitHub

Service Worker側では message イベントとして呼び出されます。


self.addEventListener('message', e => {
console.log(`I got a message from browser. ${e.data}`);
});

view raw

index.js

hosted with ❤ by GitHub

Service WorkerからWebブラウザを呼び出し

プッシュ通知を受け取った際など、Webブラウザ側にメッセージを送ることで表示を変えると言った効果が考えられます。

まず Service Worker側でメッセージを送りますが、Service Workerの場合はイベントが発生しないと実行できません。例えば push イベント時にメッセージを送る場合です。


self.addEventListener('push', e => {
e.ports[0].postMessage({
msg: "Hey I just got a push from you!",
data: e.data
});
});

view raw

index.js

hosted with ❤ by GitHub

そしてWebブラウザ側では port1 にメッセージが届きます。


const channel = new MessageChannel();
channel.port1.onmessage = e => {
return e.data.error ?
console.log(e.data.error) :
console.log(`I got a message from ws. ${JSON.stringify(e.data)}`);
};

view raw

index.js

hosted with ❤ by GitHub


WebブラウザからService Workerに対してメッセージを送る場合には port2 、逆の場合は port1 に来ると覚えておくと良いでしょう。Service Workerをよりダイナミックに活用したい時に覚えておくと良さそうです。

PWAの基礎知識(その9)「Push API/VAPID編」

Webブラウザにプッシュ通知を送れるPush APIは魅力的な機能ですが、各ブラウザによって実装が異なっていたり、Firebaseでプロジェクトを登録したりするのが手間でした。それを共通化し、さらにプロジェクト登録不要で使えるようにする仕組みがVAPIDになります。

今回はこのVAPIDを使ったPush APIの使い方です。

必要なもの

今回はVAPIDに対応したライブラリ、web-pushを使います。今回はNode.jsのライブラリです。zaru/webpushというRuby向けのライブラリもあります。

初期設定

まずサーバ側で秘密鍵と公開鍵を用意します。これは以下のコードで可能です。


const webpush = require("web-push");
JSON.stringify(webpush.generateVAPIDKeys());
// '{"publicKey":"BDC..tP4","privateKey":"Kne…Km8"}'

view raw

index.

hosted with ❤ by GitHub

この内容を application-server-keys.json などとしてサーバに保存しておきます。

JavaScriptの準備

Service Worker

VAPIDを使った場合の魅力として、ペイロードが送れるという点が挙げられます。メッセージ付きで送れるので、クライアントサイドで処理判別ができます。そのための処理をService Worker用のJavaScriptに入れておきます。


self.addEventListener('install', () => {
console.log('ServiceWorker がインストールされました');
});
self.addEventListener('push', ev => {
// payloadの取得
const {title, msg, icon} = ev.data.json();
self.registration.showNotification(title, {
icon: icon,
body: msg
});
self.registration.pushManager.getSubscription().then(subscription => {
console.log(subscription);
}, err => console.log(err));
});

view raw

index.js

hosted with ❤ by GitHub

クライアントサイド

次にクライアントサイドですが、まずService Workerを読み込みます。そして、その後プッシュ通知の登録状態を確認しています。


if (!('serviceWorker' in navigator)) {
// Service Worker非対応
}
navigator.serviceWorker.register('./serviceworker.js')
.then(() => {
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
// プッシュ通知がサポートされていない場合
return;
}
if (Notification.permission === 'denied') {
// プッシュ通知を拒否された場合
return;
}
if (!('PushManager' in window)) {
// PushManagerが存在しない場合
return;
}
return navigator.serviceWorker.ready;
})
.then(serviceWorkerRegistration => {
return serviceWorkerRegistration.pushManager.getSubscription();
})
.then(subscription => {
if (!subscription) {
// すでに購読中
} else {
// 未購読
console.log(subscription.toJSON());
}
})

view raw

index.js

hosted with ❤ by GitHub

購読を開始する際のイベントは次の通りです。大事なポイントとして、 applicationServerKey を追加しています。ここには公開鍵をunit8にしたものを適用します(後述)。


navigator.serviceWorker.ready
.then(serviceWorkerRegistration => {
return serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey
});
})
.then(subscription => {
// 購読開始
console.log(subscription.toJSON());
})

view raw

index.js

hosted with ❤ by GitHub

公開鍵の設定です。 vapidPublicKey には web-push で生成した公開鍵を指定します。


const vapidPublicKey = 'BOO…YEk';
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}

view raw

index.js

hosted with ❤ by GitHub

Web Pushの購読データ(subscription)をJSON出力すると、次のような内容になっています。このデータをすべて保存しておきます(サーバなど)。


{
"endpoint":"https://fcm.googleapis.com/fcm/send/e7K…Hag&quot;,
"expirationTime":null,
"keys":{
"p256dh":"BK7…e5U",
"auth":"DGt…D8g"
}
}

view raw

index.

hosted with ❤ by GitHub

Web Pushを送る

ではサーバからプッシュ通知を送ってみましょう。コードは以下のようになります。まず鍵の設定と受信者を指定します。


const webpush = require("web-push");
const keys = require("./application-server-keys.json");
webpush.setVapidDetails(
"mailto:admin@moongift.jp",
keys.publicKey,
keys.privateKey
);
const subscribers = [
{
"endpoint":"https://fcm.googleapis.com/fcm/send/e7K…Hag&quot;,
"expirationTime":null,
"keys":{
"p256dh":"BK7…e5U",
"auth":"DGt…D8g"
}
}
];

view raw

index.js

hosted with ❤ by GitHub

そしてプッシュ通知を送ります。 icon で表示するアイコンを指定しています。


const icon = `app.png`;
const params = {
title: "プッシュ通知です!",
msg: `これはサーバから送っています. 今は ${new Date().toLocaleString()} です。 メッセージとアイコンも送っています `,
icon: icon
};
Promise.all(subscribers.map(subscription => {
return webpush.sendNotification(subscription, JSON.stringify(params), {});
}))
.then(res => console.log(res))
.catch(err => console.log('ERROR', err));

view raw

index.js

hosted with ❤ by GitHub

そしてGoogle ChromeやFirefoxに対して通知が送れます。

Firefoxの場合です。


VAPIDを使った場合の利点としては、Firebaseでプロジェクトを作る手間がなく、Webブラウザの共通仕様の基で開発できるということです。なお、Safariはこの仕様に則っていない(そもそも独自仕様)ので、対応できません。

PWAの基礎知識(その8)「Push API/Chrome編」

PWAを構成する技術要素の一つにプッシュ通知があります。Push APIがそうで、該当URLを開いていなくても通知を受けられる仕組みです。

Push APIには幾つかの実装方法がありますが、今回はFCM/GCMを使ったGoogle Chrome向けの実装を紹介します。

Firebaseでプロジェクトを作成する

FCMを使うので、まず最初にFirebaseでプロジェクトを作成します。

作成した後、ウェブアプリにFirebaseを追加を選択します。

そして表示されるモーダルの中に messagingSenderId という項目があります。これを覚えておきます。

さらにプロジェクトの設定を選び、クラウドメッセージングを選びます。

その中に表示される、サーバーキーも覚えておきます。

必要な技術

Push APIを使う際に必要なのはService Workerになります。

manifest.jsonの作成

manifest.jsonを作成します。内容は例えば以下のようになります。 YOUR_SENDER_ID となっているところは先ほどの messagingSenderId の値になります。


{
"name": "Push Notification Demo",
"short_name": "Push Notification Demo",
"gcm_sender_id": "YOUR_SENDER_ID"
}

view raw

index.json

hosted with ❤ by GitHub

serviceworker.js の作成

Service Worker用のJavaScriptファイルを作成します。 serviceworker.js とします。今回は簡単な内容にしています。最低限必要なのは install と push イベントになります。


self.addEventListener('install', () => {
console.log('ServiceWorker がインストールされました');
});
self.addEventListener('push', ev => {
self.registration.showNotification('メッセージが届きました', {
body: '新しいメッセージです'
});
});

view raw

index.js

hosted with ❤ by GitHub

HTMLの記述

HTMLではmanifest.jsonを読み込みます。


<link rel="manifest" href="manifest.json">

view raw

index.html

hosted with ❤ by GitHub

JavaScriptの記述

初期化

まずPush通知が使えるかどうか、さらに購読中かどうかを判別します。以下のようなコードで順番に確認できます。


function onLoad() {
if (!('serviceWorker' in navigator)) {
// Service Worker非対応
}
navigator.serviceWorker.register('./serviceworker.js')
.then(() => {
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
// プッシュ通知がサポートされていない場合
return;
}
if (Notification.permission === 'denied') {
// プッシュ通知を拒否された場合
return;
}
if (!('PushManager' in window)) {
// PushManagerが存在しない場合
return;
}
return navigator.serviceWorker.ready;
})
.then(serviceWorkerRegistration => {
return serviceWorkerRegistration.pushManager.getSubscription();
})
.then(subscription => {
if (!subscription) {
// 未購読
} else {
// すでに購読中
}
})
}

view raw

index.js

hosted with ❤ by GitHub

購読処理

次に購読処理を書きます。これは何かのボタンを押したタイミングなどで実行すれば良いでしょう。


const SubscribePushNotification = () => {
navigator.serviceWorker.ready
.then(serviceWorkerRegistration => {
const subscribe = serviceWorkerRegistration
.pushManager
.subscribe({
userVisibleOnly: true
});
return subscribe;
})
.then(subscription => {
// 購読開始
})
}

view raw

index.js

hosted with ❤ by GitHub

プッシュ通知の送り方

上記のコードで取得できる subscription には以下のような内容が入っています。

  • endpoint
  • expirationTime
  • options
  • options.applicationServerKey
  • options.userVisibleOnly

今回のようなFCMから送信する場合に必要なのは endpoint になります。例えば以下のような値です。

そして、この対象に対してプッシュ通知を送るには以下のようなコマンドを実行します。 YOUR_API_KEY となっているのは Firebase で取得したサーバキーになります。


curl –header "Authorization: key=YOUR_API_KEY" \
–header Content-Type:"application/json" \
https://android.googleapis.com/gcm/send \
-d "{\"registration_ids\":[\"eF7Jh_81aPc:APA9…lOUg\"]}"

view raw

index.

hosted with ❤ by GitHub

registration_idsのところに配列でendpointとして取得したトークンを指定すればOKです。

受信する

プッシュ通知を受信すると通知バナーが表示されます。このメッセージは Service Worker側で固定していますが、サーバにfetchで問い合わせてデータ取得することも可能です。登録IDが必要な場合は、以下のコードをService Worker内で実行します。


self.registration.pushManager.getSubscription().then(subscription => {
console.log(subscription.endpoint); // 登録IDが返ってきます
}, err => console.log(err));

view raw

index.js

hosted with ❤ by GitHub

注意点

Push APIはタブを閉じていても(そのURLがアクティブでなくとも)表示されます。ただしGoogle Chromeを終了していたら届きませんので注意してください。Webブラウザを再開すると通知が表示されます。この方式の場合、表示させるメッセージをプッシュ通知側から送ることはできません。データが送れるのは VAPID という方式を使った場合になります。

この方法はGoogle Chrome独自の方式になります。手軽に実装できますが、FirefoxやEdgeなどでは使えませんのでご注意ください。


今回の方式の場合、サーバ側の用意は殆どいりません。取得した登録IDをサーバ側に入れておくだけで大丈夫です。保存する際に任意のユーザIDと結びつけておくことで、プッシュ通知で表示する内容をカスタマイズできるでしょう。

PWAの基礎知識(その7)「Web App Manifestを提供する上で知っておくと良いこと」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Web App Manifestを作る時に便利なテクニックを紹介します。

Web App Manifestとは?

Web App ManifestはWebアプリをスマートフォンアプリのようにインストールできる技術です。アプリストアを経由せずに配布できるのが魅力です。

インストール数を知る

どれくらいのPWAアプリが使われているか、それは皆さんが知りたいことだと思います。この時使えるのはGoogleアナリティクスです。例えば、manifest.jsonのstart_urlを次のように指定します。


"start_url": "/?utm_source=homescreen"

view raw

index.json

hosted with ❤ by GitHub

こうすることでGoogle Analyticsを使って、起動数を測定可能になります。

PWAの場合だけ表示を変える

Webブラウザから使った場合と、PWAとして立ち上げた場合で表示を変えたいこともあるでしょう。PWAではオフラインで使うことも想定してキャッシュされるので、サーバサイドでの表示出し分けはよくありません。そこでJavaScriptを使います。例えば manifest.json で次のように設定します。


"start_url": "index.html?launcher=true"

view raw

index.json

hosted with ❤ by GitHub

こうすることで、JavaScript側でlancherというパラメータの有無によって表示を変えられるようになります。不要なフッター情報などを削除しても良いでしょう。

オンライン/オフライン判定

これはPWAに限りませんが、 navigator.onLine がtrue(オンライン)/false(オフライン)によって判定できます。ムダなアクセスをなくしたり、デフォルト画像を表示したりするのに使えます。

インストールプロンプトの抑止

サイトを閲覧中にインストールしませんか、というバナーを出してもなかなかOKされないでしょう。インストールしたくなるような文脈が必要なはずです。そこで、インストールプロンプトを抑止し、任意のタイミングで出せるようにします。

まず抑止は beforeinstallprompt イベントで行います。


var deferredPrompt;
window.addEventListener('beforeinstallprompt', function(e) {
e.preventDefault();
// ここでプロンプトを変数に入れておく
deferredPrompt = e;
return false;
});

view raw

index.js

hosted with ❤ by GitHub

そして、任意のタイミングで deferredPrompt.prompt(); を実行します。


btnSave.addEventListener('click', function() {
if(deferredPrompt !== undefined) {
// インストールプロンプト表示
deferredPrompt.prompt();
deferredPrompt.userChoice
.then(function(choiceResult) {
// キャンセルされた場合
if(choiceResult.outcome == 'dismissed') {
console.log('User cancelled home screen install');
} else {
// インストールされた場合
console.log('User added to home screen');
}
deferredPrompt = null;
});
}
});

view raw

index.js

hosted with ❤ by GitHub

ただし、現状では beforeinstallprompt がいつ実行されるかが分からず、かつ一度離脱した後、確実に次も呼ばれるかは分かりませんので注意してください。

インストール状況を調べる

PWAをインストール済みかどうかは event.userChoice で判別できます。 accepted であればインストール済み、dismissedであればキャンセル済みです。


window.addEventListener('beforeinstallprompt', function(e) {
e.userChoice.then(function(choiceResult) {
if(choiceResult.outcome == 'dismissed') {
console.log('User cancelled home screen install');
}
else {
console.log('User added to home screen');
}
});
});

view raw

index.js

hosted with ❤ by GitHub

ネイティブアプリのインストールを進める

manifest.jsonに related_applications を指定することでネイティブアプリを進められるようになります。


"related_applications": [
{
"platform": "play",
"id": "com.google.samples.apps.iosched"
}
]

view raw

index.json

hosted with ❤ by GitHub

さらに preferrelatedapplications を true として指定するとPWAのインストールバナーは出さず、アプリのバナーのみになります。


"prefer_related_applications": true,
"related_applications": [
{
"platform": "play",
"id": "com.google.samples.apps.iosched"
}
]

view raw

index.json

hosted with ❤ by GitHub


PWAはネイティブアプリとWebアプリの中間とも言える存在です。単純にWebアプリ + インストールできる程度に扱うのではなく、ちょっとした工夫で解析や、よりユーザビリティの高い仕組みが提供できます。

via ウェブアプリのインストール バナー  |  Web  |  Google Developers

via ウェブアプリ マニフェスト  |  Web  |  Google Developers

PWAの基礎知識(その6)「Web App Manifest作成の便利テクニック」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Web App Manifestを作る時に便利なテクニックを紹介します。

Web App Manifestとは?

Web App ManifestはWebアプリをスマートフォンアプリのようにインストールできる技術です。アプリストアを経由せずに配布できるのが魅力です。

検証

Web App Manifestではmanifest.jsonというファイルを作成しますが、これが正しく作れているかどうかが確認しづらいという問題があります。そこで使ってみて欲しいのがGoogle Chromeの開発者ツールです。アプリケーションタブにManifestという項目が追加されています。これを見ると設定内容が正しく認識されているか一目で分かります。

また、Add to HomescreenというリンクをクリックしてChromeアプリにすることもできます。これはほぼブックマークですが、Chromeのブックマークバーよりアプリと言うリンクをクリックすると一覧で表示できます。

さらに詳しい検証

もっと詳しい情報を知ったり、 Web App Manifestに限らずPWAが正しく設定できているか確認するためにはLighthouseという機能拡張が便利です。PWAにしたいWebサイトを表示した状態で実行すると、レポート生成してくれます。

主なチェックポイントとしては次の通りです。

  • Service Workerを登録しているか
  • オフラインでも200のレスポンスを返すか
  • HTTPSを使っているか
  • スプラッシュスクリーンが使えるか
  • アドレスバーの色設定が行われているか
  • HTTPへのアクセスがHTTPSにリダイレクトされるか
  • 3G回線でも十分に速いか
  • メタタグでviewport設定を行っているか

Google Chromeの設定

開発中はインストールバナーが表示されるタイミングが非常に分かりづらく、何度も再読込を繰り返すことになるかと思います。そこでAndroidのGoogle Chromeにて、フラグの設定をします。 chroke://flags を表示して、Bypass user engagement checksを有効にします。これでバナーがすぐに出るようになります。

なお、アプリをインストールした後に削除し、再度バナーを出す場合にはアプリのデータをすべて削除し、再度Bypass user engagement checksを有効にすると表示されるようです。


Web App Manifestはまだ対応しているブラウザが少なく、利用されるとしてもAndroidがメインデバイスになるかと思います(Safariはまだ中途半端にしか対応していないため)。まだ機能的に不十分な感はありますが、幾つかのテクニックを駆使して効率的な開発に努めてください。

PWAの基礎知識(その5)「Web App Manifestの使い方」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Web App Manifestについて紹介します。

利用できるブラウザについて

Web App Manifestはまだ仕様が確定しておらず、一部のWebブラウザでしか使えません。Google Chromeはデスクトップ(アイコンのみ)、スマートフォンともに利用できます。また、iOSのSafariでは11.3からサポートを開始しています。デスクトップで利用できるのはEdgeくらいのようです。

要件

技術的には以下が必須になります。

  • HTTPSサイトである
  • Service Workerを使っている
  • マニフェストファイルを配置している

Service Workerを使っている

Service Workerを使ってオンライン上のファイルをキャッシュします。まずWebブラウザ側のJavaScriptでService Worker用のJavaScriptを読み込みます。


if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./sw.js', {
scope: './'
})
.then((reg) => {
if(reg.installing) {
console.log('Service Worker をインストール中です');
} else if(reg.waiting) {
console.log('Service Worker をインストールしました');
} else if(reg.active) {
console.log('Service Worker は有効です');
}
})
.catch((error) => {
// registration failed
console.log('登録失敗:', error);
});
}

view raw

index.js

hosted with ❤ by GitHub

Service Worker用のJavaScriptではキャッシュしたいファイルを読み込みます。


self.addEventListener('install', e => {
// インストール時に実行
e.waitUntil(
caches.open('mismith').then(cache => {
return cache.addAll([
'/',
'/client.js',
'/style.css'
])
.then(() => self.skipWaiting());
})
);
});

view raw

index.js

hosted with ❤ by GitHub

マニフェストファイルを設置している

マニフェストファイルは manifest.json という名前で作られることが多いようです。このファイルをHTMLのheadタグ内で読み込みます。


<link rel="manifest" href="/manifest.json">

view raw

index.html

hosted with ❤ by GitHub

内容は次のようになります。縦向きで、アプリのようなUIで表示されます。また、テーマカラーであったり、スプラッシュスクリーンの背景色なども指定できます。


{
"display": "standalone",
"short_name": "hifiveTodo",
"name": "hifiveTodo",
"orientation": "portrait",
"icons": [
{
"src": "launcher-icon-1x.png",
"type": "image/png",
"sizes": "48×48"
},
{
"src": "launcher-icon-2x.png",
"type": "image/png",
"sizes": "96×96"
},
{
"src": "launcher-icon-3x.png",
"type": "image/png",
"sizes": "192×192"
}
],
"start_url": "./index.html",
"background_color": "#e1efde",
"theme_color": "#e1efde"
}

view raw

index.json

hosted with ❤ by GitHub

これをWebサイト上に配置します。

サイトを表示する

Google Chromeの場合は5分以上置いてアクセスするとインストールバナーが表示されるとあります。しかし体感としてはもっと空けるか、Webブラウザの設定を変更した方が良いのではないかと思います。

表示して少し経つとバナーが表示されます。

こういう小さいバナーの場合もあります。

バナーをタップすると、インストールの確認が出ます。

インストールするとホーム画面上にアイコンが表示されます。

アプリと同じなので、コンテクストメニューがブックマークとは異なります。

アイコンをタップするとアプリが表示されます。アプリのアイコンで192pxのものを用意しておくとスプラッシュスクリーンが表示されるようです。Service Workerを使っていますのでオフラインでも使えます。


Web App Manifestはまだブラックボックスな動作もあります(バナーが表示されるタイミングや再表示させる方法など)。アプリを一度アンインストールした後、再度インストールする方法も明文化されていません。

とは言え、Webアプリをネイティブアプリ化できるのは面白い技術で、通常のアプリストアを頼らない配布も便利です。業務アプリなどでも使えそうです。

PWAの基礎知識(その3)「オンライン、オフライン判定」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Service Workerについて紹介します。

利用できるWebブラウザについて

Can I useによると、モダンなブラウザの中でService Workerが使えないのはIEとOpera miniだけになっています。スマートフォンであれば安心して利用できるようになっています。

オンライン/オフラインは navigator.onLine で判定する

オンラインかオフラインの判定として使えるのが navigator.onLine です。trueの場合はオンライン、falseはオフラインです。

オフラインの時だけ表示を変える

ではオンラインの時だけユーザがそれと分かるように表示してあげましょう。ただしService WorkerはDOMが使えませんので、文字を出すことはできません。そこで、SVG(または画像)を使います。

二つのSVGをキャッシュする

まずオンライン、オフライン用のSVGを二つ読み込みます。


self.addEventListener('install', e => {
// インストール時に実行
e.waitUntil(
caches.open('mismith').then(cache => {
return cache.addAll([
'/',
'/client.js',
'/style.css',
'/ok.svg', // オンライン用
'/fail.svg' // オフライン用
])
.then(() => self.skipWaiting());
})
);
});

view raw

index.js

hosted with ❤ by GitHub

HTMLの修正

HTML側はオンライン時の表示としておきます。


<p>
Network status: <img src="ok.svg" width="20px" />
</p>

view raw

index.html

hosted with ❤ by GitHub

表示判定処理

そして、キャッシュを表示する fetch 処理にてオンライン、オフライン状態に応じてレスポンスを変えます。


self.addEventListener('fetch', e => {
// 外部リソース取得時
const url = new URL(e.request.url);
// オフラインの時にはfail.svgを表示する
if (url.origin == location.origin && url.pathname == '/ok.svg' && !navigator.onLine) {
return e.respondWith(caches.match('/fail.svg'));
}
// それ以外の場合は同じ
e.respondWith(
caches.match(e.request, {
ignoreSearch: true
})
.then(response => {
return response || fetch(e.request);
})
);
});

view raw

index.js

hosted with ❤ by GitHub

こうするとオフライン時にはオフライン用のSVG画像を出せるようになります。


PWAではある程度のオフライン状態でも利用できますが、それでも最近のWebアプリとしてはネットワークがないと機能不足になってしまうでしょう。そうした時にJavaScript側でエラー処理を行うのはもちろんですが、ユーザの視覚にも反映してあげるとユーザビリティが高いと言えます。

PWAの基礎知識(その2)「Service Workerの開発法」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Service Workerの開発方法について紹介します。

Google Chromeの場合

Google ChromeはWebブラウザ側、Service Worker側の両方のスクリプトについて一つの開発者ツール内で扱えます。

また、オフライン時の確認をする際には開発者ツールのアプリケーションタブ、Service Workersの中にOfflineというチェックを有効にすることで動作検証できます。

Firefoxの場合

Firefoxはツールメニューのウェブ開発、Service Workerという項目を選択します。

現在、デバッガーがマルチプロセスコンテンツに対応していないのでオプトアウトしないとデバッグできません。

オプトアウト後、Service Workerが組み込まれているページを表示するとデバッグボタンが押せるようになります。これを押すと別ウィンドウでデバッガーが立ち上がります。

もちろんブレークポイントを入れることもできます。

オフライン時の検証はファイルメニューのオフライン作業を選んで有効にできます。

Safariの場合

Safariは開発メニューのService Workerを選んでデバッグします。

Firefoxと同レベルのデバッグができます。ただしオフライン作業にはできませんでした。

Edgeの場合

Edgeは開発者ツールのデバッガーの中にService Workersという項目があり、スクリプトを選んで検査というリンクをクリックすると専用のデバッガーが立ち上がります。

EdgeはService Workerが読み込まれていることを通知してくれます。これは他のブラウザにはない機能です。

なお、オフラインにする機能はないようです。


Google Chromeだけが通常のJavaScriptとService Workerを同じ開発者ツールの中でデバッグできます。他のブラウザは別ウィンドウでデバッグとなります。また、Google ChromeとFirefoxはオフライン時の挙動を素早く確認できます。

通常のJavaScriptと異なるので、デバッグ方法も異なります。各ブラウザでのやり方を覚えておくと開発がスムーズになるでしょう。

PWAの基礎知識(その1)「Service Workerの使い方」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Service Workerについて紹介します。

利用できるWebブラウザについて

Can I useによると、モダンなブラウザの中でService Workerが使えないのはIEとOpera miniだけになっています。スマートフォンであれば安心して利用できるようになっています。

使うための基本形

Service WorkerはフロントエンドのJavaScriptとは別プロセスで実行されます。まずWebブラウザ側のJavaScriptにてService Workerを登録します(今回は sw.js としています)。


if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./sw.js', {
scope: './'
})
.then((reg) => {
if(reg.installing) {
console.log('Service Worker をインストール中です');
} else if(reg.waiting) {
console.log('Service Worker をインストールしました');
} else if(reg.active) {
console.log('Service Worker は有効です');
}
})
.catch((error) => {
// registration failed
console.log('登録失敗:', error);
});
}

view raw

index.js

hosted with ❤ by GitHub

これで登録できます。

Service Workerの内容

Service Workerではselfというオブジェクトが存在します。これはdocumentが使えないService Worker内で全体のイベントハンドリングを行う存在です。イベントハンドリングが基本機能になります。


self.addEventListener('install', e => {
// インストール時に実行
});
self.addEventListener('activate', e => {
// アップデート時など
});
self.addEventListener('fetch', e => {
// 外部リソース取得時
});

view raw

index.js

hosted with ❤ by GitHub

インストール時の内容

インストール(install)イベントではキャッシュの設定をします。以下のコードではルートのHTMLだけキャッシュします。


self.addEventListener('install', e => {
// インストール時に実行
e.waitUntil(
caches.open('mismith').then(cache => {
return cache.addAll([
'/'
])
.then(() => self.skipWaiting());
})
);
});

view raw

index.js

hosted with ❤ by GitHub

各リソースへのリクエスト

画像に限らず、リソースへのアクセスがあるとこのfetchイベントが発生します。


self.addEventListener('fetch', e => {
// 外部リソース取得時
console.log('fetch', e.request.url);
e.respondWith(
caches.match(e.request, {
ignoreSearch:true
})
.then(response => {
return response || fetch(e.request);
})
);
});

view raw

index.js

hosted with ❤ by GitHub

ここではキャッシュの存在を確認し、キャッシュがなければfetchで改めてリクエストします。

例えばこの状態で一度Webページを表示し、オフラインして表示するとオフライン化はされますが、HTMLだけしか表示されません。

そこでキャッシュするリソースを追加します。


self.addEventListener('install', e => {
// インストール時に実行
e.waitUntil(
caches.open('mismith').then(cache => {
return cache.addAll([
'/',
'/client.js',
'/style.css'
])
.then(() => self.skipWaiting());
})
);
});

view raw

index.js

hosted with ❤ by GitHub

この状態で一度オンライン状態で読み込み、再度オフラインにして読み込み直すとスタイルシートもキャッシュされているのが確認できます。


Service Workerを使ったリソースのキャッシングの仕組みはとてもシンプルです。PWA化の第一歩として試してみてください。