基于Css Variable的主題切換完美解決方案(推薦)
當(dāng)接到這個(gè)需求的時(shí)候,百度到業(yè)界關(guān)于主題切換的方案還挺多的,css鏈接替換、className更改、less.modifyVars、css in js等等,但每一種方案聽(tīng)起來(lái)都是又累又貴。有沒(méi)有那種代碼侵入低,小白無(wú)腦又好維護(hù)的方案呢?那自然是有的,確切的說(shuō)是css它本身就支持。
Css3 Variable
定義一個(gè)全局顏色變量,改變這個(gè)變量的值頁(yè)面內(nèi)所有引用這個(gè)變量的元素都會(huì)進(jìn)行改變。好簡(jiǎn)單是不是?
// base.less
:root {
--primary: green;
--warning: yellow;
--info: white;
--danger: red;
}
// var.less
@primary: var(--primary)
@danger: var(--danger)
@info: var(--info)
// page.less
.header {
background-color: @primary;
color: @info;
}
.content {
border: 1px solid @danger;
}
// change.js
function changeTheme(themeObj) {
const vars = Object.keys(themeObj).map(key => `--${key}:${themeObj[key]}`).join(';')
document.documentElement.setAttribute('style', vars)
}
本文結(jié)束
個(gè)P,它不支持 IE ?。?!0202年還要兼容IE嗎?是的,就是要兼容IE。
css vars ponyfill
是的,還真有polyfill能兼容IE: css-vars-ponyfill 。它搞定IE的方式大概是這樣子的
+-------------------------+
| 獲取頁(yè)面內(nèi)style標(biāo)簽內(nèi)容 |
| 請(qǐng)求外鏈css內(nèi)容 |
+-------------------------+
|
|
v
+-------------------------+ 是 +-------------------------+
| 內(nèi)容是否含有var() | ----> | 標(biāo)記為src |
+-------------------------+ +-------------------------+
| |
| 否 |
v v
+-------------------------+ +-------------------------+
| 標(biāo)記為skip | | 將var(*)替換為變量值, |
| | | 新增style標(biāo)簽添加到head |
+-------------------------+ +-------------------------+
效果大概是這個(gè)樣子的
簡(jiǎn)單粗暴又不失優(yōu)雅,在支持css var的瀏覽器中不會(huì)進(jìn)行處理,所以不需要擔(dān)心性能問(wèn)題( 是IE的問(wèn)題,不是我的問(wèn)題
)。 我們來(lái)改造一下代碼
// store/theme.js
import cssVars from 'css-vars-ponyfill'
export default {
state: {
'primary': 'green',
'danger': 'white'
},
mutations: {
UPDATE_THEME(state, payload) {
const variables = {}
Object.assign(state, payload)
Object.keys(state).forEach((key) => {
variables[`--${key}`] = state[key]
})
cssVars({
variables
})
}
},
actions: {
changeTheme({ commit }, theme = {}) {
commit('UPDATE_THEME', theme)
}
}
}
// router.js
// 因?yàn)槁酚商D(zhuǎn)后的頁(yè)面會(huì)按需加載新的css資源,重新轉(zhuǎn)換
const convertedPages = new Set()
router.afterEach((to) => {
if (convertedPages.has(to.path)) return
convertedPages.add(to.path)
context.store.dispatch('theme/changeTheme')
})
SSR項(xiàng)目閃屏問(wèn)題優(yōu)化
在SSR項(xiàng)目中用上述方案你可能會(huì)在IE中看到這樣的情況
因?yàn)?css-vars-ponyfill
是依賴dom元素來(lái)實(shí)現(xiàn)轉(zhuǎn)換的,在node中無(wú)法使用,所以從server直出未轉(zhuǎn)換的css代碼到client加載js文件轉(zhuǎn)換css間存在一段樣式空檔。
+- - - - - - - - - - - - - - - - - - - -+
' 樣式空窗期: '
' '
+----------+ ' +----------------+ +------------+ ' +-------------+
| 發(fā)起請(qǐng)求 | --> ' | SSR直出頁(yè)面 | --> | 加載js依賴 | ' --> | 替換css變量 |
+----------+ ' +----------------+ +------------+ ' +-------------+
' '
+- - - - - - - - - - - - - - - - - - - -+
解決這個(gè)問(wèn)題也很簡(jiǎn)單,只需要在每個(gè)用到 css var 的地方加上一個(gè)兼容寫法
@_primary: red
@primary: var(--primary)
:root{
--primary: @_primary
}
.theme {
color: @primary;
}
// 改為
.theme {
color: @_primary;
color: @primary;
}
在不支持css var的瀏覽器上會(huì)渲染默認(rèn)顏色 red ,等待js加載完畢后ponyfill替換樣式覆蓋。
Webpack插件開(kāi)發(fā)
手動(dòng)在每個(gè)用到的地方添加兼容寫法既幸苦又不好維護(hù),這個(gè)時(shí)候我們需要了解一些 webpack 生命周期以及插件開(kāi)發(fā)相關(guān)的知識(shí),我們可以通過(guò)手寫一個(gè)webpack插件,在 normalModuleLoader ( v5版本被廢棄,使用NormalModule.getCompilationHooks(compilation).loader )的hooks中為所有css module添加一個(gè)loader來(lái)處理兼容代碼。
筆者項(xiàng)目使用了less,注意webpack中l(wèi)oader執(zhí)行順序是 類似棧的先進(jìn)后出 ,所以我需要把轉(zhuǎn)換loader添加到less-loader之前,確保我們處理的是編譯后的css var寫法而非less變量。
// plugin.js
export default class HackCss {
constructor (theme = {}) {
this.themeVars = theme
}
apply(compiler) {
compiler.hooks.thisCompilation.tap('HackCss', (compilation) => {
compilation.hooks.normalModuleLoader.tap(
'HackCss',
(_, moduleContext) => {
if (/\.vue\?vue&type=style/.test(moduleContext.userRequest)) {
// ssr項(xiàng)目同構(gòu)會(huì)有2次compiler,如果module中存在loader則不繼續(xù)添加
if (hasLoader(moduleContext.loaders, 'hackcss-loader.js')) {
return
}
let lessLoaderIndex = 0
// 項(xiàng)目用了less,找到less-loader的位置
moduleContext.loaders.forEach((loader, index) => {
if (/less-loader/.test(loader.loader)) {
lessLoaderIndex = index
}
})
moduleContext.loaders.splice(lessLoaderIndex, 0, {
loader: path.resolve(__dirname, 'hackcss-loader.js'),
options: this.themeVars
})
}
}
)
})
}
})
}
// loader.js
const { getOptions } = require('loader-utils')
module.exports = function(source) {
if (/module\.exports/.test(source)) return source
const theme = getOptions(this) || {}
return source.replace(
/\n(.+)?var\(--(.+)?\)(.+)?;/g,
(content, before, name, after = '') => {
const [key, indent] = before.split(':')
const add = after.split(';')[0]
return `\n${key}:${indent}${theme[name]}${after}${add};${content}`
}
)
}
至此,我們可以愉快自如的切換主題了。
后記
通過(guò)如何“懶得寫更多代碼”來(lái)吸收新知識(shí)會(huì)更加有趣, 希望這篇文章能夠幫助到你。
到此這篇關(guān)于基于Css Variable的主題切換完美解決方案(推薦)的文章就介紹到這了,更多相關(guān)css Variable的主題切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
- 本文主要介紹了CSS變量實(shí)現(xiàn)主題切換的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-06-23

詳解如何簡(jiǎn)單實(shí)現(xiàn)CSS主題的切換
這篇文章主要介紹了詳解如何簡(jiǎn)單實(shí)現(xiàn)CSS主題的切換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)2020-06-15- 今天,CSS預(yù)處理器是Web開(kāi)發(fā)的標(biāo)準(zhǔn)。 預(yù)處理器的一個(gè)主要優(yōu)點(diǎn)是它們使您能夠使用變量, 這有助于您避免復(fù)制和粘貼代碼,并簡(jiǎn)化了開(kāi)發(fā)和重構(gòu)。今天通過(guò)本文給大家分享如何將2018-11-14

關(guān)于CSS自定義屬性與前端頁(yè)面的主題切換問(wèn)題
這篇文章主要介紹了CSS自定義屬性與前端頁(yè)面的主題切換,CSS自定義屬性在一個(gè)css選擇器內(nèi)部進(jìn)行定義,使用兩個(gè)短橫連接線 -- 作為開(kāi)頭命名的名稱即被稱為自定義屬性,本文結(jié)2022-03-21



