最近在给自己写主页(同时也是博客),我做了一个切换主题色的功能。每次进入页面时,会随机选择一套配色,让页面显得灵动一些,就像下面这样:
这是如何实现的呢?不妨先从自定义颜色入手
// windi.config.js
export default defineConfig({
theme: {
extend: {
colors: {
primary: "#2196f3",
},
},
},
})
这样就定义了一个 primary
的颜色,之后就能正常使用了,(如 bg-primary
/ text-primary
)
当然,不仅可以传递字符串,还能够使用对象定义一组颜色:(如 bg-primary-light
)
// windi.config.js
export default defineConfig({
theme: {
extend: {
colors: {
primary: {
extralight: "#d3eafd",
light: "#b2dafb",
medium: "#6ebbf7",
DEFAULT: "#2196f3"
},
},
},
},
})
为了使颜色可变,使用 CSS 变量会方便许多,WindiCSS 当然也是支持的:
:root {
--color-primary-extralight: #d3eafd;
--color-primary-light: #b2dafb;
--color-primary-medium: #6ebbf7;
--color-primary: #2196f3;
--color-primary-dark: #27415b;
}
// windi.config.js
export default defineConfig({
theme: {
extend: {
colors: {
primary: {
extralight: 'var(--color-primary-extralight)',
light: 'var(--color-primary-light)',
medium: 'var(--color-primary-medium)',
DEFAULT: 'var(--color-primary)',
dark: 'var(--color-primary-dark)',
},
},
},
},
})
这样就能够基本实现在 WindiCSS 中使用 CSS 变量了,不过还有一个小问题:
WindiCSS 支持为颜色设置透明度,例如 bg-gray-800/80
bg-gray-800 bg-opacity-80
这两种写法。上面的配置方式会导致这种语法失效(丢失透明度)。所以,我们需要给CSS变量换一种形式。同时,需要一个高阶工具函数来包装一下变量:
:root {
--color-primary-extralight: 211 234 253;
--color-primary-light: 178 218 251;
--color-primary-medium: 110 187 247;
--color-primary: 33 150 243;
--color-primary-dark: 39 65 91;
}
// windi.config.js
function withOpacityValue(variable) {
return val => {
if (val.opacityValue === undefined) {
return `rgb(var(${variable}))`
}
return `rgb(var(${variable}) / ${val.opacityValue})`
}
}
export default defineConfig({
theme: {
extend: {
colors: {
primary: {
extralight: withOpacityValue('--color-primary-extralight'),
light: withOpacityValue('--color-primary-light'),
medium: withOpacityValue('--color-primary-medium'),
DEFAULT: withOpacityValue('--color-primary'),
dark: withOpacityValue('--color-primary-dark'),
},
},
},
},
})
如此一来,每当使用 primary
颜色时,WindiCSS 都会调用函数来生成样式,通过对 opacityValue
的判断来实现对透明度语法的支持。
显然,如果手动为 light
extralight
等颜色变种指定颜色值是不现实的,况且现在需要用 R G B 三个数字来表示颜色,编辑器没有高亮,不直观,也会导致维护困难。
这时候SCSS就能派上用场了!SCSS提供了基础的CSS数据类型,判断、遍历语法,同时也提供了海量的工具函数(例如 red()
blue()
green()
等用于通道分离,mix()
用于颜色混合)
首先来实现一个工具函数,将传入的十六进制颜色转换成 R G B 三个数字的形式
@function getColorValue($color) {
@return #{red($color)} #{green($color)} #{blue($color)};
}
/* getColorValue(#2196f3) -> 33 150 243 */
我预想中的情况是——只要给一个 primary
的基础色,SCSS就能帮我把 light
extralight
等颜色变种都生成出来。我是用 mix
方法来实现:
@mixin spread-theme-map($map: ()) {
@each $key, $value in $map {
#{"--"+$key}: $value;
}
}
@function theme-primary-map($primary-color: #2196f3) {
@return (
color-primary-dark: getColorValue(mix($primary-color, black, 30%)),
color-primary: getColorValue($primary-color),
color-primary-medium: getColorValue(mix($primary-color, white, 70%)),
color-primary-light: getColorValue(mix($primary-color, white, 35%)),
color-primary-extralight: getColorValue(mix($primary-color, white, 15%))
);
}
/* spread-theme-map(theme-primary-map(#2196f3)) */
这样,就能针对某一个颜色生成对应的系列颜色属性了。接下来,只需要定义一个数组,把需要的主题色放进去,跑个循环即可(从 Material Design 的文档里随便挑了几个养眼的颜色):
$themeColorList: (
#2196f3,
#f44336,
#9c27b0,
#4caf50,
#3f51b5,
#795548,
#607d8b,
#009688
);
@for $i from 1 through length($themeColorList) {
$color: nth($themeColorList, $i);
.theme-#{$i} {
@include spread-theme-map(theme-primary-map($color));
}
}
在VSCode中,看起来是这样的:
显然舒服多了。
如果希望修改主题色,只需要给根元素(html
或 body
)增加对应类名即可(例如 theme-1
/ theme-2
),实现的方式很多,因为我使用了 Nuxt.js,下面是我的解决方案。
const randomThemeColorIndex = useState('randomThemeColorIndex', () =>
Math.floor(Math.random() * themeColorList.length) + 1
)
useHead({
bodyAttrs: {
class: 'theme-' + randomThemeColorIndex.value,
}
})