CSS3 最新特性 - CSS View Transitions

章节标题

CSS View Transitions 详解

核心知识点讲解

1. CSS View Transitions 的基本概念

CSS View Transitions(视图过渡)是 CSS View Transitions Module Level 1 规范中的一个重要特性,它允许开发者在不同页面状态之间创建平滑的过渡动画。通过捕获页面元素的前后状态并在它们之间创建过渡效果,View Transitions 为用户提供了更加流畅、直观的界面体验。

核心思想

  • 捕获阶段(Capture Phase):捕获页面元素在状态变化前后的视觉表现
  • 过渡阶段(Transition Phase):在捕获的前后状态之间创建平滑的过渡动画
  • 完成阶段(Completion Phase):过渡完成后,显示新的页面状态

优势

  • 提供更加流畅、专业的页面状态过渡效果
  • 减少用户在页面切换时的认知负荷
  • 支持单页应用和多页应用的页面过渡
  • 简化复杂动画的实现,减少 JavaScript 依赖
  • 支持细粒度的元素过渡控制

2. CSS View Transitions 的主要 API

2.1 document.startViewTransition() 方法

document.startViewTransition() 是启动视图过渡的主要方法,它接受一个回调函数,该回调函数用于执行导致页面状态变化的操作。

// 基本语法
const transition = document.startViewTransition(callback);

// 示例:切换主题
function toggleTheme() {
  document.startViewTransition(() => {
    document.documentElement.classList.toggle('dark-theme');
  });
}

// 示例:切换页面内容
function navigateTo(pageId) {
  document.startViewTransition(() => {
    // 隐藏所有页面
    document.querySelectorAll('.page').forEach(page => {
      page.classList.add('hidden');
    });
    // 显示目标页面
    document.getElementById(pageId).classList.remove('hidden');
  });
}

返回值

  • 返回一个 ViewTransition 对象,包含过渡的状态和方法
  • 可以使用 transition.finished Promise 来监听过渡完成

2.2 ViewTransition 对象

ViewTransition 对象提供了控制和监听过渡的方法和属性:

// ViewTransition 对象的属性和方法
const transition = document.startViewTransition(callback);

// 过渡完成的 Promise
transition.finished.then(() => {
  console.log('Transition completed');
});

// 跳过过渡
transition.skipTransition();

// 过渡的当前状态
console.log(transition.ready); // Promise,过渡准备就绪时解析
console.log(transition.domUpdates); // Promise,DOM 更新完成时解析

2.3 CSS 伪元素

View Transitions 使用一系列 CSS 伪元素来控制过渡效果:

伪元素 描述
::view-transition 过渡的根容器
::view-transition-group(root) 根元素的过渡组
::view-transition-image-pair(root) 根元素的前后状态图像对
::view-transition-old(root) 根元素的旧状态图像
::view-transition-new(root) 根元素的新状态图像
::view-transition-group(name) 命名元素的过渡组
::view-transition-image-pair(name) 命名元素的前后状态图像对
::view-transition-old(name) 命名元素的旧状态图像
::view-transition-new(name) 命名元素的新状态图像

示例

/* 自定义根元素过渡 */
::view-transition-old(root) {
  animation: fade-out 0.5s ease-in-out;
}

::view-transition-new(root) {
  animation: fade-in 0.5s ease-in-out;
}

/* 自定义命名元素过渡 */
::view-transition-old(header) {
  animation: slide-out 0.5s ease-in-out;
}

::view-transition-new(header) {
  animation: slide-in 0.5s ease-in-out;
}

/* 定义动画 */
@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slide-out {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

@keyframes slide-in {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}

2.4 view-transition-name 属性

view-transition-name 属性用于为元素指定一个过渡名称,以便在过渡过程中单独控制该元素的动画效果。

/* 基本语法 */
.view-transition-name: none | <custom-ident>;

/* 示例 */
/* 为 header 元素指定过渡名称 */
header {
  view-transition-name: header;
}

/* 为 logo 元素指定过渡名称 */
.logo {
  view-transition-name: logo;
}

/* 禁用元素的过渡 */
.sidebar {
  view-transition-name: none;
}

注意事项

  • 每个过渡名称在文档中必须是唯一的
  • 使用 none 可以禁用元素的过渡效果
  • 只有指定了过渡名称的元素才会在过渡过程中被单独处理

3. CSS View Transitions 的语法和用法

基本用法示例

// 1. 基本页面切换
function switchContent() {
  document.startViewTransition(() => {
    document.getElementById('content1').classList.add('hidden');
    document.getElementById('content2').classList.remove('hidden');
  });
}

// 2. 监听过渡完成
async function navigateWithTransition() {
  const transition = document.startViewTransition(() => {
    // 执行页面切换操作
    updatePageContent();
  });
  
  // 等待过渡完成
  await transition.finished;
  console.log('Navigation completed with transition');
}

// 3. 结合 CSS 自定义过渡
function toggleThemeWithTransition() {
  document.startViewTransition(() => {
    document.documentElement.classList.toggle('dark');
  });
}

CSS 配置示例

/* 1. 基本过渡样式 */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.6s;
  animation-timing-function: ease-in-out;
}

::view-transition-old(root) {
  animation-name: fade-out;
}

::view-transition-new(root) {
  animation-name: fade-in;
}

/* 2. 自定义元素过渡 */
.header {
  view-transition-name: header;
}

.logo {
  view-transition-name: logo;
}

::view-transition-old(header) {
  animation: slide-out-left 0.5s ease-in-out;
}

::view-transition-new(header) {
  animation: slide-in-right 0.5s ease-in-out;
}

::view-transition-old(logo),
::view-transition-new(logo) {
  animation-duration: 0.8s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

::view-transition-old(logo) {
  animation-name: scale-out;
}

::view-transition-new(logo) {
  animation-name: scale-in;
}

/* 3. 定义动画 */
@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slide-out-left {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in-right {
  from { transform: translateX(100%); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

@keyframes scale-out {
  from { transform: scale(1); opacity: 1; }
  to { transform: scale(0.5); opacity: 0; }
}

@keyframes scale-in {
  from { transform: scale(1.5); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* 4. 响应式过渡 */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

4. CSS View Transitions 的浏览器兼容性

支持情况

  • Chrome 111+ (自 2023 年 3 月起)
  • Edge 111+ (自 2023 年 3 月起)
  • Safari 16.4+ (自 2023 年 3 月起,部分支持)
  • Firefox:正在开发中

兼容性处理

对于不支持 View Transitions 的浏览器,可以使用特性检测并提供降级方案:

// 检测浏览器是否支持 View Transitions
function supportsViewTransitions() {
  return 'startViewTransition' in document;
}

// 使用 View Transitions 或降级方案
function navigateTo(pageId) {
  if (supportsViewTransitions()) {
    // 使用 View Transitions
    document.startViewTransition(() => {
      updatePage(pageId);
    });
  } else {
    // 降级方案:直接更新页面
    updatePage(pageId);
  }
}

使用 CSS @supports 检测

/* 基础样式 */
.content {
  transition: opacity 0.3s ease;
}

/* 支持 View Transitions 的浏览器 */
@supports (view-transition-name: none) {
  .content {
    transition: none;
  }
  
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: fade 0.6s ease-in-out;
  }
  
  @keyframes fade {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

实用案例分析

案例一:单页应用导航

需求:创建一个单页应用,使用 View Transitions 实现页面之间的平滑过渡效果。

HTML 结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>单页应用导航示例</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <!-- 导航栏 -->
  <nav class="navbar">
    <ul>
      <li><a href="#" data-page="home">首页</a></li>
      <li><a href="#" data-page="about">关于我们</a></li>
      <li><a href="#" data-page="services">服务</a></li>
      <li><a href="#" data-page="contact">联系我们</a></li>
    </ul>
  </nav>
  
  <!-- 页面内容 -->
  <main class="main-content">
    <section id="home" class="page active">
      <h1>欢迎来到首页</h1>
      <p>这是单页应用的首页内容</p>
      <div class="feature-card">
        <h2>特色服务</h2>
        <p>我们提供高质量的服务</p>
      </div>
    </section>
    
    <section id="about" class="page">
      <h1>关于我们</h1>
      <p>我们是一家专注于提供优质服务的公司</p>
      <div class="team-section">
        <h2>我们的团队</h2>
        <p>由专业人士组成的团队</p>
      </div>
    </section>
    
    <section id="services" class="page">
      <h1>我们的服务</h1>
      <p>我们提供多种专业服务</p>
      <div class="service-list">
        <h2>服务列表</h2>
        <ul>
          <li>服务 1</li>
          <li>服务 2</li>
          <li>服务 3</li>
        </ul>
      </div>
    </section>
    
    <section id="contact" class="page">
      <h1>联系我们</h1>
      <p>如有任何问题,请随时联系我们</p>
      <div class="contact-form">
        <h2>联系表单</h2>
        <form>
          <input type="text" placeholder="姓名">
          <input type="email" placeholder="邮箱">
          <textarea placeholder="消息"></textarea>
          <button type="submit">提交</button>
        </form>
      </div>
    </section>
  </main>
  
  <script src="script.js"></script>
</body>
</html>

CSS 样式

/* 基础样式 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  color: #333;
}

/* 导航栏样式 */
.navbar {
  background-color: #333;
  color: white;
  padding: 1rem;
  position: sticky;
  top: 0;
  z-index: 100;
  view-transition-name: navbar;
}

.navbar ul {
  display: flex;
  list-style: none;
  gap: 1rem;
}

.navbar a {
  color: white;
  text-decoration: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  transition: background-color 0.3s ease;
}

.navbar a:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

/* 页面内容样式 */
.main-content {
  padding: 2rem;
}

.page {
  display: none;
  min-height: 80vh;
  padding: 2rem;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.page.active {
  display: block;
}

.page h1 {
  margin-bottom: 1rem;
  color: #333;
  view-transition-name: page-title;
}

.page p {
  margin-bottom: 2rem;
  color: #666;
}

/* 特色卡片样式 */
.feature-card,
.team-section,
.service-list,
.contact-form {
  background-color: #f5f5f5;
  padding: 1.5rem;
  border-radius: 8px;
  margin-top: 2rem;
}

/* 联系表单样式 */
.contact-form form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1rem;
}

.contact-form input,
.contact-form textarea {
  padding: 0.8rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.contact-form button {
  padding: 0.8rem;
  background-color: #333;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.contact-form button:hover {
  background-color: #555;
}

/* View Transitions 样式 */
@supports (view-transition-name: none) {
  /* 导航栏过渡 */
  ::view-transition-old(navbar),
  ::view-transition-new(navbar) {
    animation: none;
    mix-blend-mode: normal;
  }
  
  /* 页面标题过渡 */
  ::view-transition-old(page-title) {
    animation: slide-out 0.6s ease-in-out;
  }
  
  ::view-transition-new(page-title) {
    animation: slide-in 0.6s ease-in-out;
  }
  
  /* 根元素过渡 */
  ::view-transition-old(root) {
    animation: fade-out 0.6s ease-in-out;
  }
  
  ::view-transition-new(root) {
    animation: fade-in 0.6s ease-in-out;
  }
}

/* 动画定义 */
@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slide-out {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

/* 响应式设计 */
@media (max-width: 768px) {
  .navbar ul {
    flex-direction: column;
    gap: 0.5rem;
  }
  
  .main-content {
    padding: 1rem;
  }
  
  .page {
    padding: 1rem;
  }
}

JavaScript 控制

// 导航控制逻辑
const navLinks = document.querySelectorAll('.navbar a');
const pages = document.querySelectorAll('.page');

// 为导航链接添加点击事件监听器
navLinks.forEach(link => {
  link.addEventListener('click', (e) => {
    e.preventDefault();
    const targetPage = link.getAttribute('data-page');
    navigateToPage(targetPage);
  });
});

// 页面导航函数
function navigateToPage(pageId) {
  // 检查浏览器是否支持 View Transitions
  if ('startViewTransition' in document) {
    // 使用 View Transitions
    document.startViewTransition(() => {
      updateActivePage(pageId);
    });
  } else {
    // 降级方案:直接更新页面
    updateActivePage(pageId);
  }
}

// 更新活动页面
function updateActivePage(pageId) {
  // 隐藏所有页面
  pages.forEach(page => {
    page.classList.remove('active');
  });
  
  // 显示目标页面
  document.getElementById(pageId).classList.add('active');
  
  // 更新导航链接状态
  navLinks.forEach(link => {
    link.style.fontWeight = link.getAttribute('data-page') === pageId ? 'bold' : 'normal';
  });
}

// 初始化:设置首页为活动页面
updateActivePage('home');

效果

  • 点击导航链接时,页面之间会有平滑的过渡动画
  • 页面标题会有滑入滑出效果
  • 整个页面内容会有淡入淡出效果
  • 导航栏保持固定,不参与过渡
  • 对于不支持 View Transitions 的浏览器,会降级为直接页面切换

案例二:主题切换

需求:创建一个主题切换功能,使用 View Transitions 实现主题切换时的平滑过渡效果。

HTML 结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>主题切换示例</title>
  <link rel="stylesheet" href="theme-toggle.css">
</head>
<body>
  <div class="container">
    <header class="header">
      <h1>主题切换示例</h1>
      <button id="theme-toggle" class="theme-toggle">切换主题</button>
    </header>
    
    <main class="content">
      <section class="card">
        <h2>欢迎使用主题切换</h2>
        <p>点击上方的按钮切换明/暗主题,体验平滑的过渡效果。</p>
      </section>
      
      <section class="card">
        <h2>功能特性</h2>
        <ul>
          <li>平滑的主题过渡动画</li>
          <li>响应式设计</li>
          <li>可访问性支持</li>
          <li>减少认知负荷</li>
        </ul>
      </section>
    </main>
    
    <footer class="footer">
      <p>© 2023 主题切换示例</p>
    </footer>
  </div>
  
  <script src="theme-toggle.js"></script>
</body>
</html>

CSS 样式

/* 基础样式 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* 容器样式 */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem;
}

/* 头部样式 */
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid #ddd;
  view-transition-name: header;
}

.header h1 {
  color: #333;
  view-transition-name: site-title;
}

/* 主题切换按钮 */
.theme-toggle {
  padding: 0.8rem 1.5rem;
  background-color: #333;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  view-transition-name: theme-button;
}

.theme-toggle:hover {
  background-color: #555;
}

/* 内容样式 */
.content {
  margin-bottom: 2rem;
}

/* 卡片样式 */
.card {
  background-color: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 2rem;
  view-transition-name: card;
}

.card h2 {
  margin-bottom: 1rem;
  color: #333;
}

.card p {
  margin-bottom: 1.5rem;
  color: #666;
}

.card ul {
  list-style: none;
}

.card li {
  margin-bottom: 0.5rem;
  color: #666;
  padding-left: 1.5rem;
  position: relative;
}

.card li::before {
  content: "•";
  position: absolute;
  left: 0;
  color: #333;
  font-weight: bold;
}

/* 页脚样式 */
.footer {
  padding-top: 1rem;
  border-top: 1px solid #ddd;
  text-align: center;
  color: #666;
}

/* 暗色主题 */
body.dark {
  background-color: #1a1a1a;
  color: #e0e0e0;
}

body.dark .header {
  border-bottom-color: #333;
}

body.dark .header h1 {
  color: #e0e0e0;
}

body.dark .theme-toggle {
  background-color: #e0e0e0;
  color: #1a1a1a;
}

body.dark .theme-toggle:hover {
  background-color: #f0f0f0;
}

body.dark .card {
  background-color: #2d2d2d;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}

body.dark .card h2 {
  color: #e0e0e0;
}

body.dark .card p,
body.dark .card li {
  color: #b0b0b0;
}

body.dark .card li::before {
  color: #e0e0e0;
}

body.dark .footer {
  border-top-color: #333;
  color: #b0b0b0;
}

/* View Transitions 样式 */
@supports (view-transition-name: none) {
  /* 头部过渡 */
  ::view-transition-old(header),
  ::view-transition-new(header) {
    animation: none;
    mix-blend-mode: normal;
  }
  
  /* 网站标题过渡 */
  ::view-transition-old(site-title),
  ::view-transition-new(site-title) {
    animation: fade 0.6s ease-in-out;
  }
  
  /* 主题按钮过渡 */
  ::view-transition-old(theme-button),
  ::view-transition-new(theme-button) {
    animation: scale 0.6s ease-in-out;
    mix-blend-mode: normal;
  }
  
  /* 卡片过渡 */
  ::view-transition-old(card),
  ::view-transition-new(card) {
    animation: slide-fade 0.8s ease-in-out;
  }
  
  /* 根元素过渡 */
  ::view-transition-old(root) {
    animation: fade-out 0.6s ease-in-out;
  }
  
  ::view-transition-new(root) {
    animation: fade-in 0.6s ease-in-out;
  }
}

/* 动画定义 */
@keyframes fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes scale {
  from { transform: scale(0.8); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

@keyframes slide-fade {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 响应式设计 */
@media (max-width: 768px) {
  .container {
    padding: 1rem;
  }
  
  .header {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }
  
  .card {
    padding: 1.5rem;
  }
}

JavaScript 控制

// 主题切换逻辑
const themeToggle = document.getElementById('theme-toggle');
const body = document.body;

// 检查本地存储中的主题偏好
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
  body.classList.add('dark');
  updateButtonText();
}

// 为主题切换按钮添加点击事件监听器
themeToggle.addEventListener('click', () => {
  // 检查浏览器是否支持 View Transitions
  if ('startViewTransition' in document) {
    // 使用 View Transitions
    document.startViewTransition(() => {
      toggleTheme();
    });
  } else {
    // 降级方案:直接切换主题
    toggleTheme();
  }
});

// 切换主题函数
function toggleTheme() {
  // 切换主题类
  body.classList.toggle('dark');
  
  // 保存主题偏好到本地存储
  const currentTheme = body.classList.contains('dark') ? 'dark' : 'light';
  localStorage.setItem('theme', currentTheme);
  
  // 更新按钮文本
  updateButtonText();
}

// 更新按钮文本
function updateButtonText() {
  themeToggle.textContent = body.classList.contains('dark') ? '切换到亮色主题' : '切换到暗色主题';
}

效果

  • 点击主题切换按钮时,页面会在亮色和暗色主题之间平滑过渡
  • 不同元素会有不同的过渡效果:标题淡入淡出,按钮缩放,卡片滑入淡出
  • 主题偏好会保存到本地存储,刷新页面后保持不变
  • 支持系统主题偏好检测
  • 对于不支持 View Transitions 的浏览器,会降级为直接主题切换

案例三:图片库筛选

需求:创建一个图片库,使用 View Transitions 实现筛选分类时的平滑过渡效果。

HTML 结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图片库筛选示例</title>
  <link rel="stylesheet" href="gallery.css">
</head>
<body>
  <div class="container">
    <header class="gallery-header">
      <h1>图片库</h1>
      <div class="filter-buttons">
        <button data-filter="all" class="filter-btn active">全部</button>
        <button data-filter="nature" class="filter-btn">自然</button>
        <button data-filter="city" class="filter-btn">城市</button>
        <button data-filter="portrait" class="filter-btn">人像</button>
      </div>
    </header>
    
    <main class="gallery">
      <div class="gallery-item nature">
        <img src="https://via.placeholder.com/300x200/FF5733/FFFFFF?text=Nature+1" alt="自然景观 1">
        <div class="gallery-caption">
          <h3>自然景观 1</h3>
          <p>美丽的自然风景</p>
        </div>
      </div>
      <div class="gallery-item city">
        <img src="https://via.placeholder.com/300x200/33FF57/FFFFFF?text=City+1" alt="城市景观 1">
        <div class="gallery-caption">
          <h3>城市景观 1</h3>
          <p>繁华的城市风光</p>
        </div>
      </div>
      <div class="gallery-item portrait">
        <img src="https://via.placeholder.com/300x200/3357FF/FFFFFF?text=Portrait+1" alt="人像 1">
        <div class="gallery-caption">
          <h3>人像 1</h3>
          <p>人物肖像摄影</p>
        </div>
      </div>
      <div class="gallery-item nature">
        <img src="https://via.placeholder.com/300x200/FF33F5/FFFFFF?text=Nature+2" alt="自然景观 2">
        <div class="gallery-caption">
          <h3>自然景观 2</h3>
          <p>壮观的自然景象</p>
        </div>
      </div>
      <div class="gallery-item city">
        <img src="https://via.placeholder.com/300x200/33FFF5/FFFFFF?text=City+2" alt="城市景观 2">
        <div class="gallery-caption">
          <h3>城市景观 2</h3>
          <p>现代城市建筑</p>
        </div>
      </div>
      <div class="gallery-item portrait">
        <img src="https://via.placeholder.com/300x200/F5FF33/FFFFFF?text=Portrait+2" alt="人像 2">
        <div class="gallery-caption">
          <h3>人像 2</h3>
          <p>艺术人像摄影</p>
        </div>
      </div>
    </main>
  </div>
  
  <script src="gallery.js"></script>
</body>
</html>

CSS 样式

/* 基础样式 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  background-color: #f5f5f5;
  color: #333;
}

/* 容器样式 */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem;
}

/* 头部样式 */
.gallery-header {
  margin-bottom: 2rem;
  text-align: center;
}

.gallery-header h1 {
  margin-bottom: 1.5rem;
  color: #333;
  view-transition-name: gallery-title;
}

/* 筛选按钮容器 */
.filter-buttons {
  display: flex;
  justify-content: center;
  gap: 1rem;
  margin-bottom: 2rem;
}

/* 筛选按钮样式 */
.filter-btn {
  padding: 0.8rem 1.5rem;
  background-color: #333;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  view-transition-name: filter-button;
}

.filter-btn:hover {
  background-color: #555;
}

.filter-btn.active {
  background-color: #007bff;
}

/* 画廊样式 */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
}

/* 画廊项样式 */
.gallery-item {
  background-color: white;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  view-transition-name: gallery-item;
}

.gallery-item:hover {
  transform: translateY(-5px);
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}

/* 画廊图片样式 */
.gallery-item img {
  width: 100%;
  height: 200px;
  object-fit: cover;
  display: block;
}

/* 画廊标题样式 */
.gallery-caption {
  padding: 1.5rem;
}

.gallery-caption h3 {
  margin-bottom: 0.5rem;
  color: #333;
}

.gallery-caption p {
  color: #666;
  font-size: 0.9rem;
}

/* 隐藏的画廊项 */
.gallery-item.hidden {
  display: none;
}

/* View Transitions 样式 */
@supports (view-transition-name: none) {
  /* 画廊标题过渡 */
  ::view-transition-old(gallery-title),
  ::view-transition-new(gallery-title) {
    animation: fade 0.6s ease-in-out;
  }
  
  /* 筛选按钮过渡 */
  ::view-transition-old(filter-button),
  ::view-transition-new(filter-button) {
    animation: scale 0.6s ease-in-out;
    mix-blend-mode: normal;
  }
  
  /* 画廊项过渡 */
  ::view-transition-old(gallery-item),
  ::view-transition-new(gallery-item) {
    animation: slide-fade 0.8s ease-in-out;
  }
  
  /* 根元素过渡 */
  ::view-transition-old(root) {
    animation: fade-out 0.6s ease-in-out;
  }
  
  ::view-transition-new(root) {
    animation: fade-in 0.6s ease-in-out;
  }
}

/* 动画定义 */
@keyframes fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes scale {
  from { transform: scale(0.8); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

@keyframes slide-fade {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 响应式设计 */
@media (max-width: 768px) {
  .container {
    padding: 1rem;
  }
  
  .filter-buttons {
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  
  .filter-btn {
    padding: 0.6rem 1.2rem;
    font-size: 0.9rem;
  }
  
  .gallery {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 1.5rem;
  }
  
  .gallery-item img {
    height: 180px;
  }
  
  .gallery-caption {
    padding: 1.2rem;
  }
}

@media (max-width: 480px) {
  .gallery {
    grid-template-columns: 1fr;
  }
  
  .gallery-item img {
    height: 200px;
  }
}

JavaScript 控制

// 画廊筛选逻辑
const filterButtons = document.querySelectorAll('.filter-btn');
const galleryItems = document.querySelectorAll('.gallery-item');

// 为筛选按钮添加点击事件监听器
filterButtons.forEach(button => {
  button.addEventListener('click', () => {
    const filter = button.getAttribute('data-filter');
    
    // 检查浏览器是否支持 View Transitions
    if ('startViewTransition' in document) {
      // 使用 View Transitions
      document.startViewTransition(() => {
        applyFilter(filter);
        updateActiveButton(button);
      });
    } else {
      // 降级方案:直接应用筛选
      applyFilter(filter);
      updateActiveButton(button);
    }
  });
});

// 应用筛选
function applyFilter(filter) {
  galleryItems.forEach(item => {
    if (filter === 'all' || item.classList.contains(filter)) {
      item.classList.remove('hidden');
    } else {
      item.classList.add('hidden');
    }
  });
}

// 更新活动按钮
function updateActiveButton(activeButton) {
  filterButtons.forEach(button => {
    button.classList.remove('active');
  });
  activeButton.classList.add('active');
}

效果

  • 点击筛选按钮时,画廊会根据选择的分类筛选图片,并使用平滑的过渡动画
  • 不同元素会有不同的过渡效果:标题淡入淡出,按钮缩放,图片项滑入淡出
  • 支持响应式设计,在不同屏幕尺寸下调整布局
  • 对于不支持 View Transitions 的浏览器,会降级为直接筛选

知识总结

  1. CSS View Transitions 的核心价值

    • 提供更加流畅、专业的页面状态过渡效果
    • 减少用户在页面切换时的认知负荷
    • 支持单页应用和多页应用的页面过渡
    • 简化复杂动画的实现,减少 JavaScript 依赖
    • 支持细粒度的元素过渡控制
  2. CSS View Transitions 的主要 API

    • document.startViewTransition():启动视图过渡的方法
    • ViewTransition 对象:控制和监听过渡的状态和方法
    • CSS 伪元素:::view-transition-* 系列伪元素
    • view-transition-name 属性:为元素指定过渡名称
  3. CSS View Transitions 的语法特点

    • 使用 JavaScript 启动过渡,CSS 控制过渡效果
    • 支持为不同元素定义不同的过渡效果
    • 可以通过 CSS 动画自定义过渡效果
    • 支持监听过渡的各个阶段
  4. 浏览器兼容性

    • 现代浏览器(Chrome、Edge)已支持
    • Safari 16.4+ 部分支持
    • Firefox 正在开发中
    • 可以使用特性检测提供降级方案
  5. 实际应用场景

    • 单页应用导航
    • 主题切换
    • 内容筛选和排序
    • 表单状态变化
    • 模态框的显示和隐藏
    • 数据可视化的更新

学习建议

  1. 实践练习

    • 创建一个使用 View Transitions 的单页应用导航
    • 实现一个带有平滑过渡效果的主题切换功能
    • 构建一个使用 View Transitions 的图片画廊筛选系统
    • 尝试为表单提交和验证添加过渡效果
  2. 深入学习

    • 研究 CSS View Transitions Module Level 1 规范的完整内容
    • 探索 View Transitions 与其他 CSS 特性的结合使用
    • 学习如何优化 View Transitions 的性能
    • 了解 View Transitions 的未来发展方向和新特性
  3. 应用拓展

    • 在实际项目中使用 View Transitions 提升用户体验
    • 创建可重用的 View Transitions 组件
    • 探索 View Transitions 在不同类型网站中的应用
    • 研究如何使用 View Transitions 实现更复杂的动画效果

通过本教程的学习,相信你已经掌握了 CSS View Transitions 的基本概念和使用方法。在实际项目中,合理运用这一强大的 CSS 特性,可以创建更加流畅、专业的用户界面,提升网站的整体用户体验。

« 上一篇 CSS3 最新特性 - CSS Scroll Snap 完整规范 下一篇 » CSS3最新特性 - CSS Color Level 4