戴兜

戴兜的小屋

Coding the world.
github
bilibili
twitter

SCSS+WindiCSS實現主題色切換

最近在給自己寫首頁(同時也是博客),我做了一個切換主題色的功能。每次進入頁面時,會隨機選擇一套配色,讓頁面顯得靈動一些,就像下面這樣:

image

這是如何實現的呢?不妨先從自定義顏色入手

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 中,看起來是這樣的:

image

顯然舒服多了。

剩下的工作該劃掉了#

如果希望修改主題色,只需要給根元素(htmlbody)增加對應類名即可(例如 theme-1 / theme-2),實現的方式很多,因為我使用了 Nuxt.js,下面是我的解決方案。

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

useHead({
  bodyAttrs: {
    class: 'theme-' + randomThemeColorIndex.value,
  }
})
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。