ヒーロートランジション
共有要素の連続性でコンテキストを維持するトランジション
ヒーロートランジション
ヒーロートランジションは、2つの画面間で共有される要素が自然に移動し変形する**連続性トランジション(Continuity Transition)**です。要素が1つの画面から別の画面へ「旅」し、ユーザーに強力な視覚的つながりとコンテキストを提供します。
デモ
Loading demo...
UXの原則
いつ使用するか?
ヒーロートランジションは連続性とコンテキストの維持が重要な場合に使用されます。
コンテンツの関係別適合性
콘텐츠 관계 | 적합성 | 설명 |
---|---|---|
関連のないコンテンツ | ❌ | 共有要素がない場合は不適切 |
兄弟関係 | ⚠️ | ギャラリーなど特定の状況で効果的 |
階層関係 | ✅ | リスト→詳細のように同じオブジェクトの異なるビューに最適 |
主な使用ケース
- ギャラリー → フルスクリーン: 画像が拡大して詳細ビューへ遷移
- 製品リスト → 詳細: 製品カードが詳細ページに拡張
- プロフィール → 編集: プロフィール写真を維持したまま編集モードへ遷移
- メディアプレーヤー: ミニプレーヤーからフルプレーヤーへ拡張
なぜこのように動作するのか?
- 視覚的連続性: 要素の移動を追跡して方向感覚を維持
- 空間的関係の表現: 小さいものから大きいものへ、要約から詳細へ
- 認知負荷の最小化: 突然の変化ではなく自然な変形
- 興味と楽しさ: ダイナミックなモーションでユーザーエンゲージメントを向上
モーションデザイン
ヒーロー要素の旅:
1. 出発点 → 位置、サイズ、形状を記録
2. 変形中 → スムーズな補間アニメーション
3. 到着点 → 新しい位置とサイズに定着
他の要素:
- 出ていくページ: フェードアウト
- 入ってくるページ: ヒーロー要素が定着した後フェードイン
基本的な使い方
1. トランジションの設定
import { Ssgoi } from '@ssgoi/react';
import { hero } from '@ssgoi/react/view-transitions';
const config = {
transitions: [
{
from: '/gallery',
to: '/gallery/*',
transition: hero(),
symmetric: true
}
]
};
export default function App() {
return (
<Ssgoi config={config}>
{/* アプリの内容 */}
</Ssgoi>
);
}
2. 要素のマーキング
共有される要素にdata-hero-key
属性を追加:
// リストページ
function ProductList() {
return (
<div className="grid">
{products.map(product => (
<Link key={product.id} to={`/product/${product.id}`}>
<img
data-hero-key={`product-${product.id}`}
src={product.image}
alt={product.name}
/>
<h3>{product.name}</h3>
</Link>
))}
</div>
);
}
// 詳細ページ
function ProductDetail({ product }) {
return (
<div>
<img
data-hero-key={`product-${product.id}`}
src={product.image}
alt={product.name}
className="w-full"
/>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
実践的な活用例
1. 画像ギャラリー
サムネイルからフルスクリーンへの拡張:
// ギャラリーグリッド
function Gallery() {
return (
<div className="grid grid-cols-3 gap-4">
{images.map(img => (
<Link to={`/photo/${img.id}`} key={img.id}>
<div
data-hero-key={`photo-${img.id}`}
className="aspect-square overflow-hidden"
>
<img src={img.thumb} className="object-cover w-full h-full" />
</div>
</Link>
))}
</div>
);
}
// フルスクリーンビュー
function PhotoView({ id }) {
return (
<div
data-hero-key={`photo-${id}`}
className="fixed inset-0 bg-black"
>
<img src={image.full} className="w-full h-full object-contain" />
</div>
);
}
2. カード拡張
小さなカードからフルスクリーン詳細へ:
// カードリスト
function CardGrid() {
return (
<div className="grid gap-6">
{items.map(item => (
<article
key={item.id}
data-hero-key={`card-${item.id}`}
onClick={() => navigate(`/item/${item.id}`)}
className="bg-white rounded-lg shadow-lg p-4 cursor-pointer"
>
<h3>{item.title}</h3>
<p>{item.summary}</p>
</article>
))}
</div>
);
}
// 拡張された詳細
function ItemDetail({ item }) {
return (
<article
data-hero-key={`card-${item.id}`}
className="min-h-screen bg-white p-8"
>
<h1 className="text-4xl">{item.title}</h1>
<p className="text-lg">{item.fullContent}</p>
</article>
);
}
3. 複数のヒーロー要素
複数の要素が同時に遷移:
// プロフィールカード
function ProfileCard({ user }) {
return (
<div onClick={() => navigate(`/profile/${user.id}`)}>
<img
data-hero-key={`avatar-${user.id}`}
src={user.avatar}
className="w-16 h-16 rounded-full"
/>
<h3 data-hero-key={`name-${user.id}`}>
{user.name}
</h3>
</div>
);
}
// プロフィール詳細
function ProfileDetail({ user }) {
return (
<div>
<img
data-hero-key={`avatar-${user.id}`}
src={user.avatar}
className="w-32 h-32 rounded-full"
/>
<h1 data-hero-key={`name-${user.id}`}>
{user.name}
</h1>
</div>
);
}
注意事項
ヒーローキーのルール
- 各ページで
data-hero-key
は一意である必要があります - キーは文字列で、動的に生成可能
- 同じキーを持つ要素同士のみが接続されます
パフォーマンスの考慮事項
- ヒーロー要素はDOMでクローンされてアニメーションされます
- ヒーロー要素が多すぎるとパフォーマンスに影響します
- 画像は事前にロードしてちらつきを防ぎます
アクセシビリティ
- モーション削減設定時は即座に遷移
- フォーカス管理によるキーボードナビゲーションのサポート
- スクリーンリーダーは通常のページ遷移として認識
ベストプラクティス
✅ DO
- 視覚的に重要な要素に使用
- 同じオブジェクトの異なる表現に適用
- 自然なサイズ/位置変化を維持
- ユーザーの視線を導く際に活用
❌ DON'T
- あまりに多くの要素に同時適用しない
- 関連のない要素同士を接続しない
- 極端なサイズ変化は避ける(10倍以上)
- テキストコンテンツには慎重に使用