主题切换功能 --派大星的开发日记
个人开发记录笔记,仅个人理解,有错误还请指出
前言
在现代 Web 应用中,主题切换功能几乎成为了标配。为了让用户的体验更上一层楼,加入一些炫酷的过渡动画无疑是一个不错的选择。这篇文章将分享如何结合 Vue3、VueUse、ViewTransition API 和 Web Animations API,实现一个圆形扩散过渡的主题切换功能,类似element-plus的动画,代码简洁,效果丝滑。
效果预览
点击切换主题时,会有一个圆形扩散的动画, 跟随系统不执行动画,页面的主题切换过程更加自然流畅(给不懂技术的领导看效果更佳)。
用到的技术点
在正式进入代码实现之前,先详细介绍一下我们所用到的几个关键技术及其用途:
1. VueUse 的 useColorMode
- 功能 :VueUse 是一个 Vue 的工具库,其中的
useColorMode
是专门用来管理主题模式的。 - 特点 :
- 可以轻松管理亮色、暗色和自动主题。
- 自动监听系统主题模式(比如系统从亮色切换到暗色时,应用会自动切换)。
- 作用:简化主题切换的逻辑,减少我们对主题状态的手动管理。
2. ViewTransition API
- 功能:这是浏览器的新特性,允许在页面视图变化时添加平滑过渡动画。
- 特点 :
- 能够捕获页面更新前后的 DOM 状态。
- 配合动画,能让页面切换过程更加平滑和自然。
- 作用:在主题切换时,配合动画实现圆形扩散效果。
3. Web Animations API
- 功能:提供了 JavaScript 操控动画的能力,比传统的 CSS 动画更加灵活。
- 特点 :
- 可以动态控制动画的属性(如位置、时间等)。
- 支持暂停、继续等操作。
- 作用:用来实现动画的细节,比如圆形扩散的路径和覆盖范围。
4. 几何计算
- 功能:通过数学公式,动态计算动画的覆盖范围。
- 公式 :
动画的扩散半径需要满足覆盖整个屏幕,可以用以下公式计算:
radius = √((最大横向距离)² + (最大纵向距离)²)
- 作用:确保动画能够从点击点扩散到屏幕边缘,避免动画不完整。
代码思路解析
在了解了用到的技术之后,我们再来看实现的逻辑步骤。以下是具体的代码及其详细中文注释,让你快速掌握其中的精髓。
vue
<script setup>
import { ref, computed, onMounted } from "vue";
import { useColorMode } from "@vueuse/core";
const { system, store } = useColorMode();
// 计算属性,动态获取设置的 主题色模式
const myColorMode = computed(() =>
store.value === "auto" ? system.value : store.value
);
let themeSelected = ref(
store.value == "light" ? 1 : store.value == "dark" ? 2 : 3
);
const changeTheme = (val, e, id) => {
themeSelected.value = id;
if (store.value == val) return;
if (val === "auto") {
document.startViewTransition(() => {
store.value = val;
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(system.value); // 使用系统当前主题
});
return;
}
let transition = document.startViewTransition(() => {
store.value = val;
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(val);
});
transition.ready.then(() => {
// 获取选中元素的位置信息
const x = e.clientX;
const y = e.clientY;
// 获取画圆的半径
const radius = Math.sqrt(
Math.max(x, window.innerWidth - x) ** 2 +
Math.max(y, window.innerHeight - y) ** 2
); // 勾股定理
document.documentElement.animate(
{
clipPath: [
`circle(0 at ${x}px ${y}px)`,
`circle(${radius}px at ${x}px ${y}px)`,
],
}, // 在坐标x,y处从半径为0的圆变为半径为radius的圆
{
duration: 500,
pseudoElement: "::view-transition-new(root)",
}
);
});
};
let themeList = ref([
{
id: 1,
name: "亮色主题",
click: (e) => changeTheme("light", e, 1),
},
{
id: 2,
name: "暗色主题",
click: (e) => changeTheme("dark", e, 2),
},
{
id: 3,
name: "将主题与电脑同步",
click: (e) => changeTheme("auto", e, 3),
},
]);
</script>
<template>
<div class="interface-wp">
<div class="theme-box">
<div class="theme-title">界面主题</div>
<div
class="theme-item"
v-for="item in themeList"
:key="item.id"
v-wave="{
color: 'var(--animation-color)',
duration: 0.3,
}"
@click="item.click"
>
<div
class="theme-item-box"
:class="{ active: themeSelected == item.id }"
>
<article></article>
</div>
<div class="theme-item-name">{{ item.name }}</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.interface-wp {
padding-top: 80px;
box-sizing: border-box;
.theme-box {
padding: 20px;
border-radius: 10px;
box-sizing: border-box;
.theme-title {
font-weight: 600;
}
.theme-item {
margin-top: 10px;
display: flex;
align-items: center;
height: 40px;
line-height: 40px;
background: var(--setting-menu-item-bg);
color: var(--setting-menu-item-active-color);
border-radius: 10px;
padding: 0 10px;
box-sizing: border-box;
cursor: pointer;
&:hover {
background: var(--setting-menu-item-hover-bg);
& .theme-item-box article {
background: #bcc2cc;
}
}
.theme-item-box {
width: 20px;
height: 20px;
border-radius: 5px;
border: 1px solid #586274;
background: #fff;
padding: 2px;
box-sizing: border-box;
&.active {
article {
background: #ffbc00;
}
}
article {
width: 100%;
height: 100%;
background: #fff;
border-radius: 2px;
}
}
.theme-item-name {
margin-left: 10px;
font-size: 14px;
}
}
}
}
</style>
<style>
:root {
--theme-color: #000;
--bg-color: #fff;
background-color: var(--bg-color);
color: var(--theme-color);
}
:root.dark {
--bg-color: #303030;
--theme-color: #fff;
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
</style>
总结
通过结合 VueUse 的 useColorMode
、ViewTransition API 和 Web Animations API,我们不仅实现了主题切换功能,还为用户提供了一个更加炫酷、流畅的体验。希望这篇文章能为你在实际开发中提供帮助!