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