Note-45089-92

Token ID: 4

ERC-721 1 Transfers

Metadata

{
  "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"
    }
  ]
}