アニメーション付き ライト/ダーク テーマ切替
View Transitions API でいまや定番となった「サークルワイプ」テーマ切替エフェクトを実現。スナップショット擬似要素に clip-path を当てる創造的な使い方。
UI Patterns
詳細な説明
サークルワイプエフェクト
Chrome の DevRel デモで有名になったこのエフェクトは、クリック位置から円形の reveal を成長させ、ライトからダークへページ全体をきれいなワイプで遷移させます。
async function toggleTheme(event) {
const x = event.clientX;
const y = event.clientY;
const transition = document.startViewTransition(() => {
document.documentElement.classList.toggle('dark');
});
await transition.ready;
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y),
);
document.documentElement.animate(
{
clipPath: [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
],
},
{
duration: 500,
easing: 'ease-in-out',
pseudoElement: '::view-transition-new(root)',
},
);
}
仕組み
startViewTransitionがライトテーマをキャプチャ。- クラストグルでダークへ切替、React 再描画。
- ブラウザがダークテーマをキャプチャ。
await transition.readyでスナップショット準備完了を待機。::view-transition-new(root)擬似要素のclip-pathをアニメーション。新しい(ダーク)スナップショットを成長する円で reveal、その下に古い(ライト)スナップショットが表示され続けます。
方向反転
ダーク→ライトでは、新しい擬似要素を成長させる代わりに 古い 擬似要素を縮小アニメーションさせます。あるいは常に new を成長させてもよく、ユーザーの知覚はどちらの方向でも同じなので両方向で同様に機能します。
Reduced motion
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.documentElement.classList.toggle('dark');
return;
}
このプリファレンスを設定したユーザーにはアニメーションを完全にスキップします。
ユースケース
ブランドの一部としての視覚的演出を持つマーケティングサイトやダッシュボードのテーマスイッチャー。実例: Chrome 111+ DevRel デモ、複数の CSS Day トーク、Linear の設定パネルなど。