做者:令夕
css
https://juejin.im/post/5e92ad7a518825736c5b91cdhtml
最近在作網站換膚的需求,也就是主題切換。那麼如何切換主題的顏色呢?如下是網站換膚的實現以及基於換膚拓展的一些方案分享給你們,但願你們在作相似需求的時候可以有些參考。vue
覆蓋樣式實現
// light
$color-brand1: #ffcd32;
$fill-1: #fff !default;
$color-text: #3c3c3c;
$color-text-1: #757575;
$color-text-2: #222;
// dark
$dark-fill-1: #222 !default; // 品牌色
$dark-color-text: #fff;
$dark-color-text-1: rgba(255, 255, 255, 0.3);
$dark-color-text-2: $color-brand1;
// 頁面使用
<style lang="scss">
@import "./assets/scss/index.scss";
[data-theme="dark"] {
body {
background: $dark-fill-1;
}
.reaconmend .reaconmend-list .item .name {
color: $dark-color-text;
}
.reaconmend .reaconmend-list .item .desc {
color: $dark-color-text-1;
}
.header .text {
color: $dark-color-text-2;
}
}
</style>
利用css優先級的原理覆蓋掉原有樣式的實現,每定義一套皮膚就要定義對應的sass變量,以及定義一套覆蓋原有樣式的皮膚樣式。若是有多套皮膚的話,覆蓋的代碼量就會n套。node
缺點: 樣式不易管理,查找樣式複雜,開發效率低,拓展性差,維護成本高,多人協做溝通麻煩。git
sass變量實現
// variable.scss
// 淺色
$colors-light: (
fill-1: #fff,
text: #3c3c3c,
text-1: #757575,
text-2: #222,
);
// 深色
$colors-dark: (
fill-1: #222,
text: #fff,
text-1: rgba(255, 255, 255, 0.3),
text-2: #ffcd32,
);
// mixin.scss
// 背景色
@mixin bg-color($key) {
background-color: map-get($colors-light, $key);
[data-theme="dark"] & {
background-color: map-get($colors-dark, $key);
}
}
// text色
@mixin text-color($key) {
color: map-get($colors-light, $key);
[data-theme="dark"] & {
color: map-get($colors-dark, $key);
}
}
// 頁面使用
<style lang="scss" rel="stylesheet/scss">
@import "../../../assets/scss/variable.scss";
@import "../../../assets/scss/mixin.scss";
.reaconmend-list {
.list-title {
height: 40px;
line-height: 40 px;
text-align: center;
@include text-color(text-1);
}
}
</style>
如上所示用到的知識點包含Sass變量(variable),嵌套(nestend rules),混合(mixins), Sass Maps的函數-map-get( key)。github
Maps的含義:Maps可視爲鍵值對的集合,鍵被用於定位值 在css種沒有對應的概念。和Lists不一樣Maps必須被圓括號包圍,鍵值對被都好分割 。Maps中的keys和values能夠是sassscript的任何對象。(包括任意的sassscript表達式 arbitrary SassScript expressions) 和Lists同樣Maps主要爲sassscript函數服務,如 map-get函數用於查找鍵值,map-merge函數用於map和新加的鍵值融合,@each命令可添加樣式到一個map中的每一個鍵值對。Maps可用於任何Lists可用的地方,在List函數中 Map會被自動轉換爲List , 如 (key1: value1, key2: value2)會被List函數轉換爲 key1 value1, key2 value2 ,反之則不能。(網友Soledad提供)web
使用scss變量換膚相比覆蓋樣式express
-
拓展性更強 -
將換膚的邏輯進行了收斂
生成多套皮膚css
使用覆蓋樣式實現與scss變量實現會把多套皮膚的樣式都編譯到一個css文件裏面,若是有多套皮膚樣式,這個文件是會很是大的。爲了解決這樣的問題,就天然的想出了拆分scss的實現:npm
實現方案,經過編譯工具與構建工具編譯出多套皮膚css,經過js動態的link對應的皮膚樣式瀏覽器
// js動態處理
var theme = /\bt=(\w+)/.exec(location.search);
theme = theme ? theme[1] : "light";
changeTheme(theme);
function changeTheme(theme) {
var head = document.getElementsByTagName("head")[0];
var link = document.createElement("link");
link.dataset.type = "theme";
link.href = "assets/css/theme-" + theme + "/pages/home/home.css";
link.rel = "stylesheet";
link.type = "text/css";
head.appendChild(link);
}
CSS變量實現
// variable.scss
// 默認變量
:root {
--fill-1: #fff;
--text: #3c3c3c;
--text-1: #757575;
--text-2: #222;
--font-size-large: 18px;
--font-size-large-x: 22px;
--font-size-medium: 14px;
--font-size-medium-x: 16px;
--font-size-small-s: 10px;
--font-size-small: 12px;
}
// 深色變量
[data-theme="dark"] {
--fill-1: #222;
--text: #fff;
--text-1: rgba(255, 255, 255, 0.3);
--text-2: #ffcd32;
}
在頁面對css變量作引入使用
// 頁面使用
@import "../../assets/scss/variable.scss";
.header {
position: relative;
height: 70px;
text-align: center;
font-size: 0;
.text {
display: inline-block;
vertical-align: top;
line-height: 70px;
font-size: var(--font-size-large);
color: var(--text-2);
}
}
具體的實現效果:
問題點:css變量會存在兼容性問題
css變量兼容性以下:雖然如今大部分主流瀏覽器均可以兼容,可是還要考慮更多的兼容性這塊的請往下看:
CSS變量兼容性實現-1
在css變量的基礎上新增了postcss-custom-properties這個插件 安裝依賴:npm install postcss-custom-properties --save-dev npm install postcss-loader --save-dev
在根目錄新建postcss.config.js增長配置,配置以下:
const postcssCustompProperties = require("postcss-custom-properties");
module.exports = {
plugins: [
postcssCustompProperties({
importFrom: "src/assets/scss/variable.scss"
})
]
};
postcss 會將css自定義變量直接編譯爲肯定值
,而不是保留
。這時就須要 postcss 插件
來爲咱們保留這些自定義變量,使用 postcss-custom-properties效果以下:
-
優勢:會生成一套與css變量對應的css -
缺點:在構建時根據css變量生成對應的css,換膚是運行時並不能生成對應的css。
換膚後樣式:
CSS變量兼容性實現-2
首先須要建一個存放公共css變量的js文件,將須要定義的css變量存放到該js文件,例如(variable.js)
// variable.js
// 字體變量
const baseSize = {
"--font-size-large-x": "22px",
"--font-size-large": "18px",
"--font-size-medium": "14px",
"--font-size-medium-x": "16px",
"--font-size-small-s": "10px",
"--font-size-small": "12px",
};
//淺色
export const lightTheme = {
"--fill-1": "#fff",
"--text": "#3c3c3c",
"--text-1": "#757575",
"--text-2": "#222",
...baseSize,
};
// 深色
export const darkTheme = {
"--fill-1": "#222",
"--text": "#fff",
"--text-1": "rgba(255, 255, 255, 0.3)",
"--text-2": "#ffcd32",
...baseSize,
};
頁面使用css變量,例如:
<style lang="scss">
.text {
display: inline-block;
vertical-align: top;
line-height: 70px;
font-size: var(--font-size-large);
color: var(--text-2);
}
</style>
安裝css-vars-ponyfill 插件
css-vars-ponyfill官方概念:在傳統瀏覽器和現代瀏覽器中爲CSS自定義屬性(又名「CSS變量」)提供客戶端支持的ponyfill。(具體用法與概念請查閱官方網站:css-vars-ponyfill)
封裝切換主題的js,在main.js作初始化調用
// theme.js
import { lightTheme, darkTheme } from "../src/assets/js/variable";
import cssVars from "css-vars-ponyfill";
export const initTheme = (theme) => {
document.documentElement.setAttribute("data-theme", theme ? "light" : "dark");
cssVars({
watch: true, // 當添加,刪除或修改其<link>或<style>元素的禁用或href屬性時,ponyfill將自行調用
variables: theme ? lightTheme : darkTheme, // variables 自定義屬性名/值對的集合
onlyLegacy: false, // false 默認將css變量編譯爲瀏覽器識別的css樣式 true 當瀏覽器不支持css變量的時候將css變量編譯爲識別的css
});
};
在切換主題的按鈕組件中調用
總結:css自定義屬性 + css-vars-ponyfill(解決兼容性) 預覽效果
細心的小夥伴們,必定發現了這裏的css變量已經編譯成瀏覽器可識別的css樣式了。
ElementUI實現
官方的實現解釋
-
先把默認主題文件中涉及到顏色的 CSS 值替換成關鍵詞:github.com/ElementUI/t… -
根據用戶選擇的主題色生成一系列對應的顏色值:github.com/ElementUI/t… -
把關鍵詞再換回剛剛生成的相應的顏色值:github.com/ElementUI/t… -
直接在頁面上加 style
標籤,把生成的樣式填進去:github.com/ElementUI/t…
已實現的連接參考:https://juejin.im/post/5ca41617f265da3092006155#heading-1
less在線編譯實現
根據less能夠直接 編譯less變量實現的步驟以下:
// variable.less 定義less變量
// 公共字體
@font-size-large-x: 22px;
@font-size-large: 18px;
@font-size-medium: 14px;
@font-size-medium-x: 16px;
@font-size-small-s: 10px;
@font-size-small: 12px;
// 淺色
@fill-1: #fff;
@text: #3c3c3c;
@text-1: #757575;
@text-2: #222;
// 頁面使用 例如:
// 下面.text的css 以下,這裏的 @font-size-large 和 @text-2就是 less 變量:
.text {
display: inline-block;
vertical-align: top;
line-height: 70px;
font-size: @font-size-large;
color: @text-2;
}
當點擊換膚按鈕的時候,直接去加載 less.js
,具體代碼以下:
<template>
<div class="header">
<div class="text">小恐龍換膚</div>
<div role="switch" class="switch" :class="theme === true ? 'is-checked' : ''">
<input type="checkbox" class="switch-input" />
<span class="switch-core" @click="changeTheme"></span>
</div>
</div>
</template>
<script>
import { lightTheme, darkTheme } from "../../assets/js/variable";
export default {
name: "m-header",
data() {
return {
theme: true
};
},
methods: {
changeTheme() {
this.theme = !this.theme;
// 調用 `less.modifyVars` 方法來改變變量值
window.less.modifyVars(this.theme ? lightTheme : darkTheme);
}
},
mounted() {}
};
</script>
定義variable.js是由於若是直接將less變量放在modifyVars中切換的效果只會生效一次,因此根據切換的狀態使用對應的less變量。
//淺色
export const lightTheme = {
"@fill-1": "#fff",
"@text": "#3c3c3c",
"@text-1": "#757575",
"@text-2": "#222",
};
// 深色
export const darkTheme = {
"@fill-1": "#222",
"@text": "#fff",
"@text-1": "rgba(255, 255, 255, 0.3)",
"@text-2": "#ffcd32",
};
而後點擊色塊進行試驗,發現並無生效,這是why?而後就去看了其文檔,原來它會找到全部以下的less 樣式標籤,而且使用已編譯的css同步建立 style 標籤。也就是說咱們必須吧代碼中全部的less 都如下面這種link的方式來引入,這樣less.js
才能在瀏覽器端實現編譯。
<link rel="stylesheet/less" type="text/css" href="index.less" />
這裏我用了vue,因此直接把 less
文件放在了public
目錄下,而後在html中直接引入:
點擊切換按鈕,可見background和color確實都變了
注:使用less 來實現換膚要注意
less 文件
在html
中編寫的位置,否則極可能被其餘css 文件所幹擾致使換膚失敗。若是less文件特別大,會存在編譯性能問題。
拓展-圖片切換
以上的方案---只是對background-color和color進行的換膚,若是要對圖片進行換膚該怎麼辦吶?
圖片切換
項目中還存在不少佔位圖或者其餘圖片會隨着主題的變化而變化。經過引入全部圖片,並用文件名來區分不一樣主題所對應的圖片。在點擊切換主題時,切換到主題所對應的文件,就能實現圖片切換了。
<img class :src="avatar" alt />
// 頁面實現
<template>
<div class="header">
<div class="text">小恐龍換膚</div>
<div role="switch" class="switch" :class="theme === true ? 'is-checked' : ''">
<input type="checkbox" class="switch-input" />
<span class="switch-core" @click="changeTheme"></span>
</div>
</div>
</template>
<script>
import { initTheme } from "../../theme";
import bus from "../../bus";
export default {
name: "m-header",
data() {
return {
theme: true, // false深色主題
avatar: ""
};
},
methods: {
changeTheme() {
this.theme = !this.theme;
initTheme(this.theme);
this.setThemeValue(this.theme);
bus.$emit("changeTheme", this.theme);
},
setThemeValue(theme) {
theme = theme ? "light" : "dark";
this.avatar = require(`@/assets/images/logo-${theme}.jpeg`);
}
},
created() {
this.setThemeValue(this.theme);
}
};
</script>
在點擊切換主題時,會發射一個 changeTheme
事件,各組件接收到 changeTheme
事件,就會爲圖片從新賦值,也就達到了切換圖片的效果。
最後
很感謝能在百忙中抽出時間把這篇文章看完的小夥伴:) 若是還有什麼疑問或者建議,能夠多多交流,原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。好了,本文到此結束,但願對你有幫助 :)
本文分享自微信公衆號 - Vue中文社區(vue_fe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。