iPhone から写真付きのつぶやきを投稿できないことがあったので、WebP 変換ライブラリで軽くしてから送るようにした

困っていたこと

iPhone のホーム画面に追加した管理画面から、写真付きのつぶやきを投稿しようとすると、投稿ボタンが読み込み中のまま止まったり、投稿できなかったりすることがあった。パソコンから投稿したときは問題なかった。

写真を送る前にしていること

管理画面では、写真をサーバーへ送る前に、ブラウザの中で写真を軽くしている。

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 を作る形に改めた。

2. 開発サーバーで読み込めるようにする

このライブラリは自分で .wasm ファイルを読み込む。開発サーバー(Vite)の事前まとめ処理(optimizeDeps)を通すとファイルの場所がずれて読み込めなくなるので、astro.config.mjs で対象から外した。

3. サーバーのエラーを「ログイン切れ」と取り違えない

前回、セッション切れを見分ける処理を入れたとき、「JSON 以外の返事が来たらログイン切れ」としていた。しかし Cloudflare 側のエラー(処理時間の上限超えなど)も JSON 以外で返ってくるので、本当の原因が分からなくなる。

ログイン画面へのリダイレクトと 401 だけをログイン切れとして扱い、それ以外はエラー番号付きで表示するようにした。

参考