핀터레스트 전환
카드가 화면을 가득 채우며 확장되는 몰입형 전환
핀터레스트 전환
핀터레스트 전환은 갤러리의 작은 아이템이 전체 화면으로 확장되는 **확장형 전환(Expansion Transition)**입니다. 사용자가 선택한 콘텐츠가 화면을 가득 채우며 확대되어, 강력한 몰입감과 시각적 연속성을 제공합니다.
데모
Loading demo...
UX 원칙
언제 사용하나요?
핀터레스트 전환은 시각적 콘텐츠의 확대 탐색 시 사용됩니다.
콘텐츠 관계별 적합성
콘텐츠 관계 | 적합성 | 설명 |
---|---|---|
관련 없는 콘텐츠 | ❌ | 확장할 연결점이 없어 부적합 |
형제 관계 | ⚠️ | 갤러리 내 아이템 간 이동 시 고려 가능 |
계층 관계 | ✅ | 썸네일→풀뷰처럼 같은 콘텐츠의 확대 뷰에 최적 |
주요 사용 케이스
- 이미지 갤러리: 썸네일에서 고해상도 이미지로 확대
- 제품 카탈로그: 제품 그리드에서 상세 정보로 확장
- 미디어 브라우징: 비디오 썸네일에서 플레이어로 전환
- 포트폴리오: 작품 목록에서 전체 화면 프레젠테이션으로
왜 이렇게 동작하나요?
- 공간 확장 메타포: 작은 창문을 통해 큰 세계로 들어가는 느낌
- 자연스러운 초점 이동: 사용자의 시선이 자연스럽게 확장되는 요소를 따라감
- 컨텍스트 보존: 어디서 왔는지 명확하여 방향감각 유지
- 드라마틱한 효과: 확장 애니메이션이 콘텐츠의 중요성 강조
모션 디자인
확장 과정:
1. 선택된 카드의 위치와 크기 캡처
2. 카드가 화면 중앙으로 이동하며 확대
3. 동시에 배경 페이드와 스케일 변화
4. 최종적으로 전체 화면 채움
축소 과정:
1. 상세 화면이 축소되며 원래 위치로
2. 배경이 다시 나타나며 갤러리 복원
3. 자연스러운 스프링 애니메이션으로 정착
기본 사용법
1. 전환 설정
import { Ssgoi } from '@ssgoi/react';
import { pinterest } from '@ssgoi/react/view-transitions';
const config = {
transitions: [
{
from: '/gallery',
to: '/gallery/*',
transition: pinterest(),
symmetric: true
}
]
};
export default function App() {
return (
<Ssgoi config={config}>
{/* 앱 내용 */}
</Ssgoi>
);
}
2. 요소 마킹
갤러리와 상세 페이지에 각각 다른 data 속성 사용:
// 갤러리 페이지
function Gallery() {
return (
<div className="masonry-grid">
{items.map(item => (
<Link
key={item.id}
to={`/gallery/${item.id}`}
data-pinterest-gallery-key={item.id}
className="gallery-item"
>
<img src={item.image} alt={item.title} />
<h3>{item.title}</h3>
</Link>
))}
</div>
);
}
// 상세 페이지
function ItemDetail({ item }) {
return (
<div
data-pinterest-detail-key={item.id}
className="detail-container"
>
<button onClick={() => navigate('/gallery')}>
✕ Close
</button>
<img src={item.image} alt={item.title} />
<article>
<h1>{item.title}</h1>
<p>{item.description}</p>
</article>
</div>
);
}
실전 활용 예시
1. 메이슨리 갤러리
Pinterest 스타일 메이슨리 레이아웃:
// 메이슨리 그리드
function MasonryGallery() {
return (
<div className="columns-2 md:columns-3 lg:columns-4 gap-4">
{images.map((img, index) => (
<div
key={img.id}
data-pinterest-gallery-key={img.id}
onClick={() => navigate(`/image/${img.id}`)}
className="break-inside-avoid mb-4 cursor-pointer
hover:scale-105 transition-transform"
>
<img
src={img.url}
alt={img.title}
className="w-full rounded-lg"
/>
<div className="p-2">
<p className="text-sm font-medium">{img.title}</p>
<p className="text-xs text-gray-500">{img.author}</p>
</div>
</div>
))}
</div>
);
}
// 확장된 뷰
function ImageView({ image }) {
return (
<div
data-pinterest-detail-key={image.id}
className="fixed inset-0 bg-white z-50 overflow-auto"
>
<div className="max-w-4xl mx-auto p-4">
<img
src={image.url}
alt={image.title}
className="w-full rounded-lg shadow-xl"
/>
<div className="mt-4">
<h1 className="text-2xl font-bold">{image.title}</h1>
<p className="text-gray-600 mt-2">{image.description}</p>
</div>
</div>
</div>
);
}
2. 제품 카탈로그
제품 카드가 상세 페이지로 확장:
// 제품 그리드
function ProductGrid() {
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{products.map(product => (
<article
key={product.id}
data-pinterest-gallery-key={product.id}
onClick={() => navigate(`/product/${product.id}`)}
className="bg-white rounded-xl shadow-lg overflow-hidden
cursor-pointer hover:shadow-xl transition-shadow"
>
<img
src={product.image}
alt={product.name}
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h3 className="font-semibold">{product.name}</h3>
<p className="text-lg font-bold mt-2">${product.price}</p>
</div>
</article>
))}
</div>
);
}
// 제품 상세
function ProductDetail({ product }) {
return (
<div
data-pinterest-detail-key={product.id}
className="min-h-screen bg-white"
>
<div className="grid md:grid-cols-2 gap-8 p-8">
<img
src={product.image}
alt={product.name}
className="w-full rounded-lg"
/>
<div>
<h1 className="text-3xl font-bold">{product.name}</h1>
<p className="text-2xl font-bold mt-4">${product.price}</p>
<p className="mt-4 text-gray-600">{product.description}</p>
<button className="mt-6 bg-black text-white px-8 py-3 rounded-lg">
Add to Cart
</button>
</div>
</div>
</div>
);
}
커스터마이징
스프링 설정
애니메이션의 탄성과 속도 조절:
pinterest({
spring: {
stiffness: 200, // 낮을수록 부드러움
damping: 25 // 높을수록 빠른 정착
}
})
주의사항
키 속성 구분
- 갤러리:
data-pinterest-gallery-key
- 상세:
data-pinterest-detail-key
- 반드시 다른 속성명 사용 (히어로와 차별화)
레이아웃 고려사항
- 갤러리 아이템은 position이 static이어야 함
- 상세 페이지는 충분한 여백 필요
- 메이슨리 레이아웃과 특히 잘 어울림
접근성
- ESC 키로 상세 페이지 닫기 지원
- 포커스 트랩으로 키보드 네비게이션 개선
- 모션 감소 설정 시 즉시 전환
모범 사례
✅ DO
- 이미지 중심 콘텐츠에 사용
- 갤러리나 카탈로그 형태에 적용
- 닫기 버튼을 명확하게 제공
- 모바일에서도 잘 작동하도록 반응형 디자인
❌ DON'T
- 텍스트 중심 콘텐츠에는 부적합
- 너무 많은 요소가 동시에 확장되지 않도록
- 상세 페이지 로딩이 느린 경우 사용 자제
- 복잡한 레이아웃에서는 예상치 못한 동작 가능