Hero 过渡
通过共享元素的连续性保持上下文的过渡
Hero 过渡
Hero 过渡是两个屏幕之间共享元素自然移动和变形的连续性过渡(Continuity Transition)。元素从一个屏幕"旅行"到另一个屏幕,为用户提供强大的视觉连接和上下文。
演示
Loading demo...
UX 原则
何时使用?
Hero 过渡用于连续性和上下文保持重要的场景。
内容关系适用性
콘텐츠 관계 | 적합성 | 설명 |
---|---|---|
无关内容 | ❌ | 没有共享元素时不适用 |
兄弟关系 | ⚠️ | 在画廊等特定情况下有效 |
层级关系 | ✅ | 最适合列表→详情等同一对象的不同视图 |
主要用例
- 画廊 → 全屏:图片放大并切换到详细视图
- 产品列表 → 详情:产品卡片扩展为详情页面
- 个人资料 → 编辑:保持个人资料照片并切换到编辑模式
- 媒体播放器:从迷你播放器扩展到完整播放器
为什么这样工作?
- 视觉连续性:跟踪元素移动以保持方向感
- 表达空间关系:从小到大,从摘要到详细
- 最小化认知负担:自然变形代替突然变化
- 兴趣和愉悦:动态动效增加用户参与度
动效设计
Hero 元素的旅程:
1. 起点 → 记录位置、大小、形状
2. 变形中 → 平滑插值动画
3. 终点 → 定位到新位置和大小
其他元素:
- 离开页面:淡出
- 进入页面:Hero 元素定位后淡入
基本用法
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. 多个 Hero 元素
多个元素同时过渡:
// 个人资料卡片
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>
);
}
注意事项
Hero 键规则
- 每个页面中
data-hero-key
必须唯一 - 键是字符串,可以动态生成
- 只有具有相同键的元素才会连接
性能考虑
- Hero 元素在 DOM 中被克隆并动画化
- 过多的 Hero 元素会影响性能
- 预加载图片以防止闪烁
无障碍性
- 减少动画设置时立即过渡
- 焦点管理支持键盘导航
- 屏幕阅读器将其识别为普通页面过渡
最佳实践
✅ 应该
- 用于视觉上重要的元素
- 应用于同一对象的不同表示
- 保持自然的大小/位置变化
- 用于引导用户视线
❌ 不应该
- 不要同时应用于太多元素
- 不要连接无关元素
- 避免极端的大小变化(超过 10 倍)
- 对文本内容谨慎使用