使用 VueUse 和 View Transitions API 实现暗黑模式主题动画切换效果
前言
在当今的 Web 应用中,暗黑模式已经成为一种常见且受欢迎的功能。它不仅能够减少用户在夜间使用应用时的眼睛疲劳,还能为应用增添一份现代感。然而,简单的主题切换可能会显得生硬,影响用户体验。本文将介绍如何使用 VueUse 库和 View Transitions API 实现一个平滑的暗黑模式切换动画效果,让主题切换变得更加丝滑和优雅。
效果展示
技术栈介绍
View Transitions API
View Transitions API 是浏览器原生提供的一种 API,它能够让我们在 DOM 元素发生变化时创建平滑的过渡动画。这个 API 特别适合用于页面切换、主题切换等场景,能够大大提升用户体验。
兼容性:目前 View Transitions API
已经在 Chrome
和 Edge
等主流浏览器中得到支持,但在 Safari
和 Firefox
中可能尚未完全支持。Safari
仅在去年9月发布的 18
版本及以上支持。
VueUse
VueUse 是一个基于 Vue 的组合式 API 工具库,提供了大量实用的 Vue 组合式函数,能够帮助我们简化开发过程。在本文中,我们将使用 VueUse 提供的 useDark
和 useToggle
函数来实现主题切换功能。
NOTE
点击下发按钮,或在页面任意位置右键切换主题,即可查看切换效果,
实现步骤
1. 安装依赖
首先,我们需要安装 VueUse 库:
# 使用 npm
npm install @vueuse/core
# 或使用 pnpm
pnpm install @vueuse/core
# 或使用 yarn
yarn add @vueuse/core
2. 创建主题切换组件
接下来,我们创建一个主题切换组件,例如 ThemeToggle.vue
:
<template>
<div class="theme-container" ref="container">
<button class="toggle-theme" @click="toggleTheme">
<div class="icon">
{{ isDark ? '🌞' : '🌙' }}
</div>
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useDark, useToggle } from '@vueuse/core'
// 使用 VueUse 的 useDark 函数来管理暗黑模式状态
const isDark = useDark({
selector: 'html',
attribute: 'data-bs-theme',
valueDark: 'dark',
valueLight: 'light'
})
// 使用 useToggle 函数来切换暗黑模式
const toggleDark = useToggle(isDark)
// 获取容器引用
const container = ref(null)
// 主题切换函数
const toggleTheme = (event) => {
// 获取点击位置坐标
const x = event.clientX
const y = event.clientY
// 计算结束半径(从点击位置到屏幕最远点的距离)
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
)
// 兼容性处理:如果浏览器不支持 View Transitions API,则直接切换主题
if (!document.startViewTransition) {
toggleDark()
return
}
// 使用 View Transitions API 创建过渡效果
const transition = document.startViewTransition(async () => {
toggleDark()
})
// 过渡准备就绪后,执行动画
transition.ready.then(() => {
// 定义圆形裁剪路径
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
]
// 根据当前主题状态决定动画方向
document.documentElement.animate(
{
clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
},
{
duration: 400,
easing: "ease-in",
pseudoElement: isDark.value
? "::view-transition-old(root)"
: "::view-transition-new(root)",
}
)
})
}
</script>
<style>
.theme-container {
position: relative;
overflow: hidden;
}
.toggle-theme {
cursor: pointer;
border: none;
background: transparent;
padding: 8px;
border-radius: 50%;
transition: background-color 0.3s;
}
.toggle-theme:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.icon {
font-size: 20px;
}
</style>
3. 添加 CSS 样式
为了确保 View Transitions API 正常工作,我们需要添加一些 CSS 样式。这些样式可以放在全局样式文件中,例如 main.css
或 App.vue
的 <style>
标签中:
/* 禁用默认的 View Transitions 动画 */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
/* 设置亮色模式下的层级顺序 */
::view-transition-old(root) {
z-index: 1;
}
::view-transition-new(root) {
z-index: 2147483646;
}
/* 设置暗色模式下的层级顺序(与亮色模式相反) */
[data-bs-theme="dark"]::view-transition-old(root) {
z-index: 2147483646;
}
[data-bs-theme="dark"]::view-transition-new(root) {
z-index: 1;
}
4. 在主应用中使用主题切换组件
最后,我们需要在主应用中使用这个主题切换组件:
<template>
<div class="app">
<header>
<h1>我的应用</h1>
<ThemeToggle />
</header>
<main>
<!-- 应用内容 -->
</main>
</div>
</template>
<script setup>
import ThemeToggle from './components/ThemeToggle.vue'
</script>
<style>
/* 定义主题变量 */
:root {
--bg-color: #ffffff;
--text-color: #1a1a1a;
--card-bg: #f5f5f5;
--border-color: #e0e0e0;
}
:root[data-bs-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #ffffff;
--card-bg: #2d2d2d;
--border-color: #404040;
}
/* 应用主题变量 */
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
.app {
min-height: 100vh;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
main {
padding: 2rem;
}
/* 卡片样式 */
.card {
background-color: var(--card-bg);
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s, box-shadow 0.3s;
}
</style>
原理解析
View Transitions API 工作原理
捕获快照:当调用
document.startViewTransition()
时,浏览器会捕获当前 DOM 状态的快照。执行 DOM 更新:在回调函数中,我们执行 DOM 更新(例如切换主题)。
捕获新状态:浏览器捕获更新后的 DOM 状态快照。
创建过渡:浏览器使用这两个快照创建过渡动画。
自定义动画:我们可以通过 CSS 伪元素
::view-transition-old
和::view-transition-new
来自定义过渡动画。
圆形扩散效果实现
在我们的实现中,我们使用了 clipPath
属性来创建圆形扩散效果:
计算点击位置:获取用户点击的坐标。
计算结束半径:计算从点击位置到屏幕最远点的距离,确保圆形能够覆盖整个屏幕。
创建圆形裁剪路径:定义从点击位置开始的圆形裁剪路径,从 0 半径扩展到结束半径。
根据主题状态调整动画方向:根据当前主题状态决定动画的方向(从亮色到暗色或从暗色到亮色)。
兼容性处理
由于 View Transitions API 尚未在所有浏览器中得到支持,我们需要添加兼容性处理:
// 兼容性处理
if (!document.startViewTransition) {
toggleDark();
return;
}
这样,在不支持 View Transitions API 的浏览器中,主题切换仍然可以正常工作,只是没有动画效果。
优化建议
调整动画时长和缓动函数:可以根据需要调整动画的时长和缓动函数,以获得最佳的视觉效果。
添加更多主题变量:可以添加更多的 CSS 变量来控制不同元素的颜色,使主题切换更加灵活。
保存用户偏好:可以使用
localStorage
或sessionStorage
来保存用户的主题偏好,以便在下次访问时自动应用。添加系统主题跟随:可以使用
prefers-color-scheme
媒体查询来检测系统主题,并自动应用相应的主题。
希望本文能够帮助你实现一个漂亮的暗黑模式主题切换效果!如果有任何问题或建议,欢迎在评论区留言。