前端一鍵換膚的N種方案,請收下






做者:令夕
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-1rgba(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-1rgba(255, 255, 255, 0.3),
  text-2#ffcd32,
);
// mixin.scss
// 背景色
@mixin bg-color($key) {
  background-colormap-get($colors-light, $key);
  [data-theme="dark"] & {
    background-colormap-get($colors-dark, $key);
  }
}
// text
@mixin text-color($key) {
  colormap-get($colors-light, $key);
  [data-theme="dark"] & {
    colormap-get($colors-dark, $key);
  }
}

// 頁面使用

<style lang="scssrel="stylesheet/scss">
@import "../../../assets/scss/variable.scss";
@import "../../../assets/scss/mixin.scss";
.reaconmend-list {
    .list-title {
      height40px;
      line-height40 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-large18px;
  --font-size-large-x22px;
  --font-size-medium14px;
  --font-size-medium-x16px;
  --font-size-small-s10px;
  --font-size-small12px;
}
// 深色變量
[data-theme="dark"] {
  --fill-1#222;
  --text#fff;
  --text-1rgba(255, 255, 255, 0.3);
  --text-2#ffcd32;
}

在頁面對css變量作引入使用

// 頁面使用
@import "../../assets/scss/variable.scss";

.header {
  position: relative;
  height70px;
  text-align: center;
  font-size0
  .text {
    display: inline-block;
    vertical-align: top;
    line-height70px;
    font-sizevar(--font-size-large);
    colorvar(--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-height70px;
    font-sizevar(--font-size-large);
    colorvar(--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({
    watchtrue// 當添加,刪除或修改其<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-x22px;
@font-size-large18px;
@font-size-medium14px;
@font-size-medium-x16px;
@font-size-small-s10px;
@font-size-small12px;

// 淺色
@fill-1: #fff;
@text: #3c3c3c;
@text-1: #757575;
@text-2: #222;

// 頁面使用 例如:
// 下面.textcss 以下,這裏的 @font-size-large 和  @text-2就是 less 變量:
.text {
    displayinline-block;
    vertical-aligntop;
    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 {
      themetrue// 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 keep-alive深刻理解及實踐總結

手把手教你搭建一個灰度發佈環境


若此文有用,何不素質三連❤️

本文分享自微信公衆號 - Vue中文社區(vue_fe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索