apple-touch-startup-image ?
PWAは簡単にいうとWebページをネイティブアプリかのようにインストール可能にする技術。
インストールの際にどんな感じでネイティブアプリっぽく振る舞えばよいかを端末に伝えるのがmanifest.json。
アプリのアイコンや表示するアプリ名もそこで指定するのだがAndroidだとそれらの情報からスプラッシュ画面をいい感じに自動生成してくれる。
iOSはというとそんな気の利いたことはしてくれず真っ白な画面が代わりに表示される。
が、一応スプラッシュ画面を表示する手段はSafari大好き独自規格で用意されている。
ページのheadに<link rel="apple-touch-startup-image" href="url/to/splash.png">
を入れるとこれを表示してくれるらしい。
楽勝じゃん。
そう思っていた時期が私にもありました。
解像度問題
さてでは適当にアプリのアイコンのurlをそのままぶち込んで…
表示されない。
調べると用意する画像が画面サイズぴったりで作る必要があるとのこと。
つまり同じ内容でサイズが違うだけの画像を十数枚用意する必要があり、
同じ内容でurlが違うだけのlinkタグを十数行書く必要がある。
おのれSafari案件ですね。
どうしたもんかなとネットの海を彷徨っていたら
えっ……クライアント側で生成して反映されるのか……
ogpではそんなこと出来なかったよな……
画像が読み込まれるタイミングについて
pwacompatはmanifest.json等から情報をかき集めてapple-touch-startup-imageを生成する機能を有している。
canvasでレンダリングしてdataurlにしてheadにぶち込んでいる。
scriptタグの記載位置については指定が無いうえ、jsであとから読み込むのもいいよねとreadmeに書いてある。
どうやらapple-touch-iconと同じようにホーム画面に追加する瞬間に例のlinkタグがあれば良いような気がする。
実装
navigator.standalone==false&&requestAnimationFrame(w=>{const img=new Image();img.onload=()=>{
w={c:document.createElement('canvas'),w:screen.width*devicePixelRatio,h:screen.height*devicePixelRatio};
const draw=([cw,ch])=>{
w.c.width=cw;w.c.height=ch;const ctx=w.c.getContext('2d'),{width:iw,height:ih}=img,s=Math.min(cw/iw,ch/ih);
ctx.drawImage(img,0,0,1,1,0,0,cw,ch);ctx.drawImage(img,0,0,iw,ih,(cw-iw*s)/2,(ch-ih*s)/2,iw*s,ih*s);return w.c.toDataURL();
};
document.head.insertAdjacentHTML('beforeend',[[draw([w.w,w.h]),'portrait'],[draw([w.h,w.w]),'landscape']].map(x=>`<link rel="apple-touch-startup-image" href="${x[0]}" media="(orientation:${x[1]})">`).join(''));
};img.src='path/to/teaser.png';});
githubのogp用に生成した16:9くらいのアイコンと名前が入った画像が手元にあったのでそれを流用することにした。
PWAで動いていないiOSを検出するためにnavigator.standalone==falseを使っている。
screen.width(height)*devicePixelRatioで画面の解像度を計算する。
それをcanvasに当てて持ってきた画像をそこにピッタリ入るように描く。
背景色として画像の左上の色を使っている。
出来上がったらdataurlにしてlinkタグに流す。
縦横を入れ替えて同様の手順で横向きの画像も作成する。
こっちの方がかっこいいじゃん!
標準でスプラッシュ画面いじれる規格が欲しいね!!
めでたしめでたし。
まとめ
- iOSのpwaでスプラッシュを表示するにはapple-touch-startup-imageを使う
- 端末の解像度ぴったりの画像が必要だがクライアント側で生成して使える。
今回はpwacompatに倣ってdataurlにしたが時間がかかっても良いということはつまりObjectURLでも良い気がする。
そしてそれをindexeddbに保存して……
まだ改良の余地はありそう。