定義
ブログ本文に入れた縦長のスクリーンショットが上下で切れて見えたことがあるなら、あるいは画像が読み込まれた瞬間に読んでいた文章が下へずれたことがあるなら、この概念が必要だ。
固有寸法(intrinsic dimensions) とは、画像ファイル自体が持つ元のピクセルサイズのことだ。マークダウンや CMS 本文から生成される <img> にはたいてい width/height がなく、ブラウザは読み込み前に場所を確保できない。ビルド時点でローカルの画像ファイルを開いて実寸法を読み、属性として注入する ことで、場所の確保と原寸比レンダリングが同時に解決する。
なぜ必要か
寸法のない画像は読み込み前の高さがゼロだ。到着が遅れると下のコンテンツが押しやられ、これは 累積レイアウトシフト(CLS) として計測され、ユーザー体験と検索順位の両方に不利に働く。
よくある対処はコンテナに aspect-ratio: 16 / 9 を与えることだ。ずれは止まるが代償がある — 縦長画像、長いスクリーンショット、正方形の図が切り取られる。比率を 推測する 方式の限界だ。根本解決は推測をやめて元から知ることであり、ローカル資産ならビルド時にファイルを読めばよいので実行時コストもネットワークリクエストもゼロになる。
動作原理
- マークダウンが HTML ツリー(hast)へ変換される。
- プラグインがツリーを走査して
imgノードを探す。 srcがローカル資産のパス(/images/...など)ならpublic/配下の実ファイル経路へ解決する。- 画像のヘッダーから幅と高さを読む(全体のデコードは不要)。
width/height属性をノードへ注入する。- ブラウザは二つの属性から
aspect-ratioを自動計算し、読み込み前に正確な場所を確保する。 - CSS は寸法のある画像だけを
height: autoにして原寸比で描き、ない画像には既存のフォールバックを維持する。
| 状態 | 場所の確保 | 比率 | 結果 |
|---|---|---|---|
| 寸法なし | されない | 未定 | 読み込み時にレイアウトシフト |
固定 aspect-ratio フォールバック | される | 強制 16:9 | ずれなし、縦長画像が切れる |
| 固有寸法の注入 | される | 原寸 | ずれも切れもなし |
実務適用
// rehype プラグイン(ビルド時)
import { visit } from "unist-util-visit";
import imageSize from "image-size";
import { readFileSync } from "node:fs";
import path from "node:path";
export function rehypeImageSize({ publicDir }: { publicDir: string }) {
return (tree: Root) => {
visit(tree, "element", (node) => {
if (node.tagName !== "img") return;
const src = String(node.properties?.src ?? "");
if (!src.startsWith("/")) return; // 外部 URL には触れない
if (node.properties?.width) return; // 明示指定があれば尊重
try {
const file = readFileSync(path.join(publicDir, src));
const { width, height } = imageSize(file);
if (width && height) Object.assign(node.properties!, { width, height });
} catch {
// ファイルがなければフォールバックに任せる — ビルドは壊さない
}
});
};
}
/* 寸法のある画像だけ原寸比、それ以外は既存のクロップフォールバックを維持 */
.prose img[width][height] {
height: auto;
max-width: 100%;
}
.prose img:not([width]) {
aspect-ratio: 16 / 9;
object-fit: cover;
}
ユニットテストは「ローカル画像には寸法が付き、外部 URL はそのまま」を固定する。
トレードオフ
- ビルド時の注入: 実行時コストゼロ、利用者端末の負担ゼロ。代わりにビルド時間が画像数に比例して伸び、ローカル資産にしか適用できない。
- 実行時の計測(
onLoadで比率を反映): 外部 URL でも動くが、すでに手遅れだ — レイアウトシフトはその時点で発生している。 - 固定比率フォールバック: 実装が最も安いがコンテンツを損なう。寸法を得られない場合の 安全網としてのみ 残す。
- 画像 CDN のパラメータ(
?w=1200&h=800): URL から寸法が分かるので便利だが、ベンダーに縛られる。
使ってはいけない場合
- 本文の画像がすべて外部 URL の場合 — ビルド時にファイルを読めないので、別の戦略(CDN のメタデータ、アップロード時の記録)が必要だ。
- 画像を意図的に固定比率のサムネイルグリッドへ入れる場合 — そこではクロップはバグではなくデザインだ。
よくある間違い
- フォールバックを消す。 寸法を得られない画像は必ず残る。新しい経路が失敗しても従来の挙動へ安全に落ちる必要がある。
width/heightを CSS ピクセルと誤解する。 この属性は比率計算用の原寸法であり、実際の表示サイズは CSS が決める。height: autoを忘れる。width: 100%だけを与えて高さを残すと画像が潰れる。- 外部 URL までビルドで取得しようとする。 ビルドがネットワークに依存して遅くなり、CI で断続的に失敗する。
- すべての画像に
loading="lazy"を付ける。 初期表示の画像はかえって遅くなる — ヒーローだけfetchpriority="high"にする。
関連概念
- cls-skeleton-layout-reservation — 場所の確保でレイアウトシフトを防ぐ
- lqip-blur-placeholder-ssr — 読み込み前の場所を低解像度プレビューで埋める
- carousel-viewport-image-deferral — ビューポート外の画像の読み込み遅延
- server-image-proxy-transcoding-cache — サーバー側の画像変換とキャッシュ