LQIPプレースホルダ設計:blurDataURL + 支配色で“先に見える”を最小コストで
LQIPは画像本体の前に“雰囲気の見え”を置くテクニック。空白が消えるため体感が大きく改善します。 ただし動的生成や過剰なデータ量はINPを悪化させることがあります。 本稿は「軽い・単純・静的」を合言葉に、運用で事故りにくい設計をまとめます。
先に結論
- 24px/Q=40の小さなJPEGと支配色背景の併用が最もコスパ良。
- プレースホルダは前処理でJSON化し、CSR計算を避ける。
- CLSゼロは
width/height
とsizes
、レイアウトはcontent-visibility
で安定化。
要点(TL;DR)
- blurDataURLは20〜40px / Q=30〜50で十分。
- 総量はページで10〜30KB目安(JSONにサイズを書き出して監視)。
- 透過PNGや線画はLQIPの効果が薄い。支配色のみ or なしを検討。
1. LQIPが効く理由と効かない条件
人は先に“全体感”を把握できると待ち時間のストレスが減ります。LQIPはこれを満たす簡易手段です。 一方、透明度が高いPNGやアイコンでは境界が崩れやすく、逆に違和感が出ることがあります。
- 効く:写真、背景のあるイメージ、カードのサムネ。
- 効きづらい:透過アイコン、線画、極小画像。
2. 最短フロー(実務)
- 素材から24pxのミニサムネを生成し、base64化。
- 同時に支配色を抽出し、背景色に設定。
- JSONに集約してNext.jsからimport、
placeholder="blur"
で使用。 - ページ毎の総量を把握し、必要なら幅/品質を落としてチューニング。
3. 実装レシピ(コピペOK)
3.1 生成スクリプト
// scripts/gen-blur-placeholder.ts
// 依存: npm i -D sharp tsx
// 出力: placeholders.json({ "filename": { blurDataURL, bg, bytes } })
import fs from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
const IN = process.env.IN ?? "input";
const OUT = process.env.OUT ?? "placeholders.json";
async function main() {
const files = (await fs.readdir(IN)).filter(f => /\.(jpe?g|png|webp|avif)$/i.test(f));
const map: Record<string, { blurDataURL: string; bg: string; bytes: number }> = {};
for (const f of files) {
const src = path.join(IN, f);
const blur = await sharp(src)
.resize({ width: 24 }) // 16〜32pxでOK。風景は16でも可
.jpeg({ quality: 40, mozjpeg: true }) // 30〜50で調整
.toBuffer();
const { dominant } = await sharp(src).stats();
const bg = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
map[f] = {
blurDataURL: "data:image/jpeg;base64," + blur.toString("base64"),
bg,
bytes: blur.byteLength
};
}
const total = Object.values(map).reduce((s, v) => s + v.bytes, 0);
console.log("Placeholders total:", Math.round(total/1024), "KB");
await fs.writeFile(OUT, JSON.stringify(map, null, 2), "utf-8");
console.log("→", OUT);
}
main().catch(e => { console.error(e); process.exit(1); });
3.2 使用例(next/image)
// app/(example)/ImageCard.tsx
import Image from "next/image";
import placeholders from "@/placeholders.json"; // 生成物をimport
export default function ImageCard({ src, w, h }: { src: string; w: number; h: number }) {
const ph = (placeholders as any)[src];
return (
<div className="rounded-xl overflow-hidden" style={{ background: ph?.bg ?? "#f2f2f2" }}>
<Image
src={"/img/" + src}
alt=""
width={w}
height={h}
placeholder={ph ? "blur" : "empty"}
blurDataURL={ph?.blurDataURL}
sizes="(min-width: 1024px) 600px, 100vw"
loading="lazy"
/>
</div>
);
}
3.3 package.json(ビルド前生成)
// package.json(抜粋:ビルド前にLQIPを生成)
{
"devDependencies": { "sharp": "^0.33.0", "tsx": "^4.0.0" },
"scripts": {
"lqip:gen": "tsx scripts/gen-blur-placeholder.ts",
"build": "npm run lqip:gen && next build"
}
}
3.4 比較メモ(BlurHash/SVG/CSS)
- BlurHash:短い文字列・見栄えは良いがJSでの復号が必要。低速端末や大量グリッドではINP/CPUコストに注意。
- SVGトレース:表現力は高いが生成も描画も重め。ロゴ/アイコン等の小点数に向く。
- CSSグラデーション:超軽量で実装容易。単調背景なら十分な効果。
- 本稿の推奨:24px JPEG + 支配色を前処理で静的化(最小の追加バイトで運用が安定)。
3.5 CSS(レイアウト安定化)
/* content-visibility で初期レイアウトを安定化 */
.cv-auto { content-visibility: auto; contain-intrinsic-size: 1 300px; }
4. 応用と使いどころ
- ヒーロー画像は Progressive JPEG と併用し初期視認性を底上げ。
- グリッド一覧はJSONをビルド時生成し、CSRの計算を避ける。
- 配信は SWR設計 とセットで“速いのに壊れない”。
- 色は sRGB固定 を徹底。
5. 計測と検証(Lighthouse/Field)
- Lighthouse(モバイル)で LCP / INP / CLS を確認。LQIP導入前後で CLS が 0 に近いことを確認。
- Network パネルで blurDataURL が不要に大きくないか確認(1つ数百B〜数KBが目安)。
- Field(CrUXやRUM)で INP/CLS の実ユーザー値を把握。必要なら GA4 にカスタムイベント送信。
// (任意)LQIPの効果をGA4にイベント送信する例(擬似コード)
import { getCLS } from "web-vitals/attribution";
getCLS((m) => {
// 画像読み込み中にCLSが出ていないか観測
window.gtag?.("event", "lqip_cls", { value: Math.round(m.value * 1000) });
});
サイズの目安(例)
- カード12枚のサムネ:24px/Q=40 → プレースホルダ合計 ~10–18KB
- ヒーロー1枚:24px/Q=40 → ~1–3KB
- 合計 15KB 前後なら体感は十分向上し、初期転送への影響も小さいことが多いです。
6. アクセシビリティ / ダークモード
- alt テキストは必須。LQIPは視覚的補助であり、代替テキストの代わりではありません。
- ダークモードでは支配色が暗すぎる場合があるため、最小明度を設ける(例:HSLのL=12%以上)。
- アニメーション合成は避ける(INP悪化)。静的な塗り・ぼかしに留める。
7. ありがちなNGと回避策
- PNGやSVGのLQIPを高品質で作る → 逆に重くなる。写真だけに限定するか、支配色のみ。
- JSONを巨大化 → ファイル数×base64で膨らむ。ページで使う分だけ生成/分割。
- CSRでBlurHash復号 → 表現は良いがCPUコスト大。INPに敏感なら避ける。
8. 公開前チェック
- width/height/sizes 指定で CLS=0。
- blurDataURL 総量が過剰でない(10–30KB目安)。
- 支配色が背景と馴染む(白フチが出ない)。
9. まとめ
LQIPは前処理で静的化が正解。最小の追加バイトで、待ち時間のストレスを確実に減らせます。 画像自体の設計は Progressive JPEG、 配信は キャッシュ設計 と合わせて総合最適へ。
10. FAQ
BlurHashやSVGトレースは使わないほうがいい?
プロジェクト次第です。CSR復号/描画コストと引き換えに表現力が増します。INPに敏感なら静的LQIPが扱いやすいです。
LQIPはSEOに影響する?
画像コンテンツ自体の評価には直接関係しませんが、体感速度の改善は離脱率低下に寄与します。適切な
alt
や 構造化データと合わせて品質を高めましょう。