最近在給自己寫首頁(同時也是博客),我做了一個切換主題色的功能。每次進入頁面時,會隨機選擇一套配色,讓頁面顯得靈動一些,就像下面這樣:
這是如何實現的呢?不妨先從自定義顏色入手
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 中,看起來是這樣的:
顯然舒服多了。
剩下的工作該劃掉了#
如果希望修改主題色,只需要給根元素(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,
}
})