当前位置: 首页 > news >正文

CSS视图过渡(View Transitions)完全指南:打造流畅页面切换

引言

CSS视图过渡(View Transitions)是一项革命性的Web特性,它允许开发者在页面状态变化时创建流畅的过渡动画。无论是单页应用的路由切换,还是元素的增删改,视图过渡都能提供原生级别的动画效果。

一、视图过渡基础概念

1.1 什么是视图过渡

视图过渡是一种浏览器原生的动画机制,它在DOM状态变化时自动创建过渡效果。

async function navigateTo(page) { // 启动视图过渡 const transition = await document.startViewTransition(() => { // 更新DOM document.body.innerHTML = getPageContent(page); }); // 过渡完成回调 transition.finished.then(() => { console.log('Transition complete'); }); }

1.2 核心优势

特性传统方式视图过渡
实现复杂度
性能依赖JS动画原生GPU加速
跨页面困难原生支持
状态管理手动自动

二、基本用法

2.1 触发视图过渡

// 方式1:基本用法 document.startViewTransition(() => { // DOM更新操作 updatePage(); }); // 方式2:异步更新 document.startViewTransition(async () => { const data = await fetchData(); renderPage(data); }); // 方式3:带配置 const transition = document.startViewTransition(() => { updatePage(); }, { navigation: true });

2.2 过渡对象属性

const transition = document.startViewTransition(() => { updatePage(); }); // 过渡状态 console.log(transition.startTime); // 开始时间 console.log(transition.active); // 是否活跃 console.log(transition.finished); // Promise,过渡完成 // 事件监听 transition.addEventListener('start', () => { console.log('Transition started'); }); transition.addEventListener('finish', () => { console.log('Transition finished'); }); transition.addEventListener('cancel', () => { console.log('Transition cancelled'); }); transition.addEventListener('error', () => { console.log('Transition error'); });

三、CSS视图过渡样式

3.1 基础样式

/* 视图过渡容器 */ ::view-transition { /* 过渡时长 */ --view-transition-duration: 0.5s; /* 过渡时序函数 */ --view-transition-timing-function: ease-in-out; /* 是否允许滚动 */ --view-transition-allow-scroll: auto; } /* 旧视图 */ ::view-transition-old(root) { animation: fade-out 0.5s ease-in-out; } /* 新视图 */ ::view-transition-new(root) { animation: fade-in 0.5s ease-in-out; } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

3.2 命名过渡

/* 为特定元素定义过渡 */ ::view-transition-old(header) { animation: slide-left 0.4s ease-out; } ::view-transition-new(header) { animation: slide-right 0.4s ease-out; } @keyframes slide-left { from { transform: translateX(0); } to { transform: translateX(-100%); } } @keyframes slide-right { from { transform: translateX(100%); } to { transform: translateX(0); } }

3.3 自定义过渡名称

<div class="card" style="view-transition-name: card-1"> <!-- 内容 --> </div>
::view-transition-old(card-1) { animation: scale-down 0.3s ease-out; } ::view-transition-new(card-1) { animation: scale-up 0.3s ease-out; } @keyframes scale-down { from { transform: scale(1); opacity: 1; } to { transform: scale(0.8); opacity: 0; } } @keyframes scale-up { from { transform: scale(1.2); opacity: 0; } to { transform: scale(1); opacity: 1; } }

四、高级特性

4.1 跨文档视图过渡

// 在导航前准备 navigation.addEventListener('navigate', (event) => { event.intercept({ async handler() { const response = await fetch(event.destination.url); const html = await response.text(); // 创建视图过渡 await document.startViewTransition(() => { document.documentElement.innerHTML = html; }); } }); });

4.2 延迟过渡

const transition = document.startViewTransition(() => { // 异步更新 }, { delay: 100 // 延迟100ms开始 });

4.3 过渡协调

/* 协调多个元素的过渡 */ ::view-transition-group(card) { animation-duration: 0.4s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } ::view-transition-old(card) { animation-name: card-exit; } ::view-transition-new(card) { animation-name: card-enter; }

4.4 动态过渡

function animateTransition(element, newContent) { // 设置过渡名称 element.style.viewTransitionName = 'dynamic-element'; // 创建过渡 document.startViewTransition(() => { element.innerHTML = newContent; }).finished.then(() => { // 清除过渡名称 element.style.viewTransitionName = 'none'; }); }

五、实战案例

5.1 页面淡入淡出

::view-transition-old(root) { animation: fade-out 0.5s ease-out; } ::view-transition-new(root) { animation: fade-in 0.5s ease-out; } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
async function navigate(url) { await document.startViewTransition(() => { window.history.pushState({}, '', url); loadPage(url); }); }

5.2 卡片翻转动画

<div class="card" id="card1">卡片1</div> <div class="card" id="card2" hidden>卡片2</div>
::view-transition-old(card) { animation: flip-out 0.6s ease-in-out; } ::view-transition-new(card) { animation: flip-in 0.6s ease-in-out; } @keyframes flip-out { 0% { transform: perspective(400px) rotateY(0); } 100% { transform: perspective(400px) rotateY(90deg); } } @keyframes flip-in { 0% { transform: perspective(400px) rotateY(-90deg); } 100% { transform: perspective(400px) rotateY(0); } }
function switchCards() { const card1 = document.getElementById('card1'); const card2 = document.getElementById('card2'); card1.style.viewTransitionName = 'card'; card2.style.viewTransitionName = 'card'; document.startViewTransition(() => { card1.hidden = true; card2.hidden = false; }).finished.then(() => { card1.style.viewTransitionName = 'none'; card2.style.viewTransitionName = 'none'; }); }

5.3 列表项动画

::view-transition-group(list-item) { animation-duration: 0.3s; } ::view-transition-old(list-item) { animation: slide-up-out 0.3s ease-out; } ::view-transition-new(list-item) { animation: slide-up-in 0.3s ease-out; } @keyframes slide-up-out { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-20px); opacity: 0; } } @keyframes slide-up-in { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }

六、与JavaScript框架集成

6.1 React集成

import { useState, useCallback } from 'react'; function App() { const [page, setPage] = useState('home'); const navigate = useCallback(async (newPage) => { await document.startViewTransition(() => { setPage(newPage); }); }, []); return ( <div> <nav> <button onClick={() => navigate('home')}>首页</button> <button onClick={() => navigate('about')}>关于</button> <button onClick={() => navigate('contact')}>联系</button> </nav> <main> {page === 'home' && <HomePage />} {page === 'about' && <AboutPage />} {page === 'contact' && <ContactPage />} </main> </div> ); }

6.2 Vue集成

<script setup> import { ref } from 'vue'; const currentPage = ref('home'); async function navigate(page) { await document.startViewTransition(() => { currentPage.value = page; }); } </script> <template> <div> <nav> <button @click="navigate('home')">首页</button> <button @click="navigate('about')">关于</button> <button @click="navigate('contact')">联系</button> </nav> <main> <HomePage v-if="currentPage === 'home'" /> <AboutPage v-else-if="currentPage === 'about'" /> <ContactPage v-else /> </main> </div> </template>

七、浏览器兼容性

7.1 当前支持情况

浏览器版本支持状态
Chrome111+支持
Firefox120+支持
Safari16.4+支持
Edge111+支持

7.2 降级方案

async function startTransition(callback) { if (document.startViewTransition) { return document.startViewTransition(callback); } else { // 降级到普通更新 callback(); return { finished: Promise.resolve() }; } }

八、最佳实践

8.1 性能优化

/* 使用will-change提示浏览器 */ .element { will-change: transform, opacity; }

8.2 避免复杂动画

/* 避免使用昂贵的属性 */ /* 避免: box-shadow, filter, width, height */ /* 推荐: transform, opacity */

8.3 响应式过渡

@media (prefers-reduced-motion: reduce) { ::view-transition { --view-transition-duration: 0s; } }

8.4 可访问性

// 确保过渡期间内容可访问 transition.addEventListener('start', () => { document.body.setAttribute('aria-busy', 'true'); }); transition.addEventListener('finish', () => { document.body.setAttribute('aria-busy', 'false'); });

九、总结

CSS视图过渡是一项强大的新特性,它简化了页面过渡动画的实现。通过视图过渡,我们可以:

  1. 创建流畅的页面切换效果
  2. 实现元素级别的过渡动画
  3. 跨文档的无缝导航
  4. 减少JavaScript依赖

关键要点:

  • 使用document.startViewTransition()触发过渡
  • 通过CSS::view-transition-old::view-transition-new定义动画
  • 使用view-transition-name属性为元素命名
  • 考虑浏览器兼容性和降级方案

掌握视图过渡,将使你的Web应用更加流畅和专业。

http://www.cnnetsun.cn/news/2485337.html

相关文章:

  • Flutter应用架构完全指南:从MVC到Clean Architecture
  • 避开这些坑!SAP EWM盘点配置中的3个常见错误与最佳实践
  • 德诚康复|河南大型精工假肢康复连锁机构
  • 基于机器视觉的工业产品型号识别与报警系统实现
  • Tokio运行时Worker挂死原理剖析与防御实践
  • 从 WebGPT 到 WebAgent:搜索增强型智能体演进
  • ARM Cortex-A53缓存策略实战:手把手教你配置MMU页表优化程序性能
  • AI写论文必备攻略!4款AI论文写作工具,开启高效论文创作之旅!
  • MATLAB R2026a安装教程
  • 从零开始学习AI Agent的实战路线图
  • 告别Gym,拥抱Gymnasium:从Atari游戏安装到代码迁移的完整避坑指南
  • AI Agent 输出格式的隐形瓶颈
  • VL53L0X激光测距模块在STM32上的应用:除了测距,还能玩出什么花样?
  • 用Field II和MATLAB搞定超声波声场仿真:从理论推导到代码实战(附源码)
  • 读研读博,教你3招搞定文献调研
  • HarmonyOS 图片缩放没想象中简单——detailEnhance 四档质量深度解析
  • 【DeepSeek API接入实战指南】:20年AI架构师亲授5大避坑要点与3分钟快速调通秘籍
  • 别再只盯着Encoder模式了!STM32F4通用IO口+外部中断搞定EC11旋转编码器(附代码)
  • 基于STM32F105系列使用CAN总线实现双机通信代码
  • 鸿蒙支付模块构建:快捷充值选项与缴费记录的时间线设计
  • VSCode Mermaid Preview:面向技术团队的实时图表协作解决方案
  • [明道云实战] 流程一多就开始乱,怎样把明道云工作流整理成可维护的工程系统?
  • 深度测评2026年日本工程塑料厂家最佳代理服务排行榜,解锁高精尖材料新选择
  • 告别Keil!在VSCode里用PlatformIO+CubeMX+HAL库玩转STM32(保姆级配置流程)
  • 从CUDA_VISIBLE_DEVICES到Docker:聊聊GPU资源隔离的几种‘姿势’
  • MiniMax-M2.7-W8A8 双机 DP=2 部署
  • 树莓派摄像头detected=0?别急着重装系统,先检查这个新手常插错的接口
  • 考前终极口诀合集,30秒过一遍
  • 错过申报期等于白干:政策信息平台的时效性保障技术方案
  • 从Multisim仿真到理论验证:一个实际案例带你吃透结点电压法的‘自导’与‘互导’