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

实现过程

我们先创建下 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 来实现过渡动画效果,注意需要清除默认动画

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

相关推荐
寻找09之夏1 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
多多米10052 小时前
初学Vue(2)
前端·javascript·vue.js
看到请催我学习2 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
golitter.4 小时前
Vue组件库Element-ui
前端·vue.js·ui
道爷我悟了4 小时前
Vue入门-指令学习-v-on
javascript·vue.js·学习
.生产的驴5 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
老齐谈电商5 小时前
Electron桌面应用打包现有的vue项目
javascript·vue.js·electron
LIURUOYU4213085 小时前
vue.js组建开发
vue.js
九圣残炎5 小时前
【Vue】vue-admin-template项目搭建
前端·vue.js·arcgis
《源码好优多》6 小时前
基于SpringBoot+Vue+Uniapp的植物园管理小程序系统(2024最新,源码+文档+远程部署+讲解视频等)
vue.js·spring boot·uni-app