1

用途

实现视图过渡效果

示例1

实现两张图片之间的过渡效果
chrome-capture-2024-10-29 (1).gif

<script setup lang="ts">
import { ref } from "vue"

const visible = ref(false)

const toggle = () => {
  // 关键代码,把扭转状态的代码用startViewTransition包起来
  document.startViewTransition(() => {
    visible.value = !visible.value
  })
}
</script>

<template>
  <img v-if="!visible"
    class="small-img"
    src="https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg"
    @click="toggle">
  <img v-if="visible"
    class="big-img"
    src="https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg"
    @click="toggle">
</template>

<style>
.small-img {
  width: 500px;
}

.big-img {
  width: 1000px;
}
</style>

startViewTransition的默认效果是对新旧视图进行fade-in和fade-out动画过渡,也就是opacity的0和1过渡。

示例2

缩放过渡
chrome-capture-2024-10-29.gif

<script setup lang="ts">
import { ref } from "vue"

const visible = ref(false)
const toggle = () => {
  document.startViewTransition(() => {
    visible.value = !visible.value
  })
}
</script>

<template>
  <img v-if="!visible"
    class="small-img"
    src="https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg"
    @click="toggle">
  <img v-if="visible"
    class="big-img"
    src="https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg"
    @click="toggle">
</template>

<style>
.small-img {
  width: 500px;

  // 关键代码 定义过渡名称
  view-transition-name: img;
}

.big-img {
  width: 1000px;

  // 关键代码 定义过渡名称
  view-transition-name: img;
}
</style>

实现该效果的关键代码是.small-img和.big-img上定义的view-transition-name: img,这里的代码是告诉浏览器,帮我对view-transition-name为img的元素进行动画过渡。然后浏览器会对两个设置为同样view-transition-name的元素进行动画过渡。

需要注意的是,在同一个dom树中,不能同时存在两个view-transition-name相同的元素,所以你会看到上面的示例代码中使用visible进行了判断。

示例3

路由过渡
chrome-capture-2024-10-30 (1).gif

路由配置如下:

const routes = [
  {
    path: "/before",
    component: before,
  },
  {
    path: "/after",
    component: after,
  },
]

/before路由内容

<script setup lang="ts">
import { useRouter } from "vue-router"

const router = useRouter()

const handleClick = () => {
  document.startViewTransition(() => {
    router.push('/photos/1')
  })
}
</script>

<template>
  <img
    class="photo"
    src="https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg"
    alt="photo"
    @click="handleClick"
  />
</template>

<style scoped>
.photo {
  width: 400px;
  // 关键代码 定义过渡名称
  view-transition-name: photo;
}
</style>

/after路由内容

<script setup lang="ts">
import { useRouter } from "vue-router"

const router = useRouter()

const handleClick = () => {
  document.startViewTransition(() => {
    router.push('/before')
  })
}
</script>

<template>
  <img
    class="photo"
    src="https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg"
    alt="photo"
    @click="handleClick"
  />
</template>

<style scoped>
.photo {
  max-width: 100%;
  // 关键代码 定义过渡名称
  view-transition-name: photo;
}
</style>

两个路由中的img元素声明了相同的view-transition-name,然后浏览器就对其进行了动画过渡。

示例4

列表与详情路由的过渡
chrome-capture-2024-10-30 (2).gif

/articles路由

<script setup lang="ts">
import { ref } from "vue"
import { useRouter } from "vue-router";

import { photos as photosData } from "@/api";

const router = useRouter()
const photos = ref(photosData)

const handleClick = (id: number) => {
  document.startViewTransition(() => {
    router.push(`/articles/${id}`)
  })
}
</script>

<template>
  <div class="container">
    <div class="items">
      <div
        class="item"
        v-for="photo in photos"
        :key="photo.id"
        @click="handleClick(photo.id)"
      >
        <img
          class="img"
          :src="photo.src"
          :alt="photo.alt"
          :style="`view-transition-name: photo-${photo.id}`" />
        <div
          :style="`view-transition-name: title-${photo.id}`"
        >{{ photo.alt }}</div>
      </div>
    </div>
  </div>
</template>

<style scoped>
body {
  background: #f7f7f8;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
}

.items {
  display: flex;
  flex-direction: column;
  margin-top: 20px;
}

.item {
  display: block;
  margin-bottom: 20px;
  width: 400px;
  padding: 15px;
  border-radius: 3px;
  background: #fff;
}

.img {
  width: 100%;
  margin-bottom: 5px;
}
</style>

/articles/:id路由

<script setup lang="ts">
import { computed } from "vue";
import { useRouter } from "vue-router";
import { useRoute } from "vue-router"
import { photos } from "@/api";

const router = useRouter()
const route = useRoute()

const id = route.params.id as string
const data = computed(() => photos.find((photo) => photo.id === Number(id)))

const handleBack = () => {
  document.startViewTransition(() => {
    router.push("/articles")
  })
}
</script>

<template>
  <div class="container" @click.self="handleBack">
    <img
      class="img"
      :src="data?.src"
      :style="`view-transition-name: photo-${id}`" />
    <div
      class="title"
      :style="`view-transition-name: title-${id}`"
    >{{ data?.alt }}</div>
    <button @click="handleBack">返回</button>
  </div>
</template>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.img {
  width: 1000px;
}

.title {
  padding: 20px 0;
  font-size: 20px;
  font-weight: bold;
}
</style>

实现该效果的关键代码是.img元素上的view-transition-name: photo-${id}和.title元素上的view-transition-name: title-${id},由于同一个页面不能有相同的view-transition-name存在,所以在这种列表过渡的情况下,我们给他们打上序号。


热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。