Vue + Tailwind 2.0:使用 Vuex、localStorage 和用户默认偏好设置实现暗黑模式
TailwindCSS 2.0太棒了!原生暗黑模式、海量颜色,还有其他一大堆新功能。我正好在 Tailwind 2.0 发布的时候启动了一个新的Gridsome项目,用来记录我最近开发的 tea 依赖项,所以我想加入一些主题切换功能,预示着即将到来的暗黑时代。
我想要的那种条件主题有点复杂:
- 首次访问者应看到他们首选操作系统/浏览器的主题。
- 用户选择的主题应在其整个会话期间得到尊重。
- 用户选择的主题应该保存在本地存储中,这样他们返回时就无需费力地在用户界面上进行设置。
虽然 Tailwind 提供了手动选择主题的选项,但文档中关于手动驾驶的示例却含糊不清,故意没有具体说明。这CTRL+C CTRL+V在这里帮不上什么忙,所以我们只能在 VS Code 中自行摸索了。
让我们使用Vuex和localStorage来实现我们自己的有状态暗黑模式解决方案。
深层政府
在尝试将 Vuex 添加到现有的 Gridsome 项目中 45 分钟后(期间 Gridsome 官方文档似乎要把我送上刑场)console.error,我最终成功地创建了自己的 Vuex 商店,并为此感到自豪:
import Vue from 'vue'
import Vuex from 'vuex'
import theme from './modules/theme'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
theme
},
})
现在,在这个theme.js模块内部,我们可以使用一些简单的技巧,将大部分逻辑从前端隐藏起来。让我们从状态和变更开始:
export default {
state: {
theme: {}
},
mutations: {
SET_THEME(state, theme) {
state.theme = theme;
localStorage.theme = theme;
}
},
...
我们尽量简化了 mutation 操作,使其仅负责更新主题状态。将状态保存到 localStorage 中,可以让我们在用户关闭页面后再次访问时,能够检索到用户最近选择的主题,其作用类似于 cookie。
现在想想当一个新用户访问我们的网站时会发生什么。他们有可能在操作系统或浏览器中选择了浅色或深色主题,我们可以也应该尊重他们的选择。请注意,我们还没有初始化主题状态。这将是我们的第一个操作:
...
actions: {
initTheme({ commit }) {
const cachedTheme = localStorage.theme ? localStorage.theme : false;
// `true` if the user has set theme to `dark` on browser/OS
const userPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (cachedTheme)
commit('SET_THEME', cachedTheme)
else if (userPrefersDark)
commit('SET_THEME', 'dark')
else
commit('SET_THEME', 'light')
},
...
此操作将检查用户之前是否访问过该网站:
- 如果是这样,我们就使用他们缓存的主题偏好。
- 如果用户是新用户,我们会检查他们的系统是否设置为深色模式。如果是,我们会将我们的存储和缓存也设置为深色模式。
- 否则,我们将默认使用浅色模式。
我们可以从应用程序无处不在的根目录来执行此操作,无论用户从哪个入口进入,最终都会访问到该目录:
// for me this is `layouts/Default.vue`, but for you it may be `App.vue` or something else
export default {
beforeMount() {
this.$store.dispatch("initTheme");
},
...
现在我们需要一个开关来切换主题。
在创建该组件之前,让我们先回去theme.js添加逻辑:
...
// This simply flips whatever was most recently committed to storage.
toggleTheme({ commit }) {
switch (localStorage.theme) {
case 'light':
commit('SET_THEME', 'dark')
break;
default:
commit('SET_THEME', 'light')
break;
}
}
},
getters: {
getTheme: (state) => {
return state.theme;
}
},
}
Tailwind 2.0 的dark课程
接下来,我们可以添加一些属性,以便在深色模式下有条件地渲染。然后,我们将设置一个监视器,它会对主题选择的更改做出反应,运行一个函数,该函数会将 Tailwind 的darkCSS 类添加到我们应用程序的根节点,或从中移除类:
<template>
<main
class="min-h-screen
bg-green-50 text-gray-700
dark:bg-gray-900 dark:text-purple-50">
<ThemeToggler/>
<slot />
</main>
</template>
<script>
import { mapGetters } from "vuex";
import ThemeToggler from "../components/ThemeToggler.vue";
export default {
components: {
ThemeToggler,
},
beforeMount() {
this.$store.dispatch("initTheme");
},
computed: {
...mapGetters({ theme: "getTheme" }),
},
watch: {
theme(newTheme, oldTheme) {
newTheme === "light"
? document.querySelector("html").classList.remove("dark")
: document.querySelector("html").classList.add("dark");
},
},
};
</script>
最后,我们还要介绍最后一个部件,我们那令人极其失望的切换器:
<template>
<button
@click="toggleTheme"
class="dark:text-red-400 text-cyan-200">
Theme Toggle
</button>
</template>
<script>
export default {
methods: {
toggleTheme() {
this.$store.dispatch("toggleTheme");
},
},
};
</script>
创作自由
这很棒,因为你可以尽情发挥创意,用 SVG 替换文本,并添加各种各样的过渡效果来实现状态转换。你可以使用 Vue 内置的过渡元素或 Tailwind 的过渡类,或者两者都用!
如果您有任何问题、建议或更优雅的解决方案,请在下方留言!我发现有几个地方可以做得更优雅,例如使用 Vue 的条件类而不是直接给dark根元素添加或移除类。