Algorithm & UI/UX
Canvasを用いたはめ込み合成と入力画像の縮小
「ただ画像を重ねるだけ」に見えるモックアップ作成。しかし、最近の高画質なスマホスクショをそのまま扱うと、ブラウザのメモリ不足や動作の遅延を招きます。本記事では、クライアントサイドで完結しつつ、いかに軽快かつ美しく合成するか、その実装の勘所を解説します。
このツールの入力から出力まで
現行コードの確認日:
- 入力
- はめ込みたい画像を1枚選び、端末フレームの種類を選びます。
- 処理
- 画像を枠内へ描き、周囲の図形や影を重ねて完成イメージを合成します。
- 出力
- 保存は選んだフレームの固定寸法の PNG で、プレビューと保存で異なる高解像度写真を使い分けるわけではありません。
まず確認する点:画像は画面枠へ中央から切り取られ、位置の手動調整はできません。スクリーンショットの文字やボタンが欠けないか確認してください。
実装の確認箇所:src/pages/image/mockup/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。
仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、File APIとObject URL(選んだ画像の読み込みとBlob URLの解放)が参考になります。
1. アスペクト比を維持する「Canvas Cover」アルゴリズム
ユーザーがアップロードするスクショの解像度は様々です。CSSの object-fit: cover のような挙動をCanvas上で再現するため、スクショのアスペクト比とデバイスフレームの表示領域を比較し、最適な倍率と描画位置を動的に算出しています。
単に領域に合わせる(Fill)のではなく、Math.max を用いて「足りない辺がないように拡大」し、中央でクリッピングすることで、どのような画像サイズでも端末に綺麗にはまっているように見せています。
画像の縦横比を保って、画面領域を埋める描画位置を計算する例です。はみ出す部分は呼び出し側の clip() で隠します。
function coverRect(imageWidth, imageHeight, screen) {
const scale = Math.max(screen.width / imageWidth, screen.height / imageHeight);
const width = imageWidth * scale, height = imageHeight * scale;
return { x: screen.x + (screen.width - width) / 2,
y: screen.y + (screen.height - height) / 2, width, height };
} 2. 処理する画素数を抑えるプレ・レンダリング・スケーリング
4K解像度を超えるようなスクショをそのままCanvasでリサイズ・合成しようとすると、低スペックな端末ではブラウザがクラッシュすることがあります。
このツールでは、画像読み込み直後に「内部処理用の適正サイズ」へのダウンサンプリングを一段噛ませています。以降のフレーム合成では縮小後の画像を使い、元の巨大な写真を毎回処理しない構成です。小さい入力を拡大しても写真の細部は増えません。
3. iPhoneユーザーのための動的HEIC変換ユニット
このツールでは、HEICを選んだ場合はブラウザ内で別の画像形式に変換してから編集します。本ツールでは、HEICファイルが検知された場合のみ、heic2any ライブラリを動的インポート(Dynamic Import)してクライアントサイドでJPEGに変換しています。
最初から全ライブラリを読み込むのではなく、必要な時だけロードすることで、通常画像の処理では HEIC 用コードの読み込みを省きます。LCP などの実測結果を保証するものではありません。
出力の仕様と確認する点
入力画像は長辺 1500px 以下に縮小します。保存は選んだフレームの固定寸法の PNG で、プレビューと保存で異なる高解像度写真を使い分けるわけではありません。
スクショは画面領域を埋める中央配置です。縦横比が合わない場合は上下または左右が切れ、手動で位置をずらす処理はありません。ステータスバーや下部の文字が欠けない比率の画像を用意してください。
フレームは角丸矩形とノッチを Canvas で描きます。周囲の透明部分を保って PNG にできる一方、実機固有の外観や画面仕様を正確に再現するものではありません。