困っていたこと
iPhone のホーム画面に追加した管理画面から、写真付きのつぶやきを投稿しようとすると、投稿ボタンが読み込み中のまま止まったり、投稿できなかったりすることがあった。パソコンから投稿したときは問題なかった。
写真を送る前にしていること
管理画面では、写真をサーバーへ送る前に、ブラウザの中で写真を軽くしている。
- 長い辺を1600ピクセルまで縮める
- WebP(Google が作った、軽い画像の形式)に変換する
- 軽くする前の元の写真も、あとでダウンロードできるよう一緒に送る
Cloudflare Workers の無料プランは、1回の処理で使える CPU 時間がとても短い。だから重い処理はブラウザ側でやって、サーバーには軽い画像を渡す設計になっている。
原因: iPhone の Safari は WebP で書き出せない
画像を変換するときは、canvas(ブラウザの中で絵を描くための仕組み)の toBlob() という機能を使って「WebP で書き出して」とお願いしている。
ところが iPhone・iPad の Safari は WebP での書き出しに対応していない。そしてエラーを出すのではなく、黙って PNG で書き出して返してくる。
PNG は写真を軽くするのが苦手な形式なので、縮めたのに数MBのままになる。さらに中身は PNG なのに「WebP です」という名札を付けて送っていた。
テストで確かめた結果は次のとおり(ノイズの多い 4000×3000 の写真で試した)。
| ブラウザ | 送った画像 | 元の写真 | 合計 |
|---|---|---|---|
| Chrome | WebP 約240KB | 約6.6MB | 約6.8MB |
| iPhone の Safari(修正前) | 中身は PNG 約4.6MB | 約6.6MB | 約11.2MB |
| iPhone の Safari(修正後) | WebP 約240KB | 約6.6MB | 約6.8MB |
修正前の iPhone では、パソコンの倍近い量を送っていたことになる。通信に時間がかかるうえ、サーバー側も R2(画像の保存先)へ送る前に中身全体の署名を計算するので、処理が重くなっていた。
直したこと
1. ブラウザが WebP で書き出せなかったら、WebP 変換ライブラリで WebP にする
src/lib/imageCompress.ts で、書き出した結果が本当に WebP になっているかを確かめるようにした。WebP になっていなかったら、@jsquash/webp という WebP 変換ライブラリで WebP にする。
このライブラリは、Google の WebP 変換プログラム(libwebp)を WebAssembly(ブラウザの中で動く、速いプログラムの形式)にしたもの。iPhone の Safari でも動く。
最初は「JPEG で書き出し直す」形にしたが、このサイトは画像を WebP にそろえて軽くする方針なので、ライブラリで WebP を作る形に改めた。
- ライブラリは WebP で書き出せないブラウザのときだけ読み込む。パソコンの Chrome などでは読み込まれない
- ライブラリの本体(
.wasmファイル)はブラウザに配る静的ファイルとして置かれるので、Worker の大きさ(無料プランは 3MB まで)には影響しない - 試験では、4000×3000 の写真を約1秒で WebP にできた
2. 開発サーバーで読み込めるようにする
このライブラリは自分で .wasm ファイルを読み込む。開発サーバー(Vite)の事前まとめ処理(optimizeDeps)を通すとファイルの場所がずれて読み込めなくなるので、astro.config.mjs で対象から外した。
3. サーバーのエラーを「ログイン切れ」と取り違えない
前回、セッション切れを見分ける処理を入れたとき、「JSON 以外の返事が来たらログイン切れ」としていた。しかし Cloudflare 側のエラー(処理時間の上限超えなど)も JSON 以外で返ってくるので、本当の原因が分からなくなる。
ログイン画面へのリダイレクトと 401 だけをログイン切れとして扱い、それ以外はエラー番号付きで表示するようにした。