히어로 전환
공유 요소의 연속성으로 컨텍스트를 유지하는 전환
히어로 전환
히어로 전환은 두 화면 간 공유되는 요소가 자연스럽게 이동하고 변형되는 **연속성 전환(Continuity Transition)**입니다. 요소가 한 화면에서 다른 화면으로 "여행"하며, 사용자에게 강력한 시각적 연결성과 컨텍스트를 제공합니다.
데모
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배 이상)
- 텍스트 콘텐츠에는 신중하게 사용