这么炫酷的换肤动画,看一眼你就会爱上

实现过程

我们先创建下 vue 项目

shell 复制代码
npm init vite-app vue3-vite-animation

进入文件夹中

shell 复制代码
cd vue3-vite-animation

安装下依赖

shell 复制代码
npm install

启动

shell 复制代码
npm run dev

重新修改 App.vue

javascript 复制代码
<template>
  <div class="info-box">
    <div class="change-theme-btn">改变主题</div>
    <h1>Element Plus</h1>
    <p>基于 Vue 3,面向设计师和开发者的组件库</p>
  </div>
</template>

<script setup lang="ts">

</script>


<style>

.change-theme-btn {
  width: 80px;
  height: 40px;
  background-color: #fff;
  text-align: center;
  line-height: 40px;
  color: #282c34;
  cursor: pointer;
  border-radius: 8px;
  border: 2px solid #282c34;
}

.info-box {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
</style>

基本样式出来了,但是页面出现了滚动条,我们需要去掉原有样式

src/index.css,里的所有样式都删除了,再到 index.html 中将 bodymargin 属性去掉

html 复制代码
<body style="margin: 0;">
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>

接下来,我们来实现下换肤功能

使用 css 变量,先定义下一套黑暗主题、一套白色主题

css 复制代码
:root {
  --background-color: #fff;
  --color: #282c34;
  background-color: var(--background-color);
  color: var(--color);
}

:root.dark {
  --background-color: #282c34;
  --color: #fff;
}

再定义点击事件 changeColor,点击 "改变主题" 就会改变主题颜色

classList.toggle 这个方法的第一个参数是类名,第二个参数是布尔值,表示是否添加类

如果第二个参数为 true,则添加类;如果第二个参数为 false,则移除类

vue 复制代码
<div class="change-theme-btn" @click="changeColor">改变主题</div>
javascript 复制代码
/* 改变颜色 */
const changeColor = () => { 
  document.documentElement.classList.toggle('dark')
}

按钮背景颜色、边框、字体颜色都没有改变

调整下按钮样式,把背景颜色、边框、字体颜色这些都用 css 变量代替

css 复制代码
.change-theme-btn {
  width: 80px;
  height: 40px;
  background-color: var(--background-color);
  text-align: center;
  line-height: 40px;
  color: var(--color);
  cursor: pointer;
  border-radius: 8px;
  border: 2px solid var(--color);
}

这个效果不是我们想要的,需要一个过渡动画对不对

使用 startViewTransition,这个 API 会生成一个屏幕截图,将新旧屏幕截图进行替换

截图分别对应两个伪元素 ::view-transition-new(root)::view-transition-old(root)

javascript 复制代码
 // 创建一个过渡对象
document.startViewTransition(() => {
   document.documentElement.classList.toggle('dark')
})

可以看到,一个淡入淡出的效果,但是我们需要的是一个圆向外扩散的效果

用剪切效果就可以实现,其中 circle(动画进度 at 动画初始x坐标 动画初始y坐标)

设置动画时间为 1秒,作用在新的伪元素上,也即是作用在新的截图上

javascript 复制代码
const transition = document.startViewTransition(() => {
  document.documentElement.classList.toggle('dark')
})

transition.ready.then(() => { 
  document.documentElement.animate({
    clipPath: ['circle(0% at 50% 50%)', 'circle(100% at 100% 100%)']
  }, {
    duration: 1000,
    pseudoElement: '::view-transition-new(root)'
  })
})

为什么动画效果和预期的不一样

因为,默认的动画效果,把当前动画覆盖了,我们把默认动画效果去掉

css 复制代码
/* 隐藏默认的过渡效果 */
::view-transition-new(root),
::view-transition-old(root) {
  animation: none;
}

效果出来了,但是圆的扩散不是从按钮中心扩散的

那么,通过 ref="btn" 来获取 "改变主题" 按钮的坐标位置

再获取按钮坐标减去宽高,就能得到按钮的中心坐标了

vue 复制代码
<div ref="btn" class="change-theme-btn" @click="changeColor">改变主题</div>
javascript 复制代码
<script setup>
import { ref } from 'vue';
const btn = ref<any>(null)

/* 改变颜色 */
const changeColor = () => { 
  // 创建一个过渡对象
  const transition = document.startViewTransition(() => {
    document.documentElement.classList.toggle('dark')
  })

  const width = btn.value.getBoundingClientRect().width // 按钮的宽度
  const height = btn.value.getBoundingClientRect().height // 按钮的高度
  const x = btn.value.getBoundingClientRect().x + width / 2 // 按钮的中心x坐标
  const y = btn.value.getBoundingClientRect().y + height / 2 // 按钮的中心y坐标

  transition.ready.then(() => { 
    document.documentElement.animate({
      clipPath: [`circle(0% at ${x}px ${y}px)`, `circle(100% at ${x}px ${y}px)`]
    }, {
      duration: 1000,
      pseudoElement: '::view-transition-new(root)',
    })
  })
}
</script>

扩展,如果,我不要从中心扩展,要从左上角开始动画呢,右上角呢...

我们把按钮放在左上角,看看效果

修改下样式、与模板

javascript 复制代码
<template>
  <div ref="btn" class="change-theme-btn" @click="changeColor">改变主题</div>
  <div class="info-box">
    <h1>Element Plus</h1>
    <p>基于 Vue 3,面向设计师和开发者的组件库</p>
  </div>
</template>
css 复制代码
.info-box {
  width: 100vw;
  height: calc(100vh - 44px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

动画这个圆的半径不对,导致动画到快末尾的时候,直接就结束了

动画的圆的半径 = 按钮中心坐标 到 对角点的坐标

可以使用三角函数计算,两短边平方 = 斜边平方

javascript 复制代码
// 计算展开圆的半径
const tragetRadius = Math.hypot(
  window.innerWidth - x,
  innerHeight - y
)

// 设置过渡的动画效果
transition.ready.then(() => {
  document.documentElement.animate({
    clipPath: [`circle(0% at ${x}px ${y}px)`, `circle(${tragetRadius}px at ${x}px ${y}px)`]
  }, {
    duration: 1000,
    // pseudoElement 
    // 设置过渡效果的伪元素,这里设置为根元素的伪元素
    // 这样过渡效果就会作用在根元素上
    pseudoElement: '::view-transition-new(root)',
  })
})

如果是右上角呢

css 复制代码
.change-theme-btn {
  float: right;
  width: 80px;
  height: 40px;
  background-color: var(--background-color);
  text-align: center;
  line-height: 40px;
  color: var(--color);
  cursor: pointer;
  border-radius: 8px;
  border: 2px solid var(--color);
}

在右边的话,使用三角函数计算,其中一个短边就不能是 屏幕宽度 - 按钮x坐标,直接是 x 坐标就对了

那要怎么实现呢,直接取 屏幕宽度 - 按钮x坐标 与 按钮x坐标 的最大值就可以了

y 也是同理

javascript 复制代码
const tragetRadius = Math.hypot(
  Math.max(x, window.innerWidth - x),
  Math.max(y, window.innerHeight - y)
)

你可以试试其他位置,是否也是可行的

完整代码

javascript 复制代码
<template>
  <div ref="btn" class="change-theme-btn" @click="changeColor">改变主题</div>
  <div class="info-box">
    <h1>Element Plus</h1>
    <p>基于 Vue 3,面向设计师和开发者的组件库</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const btn = ref<any>(null)

/* 改变颜色 */
const changeColor = () => { 
  // 创建一个过渡对象
  const transition = document.startViewTransition(() => {
    document.documentElement.classList.toggle('dark')
  })

  const width = btn.value.getBoundingClientRect().width // 按钮的宽度
  const height = btn.value.getBoundingClientRect().height // 按钮的高度
  const x = btn.value.getBoundingClientRect().x + width / 2 // 按钮的中心x坐标
  const y = btn.value.getBoundingClientRect().y + height / 2 // 按钮的中心y坐标

  // 计算展开圆的半径
  const tragetRadius = Math.hypot(
    Math.max(x, window.innerWidth - x),
    Math.max(y, window.innerHeight - y)
  )

  // 设置过渡的动画效果
  transition.ready.then(() => {
    document.documentElement.animate({
      clipPath: [`circle(0% at ${x}px ${y}px)`, `circle(${tragetRadius}px at ${x}px ${y}px)`]
    }, {
      duration: 1000,
      // pseudoElement 
      // 设置过渡效果的伪元素,这里设置为根元素的伪元素
      // 这样过渡效果就会作用在根元素上
      pseudoElement: '::view-transition-new(root)',
    })
  })
}
</script>


<style>

:root {
  --background-color: #fff;
  --color: #282c34;
  background-color: var(--background-color);
  color: var(--color);
}

:root.dark {
  --background-color: #282c34;
  --color: #fff;
}

/* 隐藏默认的过渡效果 */
::view-transition-new(root),
::view-transition-old(root) {
  animation: none;
}

.change-theme-btn {
  float: right;
  width: 80px;
  height: 40px;
  background-color: var(--background-color);
  text-align: center;
  line-height: 40px;
  color: var(--color);
  cursor: pointer;
  border-radius: 8px;
  border: 2px solid var(--color);
}

.info-box {
  width: 100vw;
  height: calc(100vh - 44px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
</style>

换肤动画源码

小结

换肤功能,主要靠 css 变量 与 classList.toggle

startViewTransition 这个 API 来实现过渡动画效果,注意需要清除默认动画

圆点扩散效果,主要运用剪切的方式进行实现,计算过程运用了三角函数运算

相关推荐
计算机-秋大田4 小时前
基于Spring Boot的兴顺物流管理系统设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·spring·课程设计
禾苗种树5 小时前
在 Vue 3 中使用 ECharts 制作多 Y 轴折线图时,若希望 **Y 轴颜色自动匹配折线颜色**且无需手动干预,可以通过以下步骤实现:
前端·vue.js·echarts
小盼江7 小时前
水果生鲜农产品推荐系统 协同过滤余弦函数推荐水果生鲜农产品 Springboot Vue Element-UI前后端分离 代码+开发文档+视频教程
vue.js·spring boot·ui
初遇你时动了情7 小时前
react module.scss 避免全局冲突类似vue中scoped
vue.js·react.js·scss
烂蜻蜓8 小时前
Uniapp 设计思路全分享
前端·css·vue.js·uni-app·html
bin91538 小时前
DeepSeek 助力 Vue 开发:打造丝滑的二维码生成(QR Code)
前端·javascript·vue.js·ecmascript·deepseek
浪九天13 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
尚学教辅学习资料13 小时前
基于SpringBoot+vue+uniapp的智慧旅游小程序+LW示例参考
vue.js·spring boot·uni-app·旅游
IT、木易14 小时前
跟着AI学vue第五章
前端·javascript·vue.js
薛定谔的猫-菜鸟程序员14 小时前
Vue 2全屏滚动动画实战:结合fullpage-vue与animate.css打造炫酷H5页面
前端·css·vue.js