フォームで画像アップロードを受け付けていると、「iPhoneから送った写真だけプレビューが真っ白」「管理画面で画像が壊れている」という問い合わせが来ることがあります。原因の多くは HEIC(HEIF) です。対応のパターンを整理しておきます。
そもそも何が起きているか
- iPhoneのカメラは既定で HEIC 形式で保存します(設定 > カメラ > フォーマット >「高効率」)。
- 2026年時点で、
<img>で HEIC をそのまま表示できるのは Safari 系が中心です。Chrome / Firefox / Edge ではデスクトップでもまだ不安定で、確実に表示できる前提にはできません。 - サーバー側の画像ライブラリ(ビルドによっては sharp や Pillow なども)は HEIC デコードに追加の依存(libheif)が必要です。
実はiOSが自動変換しているケース
<input type="file" accept="image/*"> で写真ライブラリから選ぶと、iOS Safari は多くの場合 JPEG に変換して 渡してきます。ところが以下の場合は HEIC のまま届きます。
-
acceptにimage/heicを明示的に含めている - 「ファイル」アプリ経由で選択した
- AirDrop で Mac に送った写真を Mac のブラウザからアップロードした
つまり「iPhoneだから必ず HEIC」でも「iOSが変換するから大丈夫」でもない、というのがややこしい点です。
判定は拡張子ではなく中身で
async function isHeic(file) {
const buf = new Uint8Array(await file.slice(0, 12).arrayBuffer());
const brand = String.fromCharCode(...buf.slice(4, 12));
return brand.startsWith('ftyp') &&
['heic', 'heix', 'mif1', 'msf1'].some(b => brand.includes(b));
}
file.type が空文字で来ることもあるので、マジックバイトで判定するのが確実です。
対応パターン
| 方式 | メリット | デメリット |
|---|---|---|
| クライアントで JPEG 変換(WASM の libheif など) | サーバー改修不要、送信量も減る | ライブラリが重い(数百KB〜1MB超)、低スペック端末で遅い |
| サーバーで変換 | 確実、元ファイルを保持できる | libheif の導入・脆弱性対応が必要 |
| アップロードを拒否して案内 | 実装が最小 | UX が悪い |
個人的には「クライアントで変換を試み、失敗したらサーバーで変換」の二段構えにしています。変換ライブラリは プレビューが必要になった時点で dynamic import すると、初期バンドルを汚しません。
EXIF の向きに注意
HEIC → JPEG 変換時に Orientation を落とすと、縦写真が横向きになります。変換後に画素へ回転を適用してから EXIF を削除するのが安全です。位置情報(GPS)も一緒に落とせるので、プライバシー的にもこの順番がおすすめです。
ユーザーへの案内用
サポート対応で「とりあえず JPEG にして送ってください」とお願いする場面では、インストール不要でブラウザ内で変換できる Nibbo の HEIC→JPG 変換 を案内しています。ファイルがサーバーに送られないので、個人の写真でもお願いしやすいです。
まとめ
- HEIC は「来るかもしれない」前提で、マジックバイトで判定する
- 変換はクライアント→サーバーのフォールバック
- 向きを適用してからメタデータを削除
— Vitalii