プレースホルダ設計 2025:LQIP Blur + Dominant + Skeleton + Step Fade の組合せ
単一の LQIP だけでは “ぼやけ→突然鮮明” の視覚ジャンプが起きます。既存LQIP を Skeleton + 支配色 + Step Fade に拡張し滑らかさを改善します。
構成
- blurDataURL:雰囲気
- 支配色背景:ちらつき防止
- Skeleton:構造ヒント
- Step Fade:視覚ジャンプ緩和
1. 課題
初期の 意味的手掛かり が不足するとユーザは“待たされている”と感じます。骨組みで大枠を、支配色でテーマを補完します。
2. 組合せ設計
Blur / Dominant / Skeleton / Step Fade を “フェーズ” と “役割” で分離し責務を明確化。Blur は “雰囲気(構図/色温度)” 、Skeleton は “レイアウト予測”、Dominant は “背景チラつき抑制”、Step Fade は “遷移の滑らかさ”。
要素 | 目的 | 表示期間 | 失敗時フォールバック |
---|---|---|---|
Blur LQIP | 配色/構図提示 | 0 → mid | Dominant単色 |
Dominant BG | 残像/白フラッシュ抑制 | 先行 | テーマ色トークン |
Skeleton | レイアウト予測 | 0 → full | Aspect Ratio Box |
Step Fade | 視覚ジャンプ緩和 | mid → full | 単一フェード |
// 1. 24px Blurred JPEG → base64
sharp img.jpg
.resize(24)
.jpeg({ quality: 25 })
.toBuffer()
.then(b=> 'data:image/jpeg;base64,'+b.toString('base64'));
3. Skeleton コンポーネント
Skeleton はサイズ確保 + 視線誘導 が主目的。読みやすさ優先でコントラストは WCAG "本文" 基準未満に抑え視覚ノイズ化を避けます。
// 2. Skeleton DOM (React)
export function Skeleton({ w, h, color }: { w:number; h:number; color:string }){
return <div style={{ width:w, height:h }} className="relative overflow-hidden rounded bg-[color:var(--ph-bg)]">
<div className="absolute inset-0 animate-pulse bg-gradient-to-r from-transparent via-white/15 to-transparent" />
</div>;
}
// Aspect Ratio Box + Skeleton
function PhContainer({w,h,children}:{w:number;h:number;children:React.ReactNode}){
const ar = (h / w * 100).toFixed(4);
return <div className="relative" style={{width:w}}>
<div style={{paddingTop:ar+'%'}} />
<div className="absolute inset-0">{children}</div>
</div>;
}
4. Step Fade ロジック
2段フェードは phase 0→1→2
で ぼかし半径とスケール を段階減衰し "瞬断" を除去。mid 画像は 1/4 解像度など軽量版。
// 3. Step Fade Hook
function useStepFade(){
const [phase,setPhase] = useState(0); // 0 blur,1 mid,2 full
function onMidLoaded(){ setPhase(p=> Math.max(p,1)); }
function onFullLoaded(){ setPhase(2); }
return { phase, onMidLoaded, onFullLoaded };
}
/* 4. CSS (Tailwind例) */
.blur-phase img { filter: blur(12px); transform: scale(1.04); }
.mid-phase img { filter: blur(4px); }
.full-phase img { filter: none; transition: filter .35s ease, transform .35s ease; }
// クラス付与例
<figure className={phase===0?'blur-phase':phase===1?'mid-phase':'full-phase'}>
<img src={phase<2?midSrc:fullSrc} aria-hidden={phase<2} />
</figure>
5. CI / LCP差分検証
CI では LQIP 有無で LCP/INP/TTFB 影響 を A/B 収集し統計的有意差を閾値判定します。
# 5. CI: LQIP有無の LCP/INP 差分サンプリング
node scripts/lcp-sample.js --url /landing --runs 5 --with-placeholder true --out with.json
node scripts/lcp-sample.js --url /landing --runs 5 --with-placeholder false --out without.json
node scripts/lcp-compare.js with.json without.json --lcp-delta -150 --inp-delta 10
// scripts/lcp-compare.js (概念)
import stats from 'simple-statistics';
const a = load('with.json');
const b = load('without.json');
const p = stats.tTestTwoSample(a.lcp, b.lcp);
if (p < 0.05 && mean(a.lcp) < mean(b.lcp) - 120) process.exit(0); else process.exit(1);
6. 運用Tips
- 極端彩度→支配色過飽和回避に
OKLab
で中和 - Skeleton は
prefers-reduced-motion
でアニメ無効 - Error: ネットワーク失敗時 mid→アイコン fallback
- SSR: LQIP を HTML inline し CLS を避ける
7. FAQ
- 低速端末 CPU? → blur 半径固定 + mid 省略 fallback
- FOUT 競合? → 画像 LCP 優先でフォントは optional
- Dark Mode 差? → Dominant 抽出を OKLCH で補正
- SEO 影響? → プレースホルダは alt を持たず role=presentation
8. まとめ
複合プレースホルダは 初期知覚の空白を最小化する分業パターン。計測と CI 化で “効果の持続性” を検証しながら改善を継続。
関連ツール
公開日: 2025-09-06編集: gazou-compressor.jp