CSS教程

CSS视图过渡View Transitions API

CSS视图过渡View Transitions API:页面切换动画

教程概述

视图过渡(View Transitions)API 是一项现代Web技术,用于创建流畅的页面状态切换动画效果。该技术通过捕获DOM状态的快照,并在状态变化时在这些快照之间创建平滑过渡,使开发者能够以声明式方式实现复杂的动画效果,而无需编写大量JavaScript或CSS代码。

本教程将全面介绍View Transitions API的核心概念、使用方法和实际应用场景,帮助您在找找网项目中实现更优雅的用户界面过渡效果。

核心概念与基本原理

视图过渡API的工作原理基于状态快照的概念。当触发视图过渡时,浏览器会执行以下步骤:

  1. 捕获当前DOM状态作为旧视图快照
  2. 暂停页面渲染,防止中间状态闪烁
  3. 执行DOM更新到新状态
  4. 捕获新DOM状态作为新视图快照
  5. 创建伪元素树来托管新旧快照
  6. 执行过渡动画,在两个状态间平滑转换
  7. 清理伪元素,恢复页面正常交互

这一过程创建了一个伪元素树结构:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

基本用法与示例

单页面应用中的视图过渡

以下是一个在单页面应用中使用View Transitions API的完整示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>视图过渡示例 - 找找网</title>
    <style>
        .zzw_page {
            padding: 20px;
            transition: all 0.3s;
        }

        .zzw_button {
            padding: 10px 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin: 10px;
        }

        .zzw_content {
            padding: 20px;
            border: 1px solid #ddd;
            margin: 20px 0;
            border-radius: 4px;
        }

        /* 自定义过渡动画 */
        ::view-transition-old(root) {
            animation: zzw_fade-out 0.5s ease-in;
        }

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

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

        @keyframes zzw_fade-in {
            from { opacity: 0; }
            to { opacity: 1; }
        }
    </style>
</head>
<body>
    <div class="zzw_page">
        <h1>找找网视图过渡示例</h1>
        <button class="zzw_button" id="zzw_toggleButton">切换内容</button>

        <div class="zzw_content" id="zzw_contentArea">
            <h2>初始内容</h2>
            <p>这是视图过渡API演示的初始内容。</p>
        </div>
    </div>

    <script>
        const zzw_toggleButton = document.getElementById('zzw_toggleButton');
        const zzw_contentArea = document.getElementById('zzw_contentArea');

        let zzw_currentView = 'initial';

        zzw_toggleButton.addEventListener('click', () => {
            // 检查浏览器支持情况
            if (!document.startViewTransition) {
                zzw_switchContent();
                return;
            }

            // 使用视图过渡API
            document.startViewTransition(() => {
                zzw_switchContent();
            });
        });

        function zzw_switchContent() {
            if (zzw_currentView === 'initial') {
                zzw_contentArea.innerHTML = `
                    <h2>新内容已加载</h2>
                    <p>这是通过视图过渡API显示的新内容。</p>
                    <p>注意内容切换时的平滑过渡效果。</p>
                `;
                zzw_currentView = 'new';
            } else {
                zzw_contentArea.innerHTML = `
                    <h2>初始内容</h2>
                    <p>这是视图过渡API演示的初始内容。</p>
                `;
                zzw_currentView = 'initial';
            }
        }
    </script>
</body>
</html>

多页面应用中的视图过渡

对于多页面应用,现在可以通过一行CSS代码启用基础的视图过渡效果:

@view-transition {
  navigation: auto;
}

将上述代码添加到站点的每个页面样式表中,即可在页面导航时启用默认的交叉淡入淡出效果。


高级功能与自定义动画

元素级过渡与视图过渡命名

View Transitions API 允许为特定元素创建独立的过渡效果,通过view-transition-name属性实现:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>元素级视图过渡 - 找找网</title>
    <style>
        .zzw_card {
            width: 300px;
            padding: 20px;
            margin: 20px;
            border: 1px solid #ccc;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .zzw_featured {
            view-transition-name: zzw-featured-card;
        }

        .zzw_image {
            width: 100%;
            height: 200px;
            background-color: #f0f0f0;
            margin-bottom: 10px;
            view-transition-name: zzw-main-image;
        }

        /* 自定义元素过渡动画 */
        ::view-transition-old(zzw-featured-card),
        ::view-transition-new(zzw-featured-card) {
            animation-duration: 0.8s;
        }

        ::view-transition-old(zzw-main-image) {
            animation: zzw_scale-out 0.6s ease-in;
        }

        ::view-transition-new(zzw-main-image) {
            animation: zzw_scale-in 0.6s ease-out;
        }

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

        @keyframes zzw_scale-in {
            from { 
                transform: scale(0.5);
                opacity: 0;
            }
            to { 
                transform: scale(1);
                opacity: 1;
            }
        }
    </style>
</head>
<body>
    <div class="zzw_card zzw_featured">
        <div class="zzw_image"></div>
        <h3>特色内容卡片</h3>
        <p>此卡片具有自定义的视图过渡效果。</p>
    </div>

    <button class="zzw_button" id="zzw_updateCard">更新卡片</button>

    <script>
        const zzw_updateButton = document.getElementById('zzw_updateCard');
        const zzw_card = document.querySelector('.zzw_card');

        zzw_updateButton.addEventListener('click', () => {
            if (!document.startViewTransition) {
                zzw_toggleCardContent();
                return;
            }

            document.startViewTransition(() => {
                zzw_toggleCardContent();
            });
        });

        function zzw_toggleCardContent() {
            const zzw_image = document.querySelector('.zzw_image');
            const zzw_title = zzw_card.querySelector('h3');
            const zzw_content = zzw_card.querySelector('p');

            if (zzw_title.textContent === '特色内容卡片') {
                zzw_image.style.backgroundColor = '#e0f0ff';
                zzw_title.textContent = '已更新的卡片';
                zzw_content.textContent = '卡片内容已通过视图过渡API更新,注意图片和内容的过渡效果。';
            } else {
                zzw_image.style.backgroundColor = '#f0f0f0';
                zzw_title.textContent = '特色内容卡片';
                zzw_content.textContent = '此卡片具有自定义的视图过渡效果。';
            }
        }
    </script>
</body>
</html>

控制过渡生命周期

View Transitions API 提供了对过渡生命周期的精细控制:

// 视图过渡生命周期示例
function zzw_executeViewTransition(updateCallback) {
    // 检查浏览器支持
    if (!document.startViewTransition) {
        updateCallback();
        return;
    }

    // 启动视图过渡
    const zzw_transition = document.startViewTransition(async () => {
        await updateCallback();
    });

    // 更新回调完成时
    zzw_transition.updateCallbackDone.then(() => {
        console.log('DOM更新已完成');
    });

    // 过渡准备就绪时
    zzw_transition.ready.then(() => {
        console.log('过渡动画即将开始');
    });

    // 过渡完成时
    zzw_transition.finished.then(() => {
        console.log('过渡动画已完成');
    });
}

// 使用示例
document.getElementById('zzw_myButton').addEventListener('click', () => {
    zzw_executeViewTransition(() => {
        // 执行DOM更新
        document.getElementById('zzw_content').textContent = '内容已更新';
    });
});

浏览器兼容性与最佳实践

浏览器兼容性现状

View Transitions API 的浏览器支持情况如下:

浏览器支持版本支持状态
Chrome111+完全支持
Edge111+完全支持
Safari18.2+完全支持
Firefox未支持开发中

渐进增强实现

考虑到浏览器兼容性,实现时应采用渐进增强策略:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>渐进增强视图过渡 - 找找网</title>
    <style>
        .zzw_content {
            padding: 20px;
            transition: opacity 0.3s;
        }

        /* 仅在有视图过渡支持的浏览器中应用自定义动画 */
        @supports (view-transition-name: none) {
            ::view-transition-old(root),
            ::view-transition-new(root) {
                animation-duration: 0.5s;
            }

            .zzw_animated-element {
                view-transition-name: zzw-animated-element;
            }
        }

        /* 减少动画偏好设置 */
        @media (prefers-reduced-motion: reduce) {
            ::view-transition-old(root),
            ::view-transition-new(root) {
                animation: none;
            }
        }
    </style>
</head>
<body>
    <div class="zzw_content" id="zzw_mainContent">
        <h1>渐进增强示例</h1>
        <p>此示例在不支持View Transitions API的浏览器中仍能正常工作。</p>
    </div>

    <button class="zzw_button" id="zzw_updateContent">更新内容</button>

    <script>
        const zzw_updateButton = document.getElementById('zzw_updateContent');

        zzw_updateButton.addEventListener('click', () => {
            // 特性检测
            if (!document.startViewTransition) {
                // 不支持API时的降级方案
                zzw_updateContent();
                return;
            }

            // 支持API时的增强体验
            document.startViewTransition(() => {
                zzw_updateContent();
            });
        });

        function zzw_updateContent() {
            const zzw_content = document.getElementById('zzw_mainContent');
            const zzw_currentText = zzw_content.querySelector('p').textContent;

            if (zzw_currentText.includes('渐进增强')) {
                zzw_content.querySelector('h1').textContent = '内容已更新';
                zzw_content.querySelector('p').textContent = 
                    '视图过渡API提供了平滑的过渡效果,在不支持的浏览器中会降级为直接更新。';
            } else {
                zzw_content.querySelector('h1').textContent = '渐进增强示例';
                zzw_content.querySelector('p').textContent = 
                    '此示例在不支持View Transitions API的浏览器中仍能正常工作。';
            }
        }

        // 检测减少动画偏好
        if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
            // 在用户偏好减少动画时跳过过渡
            document.startViewTransition = undefined;
        }
    </script>
</body>
</html>

性能优化建议

  1. 精简DOM更新:将昂贵的DOM操作集中在startViewTransition回调中,减少布局颠簸
  2. 避免长时间运行的任务:确保updateCallback不会阻塞主线程过长时间
  3. 谨慎使用复杂滤镜:在伪元素上使用大滤镜或阴影可能影响性能
  4. 尊重用户偏好:始终检测prefers-reduced-motion设置并提供适当的回退

实际应用场景

图片库过渡效果

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片库视图过渡 - 找找网</title>
    <style>
        .zzw_gallery {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
            padding: 20px;
        }

        .zzw_gallery img {
            width: 100%;
            height: 200px;
            object-fit: cover;
            cursor: pointer;
            view-transition-name: var(--zzw-transition-name);
        }

        .zzw_lightbox {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            display: none;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }

        .zzw_lightbox img {
            max-width: 90%;
            max-height: 90%;
            view-transition-name: zzw-lightbox-image;
        }

        /* 自定义图片过渡效果 */
        ::view-transition-old(zzw-lightbox-image),
        ::view-transition-new(zzw-lightbox-image) {
            animation-duration: 0.5s;
        }
    </style>
</head>
<body>
    <div class="zzw_gallery" id="zzw_gallery">
        <img src="https://picsum.photos/seed/1/300/200" alt="示例图片1" 
             style="--zzw-transition-name: zzw-gallery-1;">
        <img src="https://picsum.photos/seed/2/300/200" alt="示例图片2" 
             style="--zzw-transition-name: zzw-gallery-2;">
        <img src="https://picsum.photos/seed/3/300/200" alt="示例图片3" 
             style="--zzw-transition-name: zzw-gallery-3;">
    </div>

    <div class="zzw_lightbox" id="zzw_lightbox">
        <img src="" alt="放大视图" id="zzw_lightboxImage">
    </div>

    <script>
        const zzw_gallery = document.getElementById('zzw_gallery');
        const zzw_lightbox = document.getElementById('zzw_lightbox');
        const zzw_lightboxImage = document.getElementById('zzw_lightboxImage');

        zzw_gallery.addEventListener('click', (event) => {
            if (event.target.tagName === 'IMG') {
                zzw_openLightbox(event.target);
            }
        });

        zzw_lightbox.addEventListener('click', () => {
            zzw_closeLightbox();
        });

        function zzw_openLightbox(clickedImage) {
            if (!document.startViewTransition) {
                zzw_lightboxImage.src = clickedImage.src;
                zzw_lightbox.style.display = 'flex';
                return;
            }

            // 设置lightbox图片的过渡名称与点击的图片相同
            zzw_lightboxImage.style.viewTransitionName = 
                clickedImage.style.getPropertyValue('--zzw-transition-name');

            document.startViewTransition(() => {
                zzw_lightboxImage.src = clickedImage.src;
                zzw_lightbox.style.display = 'flex';
            });
        }

        function zzw_closeLightbox() {
            if (!document.startViewTransition) {
                zzw_lightbox.style.display = 'none';
                return;
            }

            document.startViewTransition(() => {
                zzw_lightbox.style.display = 'none';
                // 重置过渡名称
                zzw_lightboxImage.style.viewTransitionName = 'zzw-lightbox-image';
            });
        }
    </script>
</body>
</html>

常见问题与解决方案

视图过渡被取消的情况

视图过渡可能因以下原因被取消:

  • view-transition-name值重复
  • DOM状态变化过于复杂
  • 页面包含beforeunload事件监听器
  • 过渡过程中发生错误

调试视图过渡

可以使用Chrome开发者工具进行调试:

  1. 打开More ToolsAnimations面板
  2. 捕获动画并调整播放速度
  3. 检查视图过渡伪元素

教程知识点总结

知识点知识内容
View Transitions API 定义一项现代Web技术,用于创建DOM状态之间的平滑过渡动画
核心原理通过捕获新旧DOM状态快照,并在伪元素树上执行过渡动画
基本用法使用document.startViewTransition(updateCallback)启动过渡
多页面应用支持通过@view-transition { navigation: auto; }启用页面间过渡
元素级过渡使用view-transition-name属性为特定元素创建独立过渡效果
生命周期控制通过updateCallbackDonereadyfinishedPromise监控过渡状态
浏览器兼容性目前Chrome、Edge和Safari新版支持,Firefox尚未支持
渐进增强策略通过特性检测提供不支持浏览器下的降级方案
性能优化精简DOM操作、避免长时间任务、尊重用户动画偏好
无障碍考虑检测prefers-reduced-motion媒体查询并为偏好减少动画的用户跳过过渡

View Transitions API 提供了创建现代化、流畅的用户界面过渡的强大能力。通过本教程介绍的概念和技术,找找网开发者可以在项目中有效应用这一技术,提升用户体验,同时确保在不支持的环境中保持功能正常。