全新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页面的切换过渡效果。

相关推荐
一撮不知名的呆毛23 分钟前
Ajax局部刷新,异步请求
前端·javascript·ajax
好奇的菜鸟1 小时前
Vue.js 中 v-bind 和 v-model 的用法与异同
前端·javascript·vue.js
-代号95271 小时前
【React】一、JSX的使用
前端·react.js·前端框架
uhakadotcom2 小时前
AI搜索引擎的尽头是电商?从perplexity开始卖货说起...
前端·人工智能·后端
selfsuer2 小时前
Element-plus 【el-input输入框】和【el-select下拉选择框】样式修改
前端·javascript·vue.js
咔叽布吉3 小时前
【前端学习笔记】ES6 新特性
前端·笔记·学习
推开世界的门4 小时前
web 中 canvas 污染 以及解决方案
前端
星离~4 小时前
css—轮播图实现
前端·css
龙雨LongYu124 小时前
vue3+ts 我写了一个跟swagger.yml生成请求和响应实体(接口)
前端·vue.js·typescript
Stanford_11065 小时前
关于IDE的相关知识之一【使用技巧】
前端·ide·windows·微信小程序·微信公众平台·twitter·微信开放平台