惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
C
Check Point Blog
博客园_首页
B
Blog
D
Docker
U
Unit 42
量子位
I
InfoQ
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
GbyAI
GbyAI
L
LangChain Blog
云风的 BLOG
云风的 BLOG
博客园 - Franky
美团技术团队
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
Vercel News
Vercel News
Recent Announcements
Recent Announcements
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Google DeepMind News
Google DeepMind News

戴兜的小屋

Hi! 2024 - 戴兜的小屋 优雅地实现滚动容器遮罩 - 戴兜的小屋 [简中] Google Summer of Code & Chrome Extensions - 戴兜的小屋 Google Summer of Code & Chrome Extensions - 戴兜的小屋 Plaid CTF Writeup [Treasure Map/CSS] - 戴兜的小屋 写一个炫酷的个人名片页✨✨ - 戴兜的小屋 【戴兜的游戏安利】Hi-Fi RUSH,不可多得的动作爽游 - 戴兜的小屋 Manifest V3扩展Content Script绕过CSP限制点击页面内元素 - 戴兜的小屋 在 Nuxt.js 中配合 windicss 实现暗黑模式适配 - 戴兜的小屋 实现滚动时Header自动隐藏 - 戴兜的小屋 你好!2023 - 戴兜的小屋 !important导致TransitionGroup失效 - 戴兜的小屋 新年快乐! - 戴兜的小屋 CSS Layout API初探:瀑布流布局实现 - 戴兜的小屋 你好! 2022 - 戴兜的小屋 手撸一个前端天气卡片 - 戴兜的小屋 Hi!2021 - 戴兜的小屋 服务器又搬迁啦! - 戴兜的小屋 base64数据隐写实现原理分析 - 戴兜的小屋
SCSS+WindiCSS实现主题色切换 - 戴兜的小屋
戴兜文章作者 · 2023-04-25 · via 戴兜的小屋

最近在给自己写主页(同时也是博客),我做了一个切换主题色的功能。每次进入页面时,会随机选择一套配色,让页面显得灵动一些,就像下面这样:

这是如何实现的呢?不妨先从自定义颜色入手

WindiCSS 自定义颜色

定义一个固定的颜色

// 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变量

为了使颜色可变,使用 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 的判断来实现对透明度语法的支持。

SCSS 生成 CSS 变量

显然,如果手动为 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中,看起来是这样的:

显然舒服多了。

剩下的工作该划掉了

如果希望修改主题色,只需要给根元素(htmlbody)增加对应类名即可(例如 theme-1 / theme-2),实现的方式很多,因为我使用了 Nuxt.js,下面是我的解决方案。

const randomThemeColorIndex = useState('randomThemeColorIndex', () =>
  Math.floor(Math.random() * themeColorList.length) + 1
)

useHead({
  bodyAttrs: {
    class: 'theme-' + randomThemeColorIndex.value,
  }
})