暗黑模式【闪白】解决方案
一、为什么刷新会“闪白”?
浏览器渲染流程:
- 下载 HTML → 2. 首次 Paint → 3. 加载 JS → 4. Vue 挂载 → 5. 读取 localStorage → 6. 设置主题
步骤 2 时JS 还没跑,不知道用户上次选的是 dark,于是按默认白色画了一遍;等步骤 6 完成再整体变暗 → 肉眼看到“闪一下”。
破解思路:在第一次 Paint 前就把主题类名写进 <html>。
二、Tailwind 打开暗黑开关
tailwind.config.js
export default {darkMode: 'class', // 手动控制模式content: ['./index.html','./src/**/*.{vue,ts}'],theme: { extend: {} },plugins: []
}
'class'表示只有<html class="dark">存在时,dark:bg-gray-900等样式才生效。- 区别于
'media'(跟随系统),适合做“一键切换”。
三、核心步骤
index.html
<head><meta charset="UTF-8" /><title>Dark/Light</title><script>(function () {const stored = localStorage.getItem('theme'); // 用户上次选择const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;if (stored === 'dark' || (!stored && prefersDark)) {document.documentElement.classList.add('dark');} else {document.documentElement.classList.remove('dark');}})();</script>
</head>
<body><div id="app"></div><script type="module" src="/src/main.ts"></script>
</body>
关键点
- 脚本放
<head>且不异步。 - 操作
document.documentElement即<html>元素。
四、matchMedia
window.matchMedia('(prefers-color-scheme: dark)')
- 只读“系统外观”的 API:返回
true→ 用户电脑/手机设的是“深色模式”。 - 作用:做默认 fallback——当用户第一次进站点、还没手动选过主题时,我们就按系统偏好给他配色,而不是硬编码成白色。
代码里怎么用
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
// 如果本地没存过主题,就用系统值
if (!stored) theme.value = prefersDark ? 'dark' : 'light';//`localStorage.getItem('theme')` 的返回值
实时同步系统变化
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {if (!localStorage.getItem('theme')) // 用户没手动选过theme.value = e.matches ? 'dark' : 'light';});
- 系统切深色 → 网页立即跟切;一旦用户手动点过按钮,就不再受系统影响。
五、切换 + 持久化
src/composables/useTheme.ts
import { ref, watchEffect } from 'vue'type Theme = 'light' | 'dark'export function useTheme() {// 初始值:先读本地,没有再读系统const theme = ref<Theme>((localStorage.getItem('theme') as Theme) ??(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'))// 主题变化 → 实时同步 <html> 与 localStoragewatchEffect(() => {const root = document.documentElementif (theme.value === 'dark') root.classList.add('dark')else root.classList.remove('dark')localStorage.setItem('theme', theme.value)})// 一键反转const toggleTheme = () => (theme.value = theme.value === 'light' ? 'dark' : 'light')return { theme, toggleTheme }
}
watchEffect让任何地方修改theme.value都能立即生效。- 纯函数,无组件依赖,可在 setup、pinia、路由守卫里随意调用。
六、组件内使用示例
<script setup lang="ts">
import { useTheme } from '@/composables/useTheme'
const { theme, toggleTheme } = useTheme()
</script><template><button@click="toggleTheme"class="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700"><span class="dark:hidden">dark</span><span class="hidden dark:inline">light</span></button>
</template>
- 无需
v-if,Tailwind 自动处理。
七、执行逻辑
浏览器解析 HTML↓
执行内联脚本(dark 类已就位)↓
首次 Paint → 颜色正确↓
Vue 加载 → 使用同一套主题变量
闪白根源被提前脚本扼杀在渲染前。
八、PWA优化功能
PWA = Progressive Web App(渐进式 Web 应用)
- 通过
manifest.json+Service Worker实现“像原生 App”的能力:- 桌面图标 / 启动屏 / 离线访问 / 推送
- 与暗黑模式的结合点
- 启动屏颜色跟随系统:
// public/manifest.json {"theme_color": "#1f2937", // 深色值"background_color": "#1f2937","display": "standalone" }- 双击图标全屏打开,主题仍是上次保存的 localStorage 值,体验一致性更好。
