CSS2024-05-14
返回首页

CSS View Transitions API 实战

CSS View Transitions API 实战

View Transitions API 让页面切换有了原生动画支持,2024 年已经可以在主流浏览器使用。

基本概念

View Transitions API 通过截图旧状态、过渡到新状态,实现平滑的页面切换。

// 最简单的用法
document.startViewTransition(() => {
  // 更新 DOM
  updateContent();
});

浏览器会:

  1. 捕获当前页面状态
  2. 执行回调函数更新 DOM
  3. 捕获新状态
  4. 在两个状态间做动画

页面切换动画

// SPA 路由切换
async function navigate(url) {
  if (!document.startViewTransition) {
    location.href = url;
    return;
  }
  
  const transition = document.startViewTransition(async () => {
    const html = await fetch(url).then(r => r.text());
    document.body.innerHTML = html;
  });
  
  await transition.finished;
}

// 添加 CSS 定义过渡效果
/* 默认过渡动画 */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

/* 自定义动画 */
::view-transition-old(root) {
  animation: fade-out 0.3s ease-out;
}

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

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

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

元素过渡

给元素添加 view-transition-name,实现跨页面的共享元素动画:

<!-- 列表页 -->
<div class="card" style="view-transition-name: card-1">
  <img src="photo.jpg" />
</div>

<!-- 详情页 -->
<div class="detail" style="view-transition-name: card-1">
  <img src="photo.jpg" />
</div>
::view-transition-old(card-1),
::view-transition-new(card-1) {
  animation-duration: 0.5s;
  animation-timing-function: ease-in-out;
}

列表页的卡片会平滑过渡到详情页。

列表项过渡

// 删除列表项
function removeItem(id) {
  document.startViewTransition(() => {
    const item = document.getElementById(id);
    item.remove();
  });
}

// 添加列表项
function addItem(data) {
  document.startViewTransition(() => {
    const list = document.querySelector('.list');
    const item = createItem(data);
    list.appendChild(item);
  });
}
/* 列表项过渡 */
.list-item {
  view-transition-name: var(--item-id);
}

::view-transition-old(*) {
  animation: slide-out 0.3s;
}

::view-transition-new(*) {
  animation: slide-in 0.3s;
}

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

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

与框架集成

React 示例:

import { useTransition } from 'react';

function useViewTransition() {
  const startTransition = useTransition()[1];
  
  return (callback) => {
    if (!document.startViewTransition) {
      callback();
      return;
    }
    
    document.startViewTransition(() => {
      startTransition(() => {
        callback();
      });
    });
  };
}

// 使用
function Card({ id }) {
  const transition = useViewTransition();
  
  return (
    <div 
      style={{ viewTransitionName: `card-${id}` }}
      onClick={() => transition(() => navigate(`/detail/${id}`))}
    >
      ...
    </div>
  );
}

Next.js 集成:

// app/layout.tsx
import { ViewTransitions } from 'next-view-transitions';

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <ViewTransitions />
      </head>
      <body>{children}</body>
    </html>
  );
}

浏览器支持

浏览器 版本
Chrome 111+
Edge 111+
Safari 18+
Firefox 实验性

兼容性处理:

function safeTransition(callback) {
  if (document.startViewTransition) {
    document.startViewTransition(callback);
  } else {
    callback();
  }
}

性能注意

  • 过渡元素不要太多(建议 < 50 个)
  • 复杂动画会增加内存占用
  • 低端设备可能需要降级
// 检测用户偏好
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  // 禁用或简化动画
  updateDirectly();
} else {
  document.startViewTransition(() => updateDirectly());
}

小结

View Transitions API 让页面切换动画变得简单:

  • 原生支持,无需库
  • 共享元素过渡效果
  • 与框架集成方便
  • 注意兼容性和性能

适合 SPA 路由切换、列表操作、卡片展开等场景。