アートディレクション実務:フォーカルクロップとbreakpoint別の画角設計(Next.js)
1枚の画像を全デバイスに流用すると、スマホでは被写体が小さく、PCではスカスカ。必要なのはアートディレクションです。 “焦点”を記述してクロップ/画角を切り替え、見せたいものを確実に見せる設計にします。
先に結論
- 焦点(x,y,zoom)をJSONで管理し、ビルドで自動クロップ。
- 画角が変わるなら picture、フレーム内で寄せるだけなら object-position。
- ヒーローは priority+fetchPriority を忘れずに。
要点(TL;DR)
1. データの持ち方(焦点メタ)
// content/focal.json(焦点メタデータの例)
{
"hero.jpg": { "x": 0.42, "y": 0.38, "zoom": 1.15 }, // 0–1で正規化座標
"team.jpg": { "x": 0.5, "y": 0.35, "zoom": 1.0 }
}
2. ビルドで自動クロップ
// scripts/focal-crop.ts — 焦点に基づく自動クロップ
// 依存: npm i -D sharp tsx
import fs from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
import focal from "../content/focal.json";
const IN = "input";
const OUT = "output";
const targets = [
{ w: 1600, h: 900, suffix: "_16x9" },
{ w: 1200, h: 1200, suffix: "_1x1" },
{ w: 800, h: 1200, suffix: "_2x3" },
];
async function main() {
await fs.mkdir(OUT, { recursive: true });
const files = (await fs.readdir(IN)).filter(f => /\.(jpe?g|png)$/i.test(f));
for (const f of files) {
const src = path.join(IN, f);
const meta = (focal as any)[f] ?? { x: 0.5, y: 0.5, zoom: 1.0 };
const img = sharp(src).rotate().toColourspace("srgb");
const { width, height } = await img.metadata();
if (!width || !height) continue;
for (const t of targets) {
// 焦点中心から目的アスペクトで切り出し
const cropW = Math.min(width, Math.round(width / meta.zoom));
const cropH = Math.min(height, Math.round((cropW * t.h) / t.w));
const cx = Math.round(meta.x * width);
const cy = Math.round(meta.y * height);
const left = Math.max(0, Math.min(width - cropW, cx - Math.round(cropW / 2)));
const top = Math.max(0, Math.min(height - cropH, cy - Math.round(cropH / 2)));
await img
.extract({ left, top, width: cropW, height: cropH })
.resize(t.w, t.h)
.jpeg({ quality: 82, mozjpeg: true, progressive: true })
.toFile(path.join(OUT, path.parse(f).name + t.suffix + ".jpg"));
}
}
}
main().catch(console.error);
3. 表示:object-position と picture
// components/FocalImage.tsx — next/imageで焦点表示
import Image from "next/image";
export default function FocalImage(props: { src: string; alt: string; focal?: { x: number; y: number } }) {
const { src, alt, focal = { x: 0.5, y: 0.5 } } = props;
const pos = `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%`;
return (
<div className="relative aspect-[16/9] overflow-hidden rounded-xl">
<Image
src={src}
alt={alt}
fill
sizes="(min-width: 1024px) 1200px, 100vw"
style={{ objectFit: "cover", objectPosition: pos }}
priority
fetchPriority="high"
/>
</div>
);
}
<!-- pictureでアートディレクション(枠外情報が重要なとき)-->
<picture>
<source media="(min-width: 1024px)" srcset="/img/hero_16x9.jpg" />
<source media="(max-width: 1023px)" srcset="/img/hero_2x3.jpg" />
<img src="/img/hero_1x1.jpg" width="1200" height="1200" alt="Hero" />
</picture>
4. 応用と使いどころ
5. 公開前チェック
- スマホ/PCで被写体が十分なサイズで見えている。
- 重要テキスト/ロゴが切れていない(安全域内)。
- ヒーローの優先度設定・CLSゼロが担保されている。
6. まとめ
アートディレクションは焦点メタ+自動クロップで運用化できます。見せたいものを確実に見せ、体験とCTRを底上げしましょう。