使用 Vue 3 的 Composition API、 TypeScript 以及 <script setup>
语法糖,实现动态主题切换,能够在 ‘light’(明亮)、‘dark’(暗黑)和 ‘system’(系统)主题之间切换,并且使用 localStorage
做了状态缓存。代码十分精简,提供 JavaScript 和 TypeScript 版本,也可以在线体验此功能:play.vuejs.org。
const theme = ref<ThemeType>((localStorage.getItem('theme') as ThemeType) || 'light')
使用 Vue 的 ref
函数创建一个响应式引用,保存当前选择的主题。尝试从 localStorage
获取保存的主题,如果没有则默认为 'light'
。watch
来观察 theme
的变化,当 theme
变化时修改 html 的 class,当 theme
设置为 ‘system’ 时,会根据当前系统的主题来设置对应的 class。setTheme
函数用来将用户选择的主题保存到 localStorage
并更新 theme
。handleChange
函数为 <select>
元素的 change
事件提供处理逻辑,它会调用 setTheme
以更新并保存用户选择的新主题。显示当前主题,并有一个下拉菜单让用户选择主题。
定义了两个 css 变量,用于设置背景颜色和文字颜色。.dark
类通过覆盖 CSS 变量,实现暗黑模式的配色方案。
<script setup lang="ts">
import { ref, watch } from 'vue'
type ThemeType = 'light' | 'dark' | 'system'
const theme = ref<ThemeType>((localStorage.getItem('theme') as ThemeType) || 'light')
watch(
theme,
(val) => {
const root = window.document.documentElement
root.classList.remove('light', 'dark')
if (theme.value === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
root.classList.add(systemTheme)
return
}
root.classList.add(val)
},
{
immediate: true,
}
)
const setTheme = (value: ThemeType) => {
localStorage.setItem('theme', value)
theme.value = value
}
const handleChange = (
event: Event & {
target: HTMLSelectElement
}
) => {
const value = event.target.value as ThemeType
setTheme(value)
}
</script>
<template>
<div>
{{ theme }}
<select v-model="theme" @change="handleChange">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
</div>
</template>
<style>
:root {
--background: #ffffff;
--text: #09090b;
}
.dark {
--background: #09090b;
--text: #f9f9f9;
}
html,
body,
#app {
height: 100%;
}
body {
background-color: var(--background);
color: var(--text);
}
</style>
<script setup>
import { ref, watch } from 'vue'
const theme = ref((localStorage.getItem('theme')) || 'light')
watch(
theme,
(val) => {
const root = window.document.documentElement
root.classList.remove('light', 'dark')
if (theme.value === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
root.classList.add(systemTheme)
return
}
root.classList.add(val)
},
{
immediate: true,
}
)
const setTheme = (value) => {
localStorage.setItem('theme', value)
theme.value = value
}
const handleChange = (event) => {
const value = event.target.value
setTheme(value)
}
</script>
<template>
<div>
{{ theme }}
<select v-model="theme" @change="handleChange">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
</div>
</template>
<style>
:root {
--background: #ffffff;
--text: #09090b;
}
.dark {
--background: #09090b;
--text: #f9f9f9;
}
html,
body,
#app {
height: 100%;
}
body {
background-color: var(--background);
color: var(--text);
}
</style>