前端动画:如何用CSS View Transitions实现页面过渡
结论:用 CSS View Transitions 实现页面过渡,SPA 场景只需一次 document.startViewTransition() 调用加不到 5 行 CSS;多页面(MPA)场景只需在两个页面的样式表里各写一行 @view-transition { navigation: auto; }。浏览器原生执行,不需要引入 Framer Motion、GSAP 这类动画库。
View Transitions 和普通 CSS transition 有什么区别?
结论:普通 transition 只能对「同一个元素、同一个属性」做变化动画;View Transitions 是浏览器在 DOM 更新前后各拍一张快照,让你对这两张快照做动画,能跨越元素的消失、新增和换位。
调用 document.startViewTransition(callback) 时,浏览器按固定顺序执行四步:捕获当前页面快照(旧快照)→ 执行 callback 更新 DOM → 捕获新页面快照(新快照)→ 把两张快照分别渲染进 ::view-transition-old 和 ::view-transition-new 伪元素做交叉淡化,默认时长 0.25 秒。
真正参与动画的是快照位图,不是 DOM 节点。所以「列表头像点击后放大成详情页大图」这种结构完全变化的场景,用 View Transitions 几行代码就能做出来。
浏览器支持:Chrome 111+、Edge 111+ 支持同文档过渡;跨文档过渡需要 Chrome 126+、Safari 18.2+。不支持时 document.startViewTransition 是 undefined,必须走降级分支。
SPA 页面过渡怎么做?
结论:三步——给需要「配对」的元素加唯一名字、用 API 包住 DOM 更新、写自定义动画 CSS。
第一步,给过渡前后的两个元素设置相同的 view-transition-name:
.thumb { view-transition-name: hero; }
.hero-img { view-transition-name: hero; }
view-transition-name 是给元素贴的「配对标签」,浏览器靠它在两张快照里找到同一个元素并计算位移。
第二步,包住状态更新:
function navigate(update) {
if (!document.startViewTransition) return update();
document.startViewTransition(update);
}
返回对象有三个 Promise:updateCallbackDone(DOM 更新完成)、ready(快照捕获完成)、finished(动画结束)。需要在动画结束后做清理时用 await transition.finished。
第三步,改时长:
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 300ms; }
多页面站点的页面跳转怎么加过渡?
结论:在两个页面各写一行 @view-transition { navigation: auto; },再让跨页元素同名即可。
/* index.html 和 detail.html 都要写 */
@view-transition { navigation: auto; }
跨页过渡的配对规则和 SPA 一致:列表页的头像和详情页的大图共用 hero 这个名字,浏览器就能在跳转时做放大动画。如果元素的 DOM 在页面加载后才渲染出来,可以在 pagereveal 事件里动态设置名字:
window.addEventListener('pagereveal', (e) => {
const img = document.querySelector('.hero-img');
if (img) img.style.viewTransitionName = 'hero';
});
自定义动画写在哪?
结论:写在一组固定命名的伪元素上,整棵树是 ::view-transition → ::view-transition-group(name) → ::view-transition-image-pair(name) → ::view-transition-old(name) / ::view-transition-new(name)。
最常用的做法是给 old 和 new 分别写 keyframes:
::view-transition-old(card) {
animation: 300ms ease-out both fade-out;
}
::view-transition-new(card) {
animation: 300ms ease-out both slide-in;
}
@keyframes fade-out { to { opacity: 0; } }
@keyframes slide-in {
from { opacity: 0; transform: translateY(12px); }
}
Chrome 125+ 还提供 view-transition-class,可以让多个元素共用同一套动画规则,不必逐个写选择器。
有哪些必须避开的坑?
结论:最大的坑是 view-transition-name 在同一时刻必须全局唯一,重复会导致这次过渡被整体跳过、页面直接跳变。
列表页循环渲染时尤其容易踩:如果每个 li 都写 view-transition-name: hero,浏览器发现同名元素超过一个,就会放弃动画。正确做法是只给被点击的那一项动态赋值:
el.style.viewTransitionName = 'hero';
await document.startViewTransition(update).finished;
el.style.viewTransitionName = '';
另外三点:快照是位图,动画期间字体或图片加载变化不会被捕捉;要加无障碍保护,用 @media (prefers-reduced-motion: reduce) 把 ::view-transition-group(*) 等伪元素的 animation 置为 none;所有调用都要做特性检测,不支持时直接执行 DOM 更新,功能不受影响。
总结:SPA 场景记住「命名 + startViewTransition + 伪元素动画」这三件事,MPA 场景记住 @view-transition 一行声明,再守住名字唯一这条红线,20 行以内的代码就能拿到原生的页面过渡效果。
原文链接:https://www.gj0.com/thread-484.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。