Step 4: Quality & Performance
19. スマホが熱くなる?
Canvas画像処理の
メモリ管理と最適化
Webアプリを使っていて、急にブラウザが重くなったり、スマホが熱を帯びたりした経験はありませんか?インフラの世界では馴染み深い「リソース管理」ですが、フロントエンドでも避けては通れない、メモリ最適化の戦いについて記録します。
1. Canvas は想像以上の「メモリ食い」
ブラウザ上で高度な画像加工を可能にする Canvas API ですが、実は非常にメモリを消費する仕組みになっています。通常、JPEGなどの画像ファイルは圧縮された状態で保存されていますが、Canvas に描画(展開)される際は、全てのピクセルが RGBA形式(1ピクセルあたり4バイト) の生のビットマップデータとしてメモリ上に展開されます。
例えば、最近のスマートフォンで撮影した 1200万画素(4000x3000px)の写真をそのまま Canvas に読み込むと、それだけで 4000 × 3000 × 4bytes ≒ 48MB ものメモリを一気に占有します。これを何度も繰り返せば、ブラウザタブのメモリ上限(OOM: Out Of Memory)に達し、クラッシュを引き起こす原因となります。
2. 解決策:URL.revokeObjectURL による明示的な解放
「猫の手ツール」の開発中に直面したのが、写真を数枚入れ替えただけで古いAndroid端末のブラウザが落ちてしまう問題でした。JavaScriptにはガベージコレクション(GC)がありますが、URL.createObjectURL() で生成された一時的なURL(Object URL)は、明示的に解放しない限りブラウザがメモリを持ち続けてしまうという特性があります。
これを解決するのが URL.revokeObjectURL() 命令です。
最適化の Before / After
【Before】メモリリークが発生する書き方
// 写真を切り替えるたびに新しいURLが発行され、古いデータがメモリに残る
const onFileChange = (file) => {
const url = URL.createObjectURL(file);
previewImg.src = url;
// ⚠️ 前のURLが破棄されないため、メモリ消費が増え続ける
}; 【After】リソースを適切に解放する書き方
let currentUrl = null;
const onFileChange = (file) => {
// 1. 以前のURLがあれば、メモリから即座に解放する
if (currentUrl) {
URL.revokeObjectURL(currentUrl);
}
// 2. 新しいURLを発行して保持する
currentUrl = URL.createObjectURL(file);
previewImg.src = currentUrl;
}; 3. 注意点:初心者がハマりやすい「見えないメモリ」
メモリ管理において、特に注意が必要なポイントをいくつか挙げます。
- Canvas要素の再利用: 毎回
document.createElement('canvas')をして巨大なキャンバスを作るのではなく、一つのキャンバスをclearRectでクリアして使い回す方が、GCの負荷を抑えられます。 - 解像度の制限: ブラウザやOSには「Canvasの最大サイズ」という物理的な制限があります。極端に巨大な画像を読み込もうとすると、描画が失敗するだけでなく、タブが即座にクラッシュすることがあります。
- Blobの扱い:
toBlob()で生成されたデータも、不要になったら速やかに変数をnullにするなどして、参照を切り離すことが重要です。
4. インフラエンジニア的な視点での考察
サーバーサイドのチューニングでは「スワップが発生しないようにメモリを空ける」のが鉄則ですが、フロントエンドは「ユーザーが所有する端末という、限られたリソースを借りて動いている」という側面があります。
サーバーならスケールアップで解決できるかもしれませんが、クライアントサイドではそうはいきません。ユーザーのスマホを熱くさせない、電池を無駄に消耗させない、そして何より「落とさない」。この リソース・エチケット とも呼べる配慮こそが、プロフェッショナルなフロントエンドエンジニアに求められる誠実さなのだと痛感しました。
5. まとめ:実務での役立ち
「猫の手ツール」では、このメモリ最適化を徹底したことで、数年前の古いスマートフォンでも、高解像度な証明写真をサクサクと生成できるようになりました。
フロントエンド開発は、派手なアニメーションやUIに目が向きがちですが、その土台を支えるのは、こうした地味で丁寧なメモリ管理です。「動く」のその先にある「心地よく動き続ける」を目指して、これからもピクセル一つ、1バイト一つの重みを意識しながらコードを書いていきたいと思います。