全新View Transitions API 更简易地实现平滑过渡效果

介绍

View Transitions API 是一个较新的web API,其实截止目前也已经出现快一年时间了,根据MDN的介绍,该API属于实验性技术,Chrom和Edge的111版本以上已经能够使用了,具体浏览器兼容可以继续往下看。

它提供了一种机制,可以轻松地在不同DOM状态之间创建动画转换,同时还可以在一个步骤中更新DOM内容,简单来说就是他提供了一种类似css transition动画的效果,我们在开发SPA网站应用时,在页面路由切换或页面状态改变时,往往需要写大量的js和css来进行控制。而View Transitions API提供了一种更简单的方法来处理所需的DOM更改和过渡动画。

视图转换原理

当调用document.startViewTransition()时,API会对当前页面进行截图。接下来,调用传递给startViewTransition()的回调,这会导致DOM发生变化。当回调成功运行时ViewTransition.updateCallbackDone 将实现,允许您响应DOM更新。API捕获页面的新状态作为实时表示。

API构造具有以下结构的伪元素树:

ruby 复制代码
::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

::view-transition是视图转换覆盖的根,它包含所有视图转换并位于所有其他页面内容的顶部。 当过渡动画即将运行时,ViewTransition.ready的回调,允许您通过运行自定义JavaScript动画而不是默认动画来响应。旧的页面视图从opacity 1动画到0,而新视图从opacity 0动画到1,这就是创建默认交叉淡入淡出的原因。当过渡动画到达其结束状态时,ViewTransition.finished将实现,允许您进行响应。

我们通过简单的实现可以实现路由切换时淡入淡出效果

ini 复制代码
  const transition = document.startViewTransition(() => {
        router.push('/detail/' + index);
    });

为了让渐变效果更明显一点将过渡时间增加一点

ruby 复制代码
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 1s;
}

实现效果如下:

自定义过渡节点

通过上面的介绍,我们了解了该API是如何实现一个动态效果的,除了我们给整个页面root增加过渡效果外,我们还能通过view-transition-name: detail 来单独对某一个dom节点进行过渡。

css 复制代码
@keyframes large {
    from {
        transform: scale(0);
    }

    to {
        transform: scale(1);
    }
}

@keyframes small {
    from {
        transform: scale(1);
    }

    to {
        transform: scale(0);

    }
}

::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 1s;
}


.detail-page::view-transition-old(detail) {
    animation: none;
    display: none;
}

.detail-page::view-transition-new(detail) {
    animation: large 1s;
}

::view-transition-new(detail) {
    animation: large 1s;
}

上面代码中,我们自定义了新的路由页面view-transition-name: detail,然后通过::view-transition-new(detail)::view-transition-old(detail),页面打开的方式为从缩放打开。这样就实现了一个手机app打开的效果。

实现效果如下:

更多应用

实现路由切换时前进后退的效果

类似于上面的示例,我们在页面路由切换时,改变新老页面的过渡效果,就可以简单地实现原生app切换的效果。具体实现如下: home.vue

xml 复制代码
<!--
 * @Author: fcli
 * @Date: 2023-11-10 11:10:36
 * @LastEditors: fcli
 * @LastEditTime: 2024-02-21 16:45:03
 * @FilePath: /viewTransitions/src/pages/home/slide.vue
 * @Description: 
-->
<template>
    <div class="home-content">
        <div class="item" @click="(e) => linkToPage(e, i)" v-for="i in 20" :key="i"
            :style="{ background: getRandomColor() }">
        </div>
    </div>
</template>


<script lang="ts" setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const linkToPage = (e: any, index: any) => {
    const transition = document.startViewTransition(() => {
        router.push('/detail/' + index);
        document.documentElement.classList.remove('router-backing');
    });
}
const getRandomColor = () => {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}


</script>
<style lang="less" scoped>
@keyframes slide-out {
    from {
        transform: translateX(0);
    }

    to {
        transform: translateX(-100%);
    }
}

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

    to {
        transform: translateX(0);
    }
}

@keyframes slide-out-reverse {
    from {
        transform: translateX(0);
    }

    to {
        transform: translateX(100%);
    }
}

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

    to {
        transform: translateX(0);
    }
}

.router-backing::view-transition-old(root) {
    animation: slide-out-reverse 0.5s;
}

.router-backing::view-transition-new(root) {
    animation: slide-in-reverse 0.5s;
}

::view-transition-old(root) {
    animation: slide-out .5s;
}

::view-transition-new(root) {
    animation: slide-in .5s;
}

.home-content {
    padding: 8px;
    display: flex;
    align-items: center;
    justify-content: space-around;
    flex-wrap: wrap;

    .item {
        width: 46%;
        height: 120px;
        margin-bottom: 8px;
        border-radius: 8px;
    }
}
</style>

detail.vue页面代码

xml 复制代码
<!--
 * @Author: fcli
 * @Date: 2024-02-20 15:10:16
 * @LastEditors: fcli
 * @LastEditTime: 2024-02-22 13:48:07
 * @FilePath: /viewTransitions/src/pages/detail.vue
 * @Description: 
-->
<template>
    <div class="detail">
        <div> 这是详情页面 </div>
        <div class="back" @click="linkReturn">返回</div>
    </div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();

const linkReturn = () => {
    const transition = document.startViewTransition(() => {
        document.documentElement.classList.add('router-backing');
        router.push('/');
    });

}
</script>
<style lang="less">
.detail {
    text-align: center;
    height: 100%;
    background: rgb(235, 238, 247);

    div {
        line-height: 80px;
    }

    .back {
        cursor: pointer;
        color: blueviolet
    }
}
</style>

这样就能实现如下效果了:

主题切换

在主题切换时我们也可以很方便地实现比较有意思的动态切换效果,实现代码如下:

ini 复制代码
let isLight = true;
const changeColor = () => {
    isLight = !isLight;
    let event = document.querySelector('.change-theme')?.getBoundingClientRect();
    const x = event?.x || 0;
    const y = event?.y || 0;
    const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
    const transition = document.startViewTransition(() => {
        const root = document.documentElement;
        root.classList.remove(isLight ? 'themeDark' : 'themeLight');
        root.classList.add(isLight ? 'themeLight' : 'themeDark');
    });

    transition.ready.then(() => {
        const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
        console.log(!isLight ? [...clipPath].reverse() : clipPath);
        document.documentElement.animate(
            {
                clipPath: !isLight ? clipPath : clipPath.reverse()
            },
            {
                duration: 500,
                easing: 'ease-in',
                pseudoElement: !isLight ? '::view-transition-new(root)' : '::view-transition-old(root)'
            }
        );
    });
}

::view-transition-old(root),
::view-transition-new(root) {
    animation: none;
    mix-blend-mode: normal;
}

.themeDark::view-transition-old(root) {
    z-index: 1;
}

.themeDark::view-transition-new(root) {
    z-index: 999;
}

::view-transition-old(root) {
    z-index: 999;
}

::view-transition-new(root) {
    z-index: 1;
}

实现效果如下:

浏览器兼容性

根据MDN的介绍,chrome和edge的新版本都支持该属性,在实际应用时需要做好兼容性判断。

总结

以上是对View Transitions API 学习后的一些经验总结和实际应用,这个API还有很多可以使用的场景,能够很简单地实现spa页面的切换过渡效果。

相关推荐
Redstone Monstrosity10 分钟前
字节二面
前端·面试
东方翱翔17 分钟前
CSS的三种基本选择器
前端·css
Fan_web39 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
冯宝宝^1 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby
前端西瓜哥1 小时前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法
又写了一天BUG1 小时前
npm install安装缓慢及npm更换源
前端·npm·node.js