在 tailwind css 中实现一键切换颜色主题
主题切换是非常常见的需求,但是 tailwind css官方文档中只有固定主题配置的介绍,无法做到动态的切换主题。今天向大家介绍在 react 项目中,使用css变量
实现动态切换颜色主题的方法。
一、tailwind css 相关配置
1.在全局 css 中添加颜色主题
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
.theme-nord {
--color-primary: 94 129 172;
--color-secondary: 129 161 193;
--color-background: 46 52 64;
--color-foreground: 216 222 233;
--color-separator: 76 86 106;
}
.theme-solarizedDark {
--color-primary: 38 139 210;
--color-secondary: 203 75 22;
--color-background: 0 43 54;
--color-foreground: 238 232 213;
--color-separator: 131 148 150;
}
.theme-solarizedLight {
--color-primary: 211 54 130;
--color-secondary: 133 153 0;
--color-background: 253 246 227;
--color-foreground: 101 123 131;
--color-separator: 88 110 117;
}
我们的主题类名有个统一的theme
前缀,这是为了防止与自定义的 tailwind 类型冲突,你可以随意修改它。另外,注意到我们的颜色色值都是使用R G B
这样的格式,这是为了匹配 tailwind 对颜色透明度配置的写法,如bg-primary[0.2]
如果采用 16 进制写法的话,这将是个无效类名。如有大佬有办法解决的话,欢迎评论区指出~
2.配置 tailwind.config.js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
backgroundColor: {
primary: "rgb(var(--color-primary) / <alpha-value>)",
secondary: "rgb(var(--color-secondary) / <alpha-value>)",
background: "rgb(var(--color-background) / <alpha-value>)",
foreground: "rgb(var(--color-foreground) / <alpha-value>)",
separator: "rgb(var(--color-separator) / <alpha-value>)",
},
textColor: {
primary: "rgb(var(--color-foreground) / <alpha-value>)",
},
},
},
plugins: [],
darkMode: "class",
};
重点关注backgroundColor
和textColor
这两个对象中的内容,他们分别是背景颜色和文字颜色的配置。其中的各种属性名就是我们在元素类名中使用的颜色名称,即bg-primary
将会呈现--color-primary
这个 css 变量的颜色。那么,"rgb(var(--color-primary) / <alpha-value>)"
这一坨是什么意思呢?这就是实现透明度配置bg-primary[0.2]
的写法,参见官方文档。如此,tailwind 方面的配置就做完了,接下来我们在 react 中实现一个主题切换小 demo。
二、React 主题切换 demo
1.实现主题切换基础版
// App.tsx
import React, { useState } from "react";
function App() {
const themes = ["nord", "solarizedDark", "solarizedLight"];
const [theme, setTheme] = useState("nord");
return (
<div
className={`
theme-${theme}
bg-background
w-screen h-screen
flex flex-col justify-center items-center
`}
>
<p className="mb-10 text-primary">当前主题:{theme}</p>
<span className="text-primary/[0.5]">点击下方按钮切换主题</span>
<div className="mt-10">
{themes.map((theme) => (
<button
className="border rounded p-2 mr-5 bg-secondary text-white"
onClick={() => setTheme(theme)}
>
{theme}
</button>
))}
</div>
</div>
);
}
export default App;
还记得我们主题的命名规则吗?theme
+主题名,我们将这个类名加到最顶层元素中,通过通过状态切换来改变主题类名,由此就完成了颜色变量的动态切换,配合tailwind.config.js
中对backgroundColor
和textColor
的配置,由此bg-primary
和text-primary
这两个类名让我们实现了颜色的动态切换。
2.实现主题切换进阶版
基础版 demo 实现了需求,但是有亿点小问题。首先是没有持久化,每次刷新页面就变回了默认的主题,其次是没有redux
这样的全局状态管理,我们在子组件中实现切换主题会很麻烦。以上两个问题都可以通过引入状态管理库解决,目前有很多选择比如redux
、mobx
、zustand
等。我这边决定使用zustand
。
实现useThemeStore
// store/themeStore.ts
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
type theme = "solarizedDark" | "nord" | "solarizedLight";
type themeStore = {
themes: theme[];
activeTheme: theme;
setTheme: (theme: theme) => void;
};
const useThemeStore = create<themeStore>()(
// 持久化中间件
persist(
(set) => ({
themes: ["solarizedDark", "nord", "solarizedLight"],
activeTheme: "nord",
setTheme: (activeTheme: theme) => set(() => ({ activeTheme })),
}),
{
name: "active-theme", // 存储在storage中的key名
// storage: createJSONStorage(() => sessionStorage), // 存储数据库配置,默认使用localstorage
// 过滤函数
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(([key]) => key === "activeTheme")
),
}
)
);
export default useThemeStore;
跟着zustand 文档就能简单实现。注意persist
这个函数是实现持久化用的,第二个入参对象中的name
属性就是我们存储的 key 值,storage
属性允许我们自定义存储的数据库,默认使用localstorage
。
在组件中使用
// App.tsx
import React from "react";
import useThemeStore from "./stores/themeStore";
function App() {
const { themes, activeTheme, setTheme } = useThemeStore((state) => state);
return (
<div
className={`
theme-${activeTheme}
bg-background
w-screen h-screen
flex flex-col justify-center items-center
`}
>
<p className="mb-10 text-primary">当前主题:{activeTheme}</p>
<span className="text-primary/[0.5]">点击下方按钮切换主题</span>
<div className="mt-10">
{themes.map((theme) => (
<button
className="border rounded p-2 mr-5 bg-secondary text-white"
onClick={() => setTheme(theme)}
>
{theme}
</button>
))}
</div>
</div>
);
}
export default App;
现在查看localstorage
就可以看到我们存储的对象,这样即便刷新页面我们选择的主题也会保存。同时useThemeStore
这个 hook 可以在任意组件中使用,于是子组件切换主题也得以实现了。