Vue專案主題色適配,看這一篇文章就夠了

Henry-boter發表於2020-01-06

Vue專案主題色適配,看這一篇文章就夠了

專案開發經常遇到一套程式碼需要配值不同的主體色,比如後臺管理系統不同品牌的主體顏色不一樣,需要根據不同品牌的環境變數打包生成不同顏色的主題和背景圖片,或者節日期間更換網頁皮膚,亦或者最近比較火的微信黑暗模式和明亮模式。都會有切換主題色的需求,那麼本文就來探討Vue專案關於主題色切換有哪些方法?

各大主流框架是怎麼做的

1.iView官方推薦的方式

在專案中建立檔案assets/my-theme/index.less,並寫入一下內容:

// assets/my-theme/index.less
@import '~view-design/src/styles/index.less';

// Here are the variables to cover, such as:
@menu-dark-title: #001529;
@menu-dark-active-bg: #000c17;
@layout-sider-background: #001529;
@primary-color: #8c0776;

複製程式碼

官方提供了完整的樣式變數預設樣式變數, 我們在main.js中引用我們建立的檔案assets/my-theme/index.less

// main.js
import Vue from 'vue';
import ViewUI from 'view-design';
import '../my-theme/index.less';

Vue.use(ViewUI);

複製程式碼

my-theme引入之前:

UTOOLS1577351448523.png
引入my-theme之後:
UTOOLS1577351553155.png

優點

  • 簡單快速,只需要修改變數,無需單個配置

缺點

  1. 不能改變圖示顏色
  2. 不能動態切換(本地開發能根據js判斷切換主題,打包後失效)
  3. 不能切換頁面圖片

2.Element UI

1.主題編輯器

Element 預設提供一套主題,CSS 命名採用 BEM 的風格,方便使用者覆蓋樣式。我們提供了四種方法,可以進行不同程度的樣式自定義。 使用線上主題編輯器,可以修改定製 Element 所有全域性和元件的 Design Tokens,並可以方便地實時預覽樣式改變後的視覺

16f41c0e1fc0b82d
主題編輯完成,下載等到一個檔案theme檔案

UTOOLS1577357295155.png

import Vue from 'vue'
import Element from 'element-ui'
import './assets/theme/index.css'

Vue.use(Element)
複製程式碼

看例子: 預設主題

UTOOLS1577361714541.png

主題編輯器例子:

UTOOLS1577361157154.png
UTOOLS1577361511372.png

2.在專案中改變 SCSS 變數

Element 預設提供一套主題,CSS 命名採用 BEM 的風格,方便使用者覆蓋樣式。我們提供了四種方法,可以進行不同程度的樣式自定義。

新建一個樣式檔案,例如 element-variables.scss,寫入以下內容:

/* 改變主題色變數 */
$--color-primary: teal;

/* 改變 icon 字型路徑變數,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";
複製程式碼

之後,在專案的入口檔案中,直接引入以上樣式檔案即可

import Vue from 'vue'
import Element from 'element-ui'
import './element-variables.scss'

Vue.use(Element)
複製程式碼

改變 SCSS 變數例子

UTOOLS1577362235442.png

優點

  1. 提供線上編輯器,可直觀的看到各個元件的樣式
  2. 直接引入樣式檔案,操作簡單
  3. 可改變圖示顏色

缺點

  1. 未實現動態改變主題樣式
  2. 圖示只支援字型格式的圖示(unicode,font-class),且需純色圖示
  3. 不能改變頁面圖片(welcome.png)

3.Vue-element-admin

1.動態換膚,選擇任意顏色替換主題色

原理: element-ui 2.0 版本之後所有的樣式都是基於 SCSS 編寫的,所有的顏色都是基於幾個基礎顏色變數來設定的,所以就不難實現動態換膚了,只要找到那幾個顏色變數修改它就可以了。 首先我們需要拿到通過 package.json 拿到 element-ui 的版本號,根據該版本號去請求相應的樣式。拿到樣式之後將樣色,通過正則匹配和替換,將顏色變數替換成你需要的,之後動態新增 style 標籤來覆蓋原有的 css 樣式。

const version = require('element-ui/package.json').version

const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
this.getCSSString(url, chalkHandler, 'chalk')

getCSSString(url, callback, variable) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = () => {
    if (xhr.readyState === 4 && xhr.status === 200) {
      this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
      callback()
    }
  }
  xhr.open('GET', url)
  xhr.send()
}
複製程式碼

官方提供主顏色選擇器元件ThemePicker

import ThemePicker from '@/components/ThemePicker'
複製程式碼

例子: 未修改前

UTOOLS1577414523609.png
修改後:
UTOOLS1577414595780.png

優點

  1. 無需準備多套主題,可以自由動態換膚
  2. 官方提供元件,使用者可直接操作

缺點

  1. 自定義不夠,只支援基礎顏色的切換(一種色系)
  2. 不能更改圖示和圖片
2.多套主題換膚

本方法就是最常見的換膚方式,本地存放多套主題,兩者有不同的名稱空間,如寫兩套主題,一套叫 day-theme ,一套叫 night-theme ,night-theme 主題都在一個 .night-theme 的名稱空間下,我們動態的在 body 上 add .night-theme ; remove .night-theme。

css樣式編寫:(已名稱空間custom-theme為例)

.custom-theme .el-button + .el-button {
  margin-left: 10px
}

.custom-theme .el-button.is-round {
  padding: 12px 20px
}

.custom-theme .el-button:focus, .custom-theme .el-button:hover {
  color: #262729;
  border-color: #bebebf;
  background-color: #e9e9ea
}

.custom-theme .el-button:active {
  color: #222325;
  border-color: #222325;
  outline: 0
}
複製程式碼

js操作

toggleClass(document.body, 'custom-theme')
複製程式碼

瀏覽器DOM渲染

UTOOLS1577416076182.png
UTOOLS1577416160750.png
UTOOLS1577416228235.png
UTOOLS1577416269777.png

優點

  1. 可更換任意頁面元件顏色
  2. 官方提供主題生成庫 element-theme 可根據需求進行了相應的改造。

缺點

  1. 樣式元件只能是element UI,其他UI元件不生效
  2. 本地存放多套樣式程式碼,程式碼冗餘
  3. 圖片不行

我們專案怎樣實現

需求:我們在套程式碼裡實現兩個品牌的主題色適配,適配內容全域性的主題色更換(brand_1為橙色,brand_2為紅色),包括header,aside,button,table,table:hover,tabs,form。圖片更換包括logo,aside圖示,welcome頁面,error頁面,disable頁面

實現方案1

按照iView官方給出的方案:準備兩套主題配色的css(brand_1,brand_2),不同的環境變數import不同的樣式檔案,貌似很簡單。Do it!

建立兩個檔案theme_brand_2.less,theme_brand_1.less

// assets/less/theme_brand_2.less
@import '../../../node_modules/view-design/src/styles/index.less';

@primary-color: #CC5D5B; // brand_1:#F7BB03,brand_2:#CC5D5B
@success-color: #19be6b;
@warning-color: #F58220;
@error-color: #FF6C5D;
@place-color: #9B9B9B;
@btn-primary-color: #fff;
複製程式碼

js判斷

// 入口檔案app.js
......
    const preject = globalRes.data.data.brandName;
    if (preject === 'theme_brand_1') {
      require('./assets/less/theme_brand_1.less');
    } else if (preject === 'theme_brand_2') {
      require('./assets/less/theme_brand_2.less');
    }
......
複製程式碼

run dev,執行,搞定!

編譯打包,上測試環境。。。 問題來了,伺服器上的theme_brand_1,沒有問題,theme_brand_2樣式未改變。改程式碼不做js判斷直接import './assets/less/theme_brand_2.less',編譯打包上測試,brand_2樣式OK。思考:為什麼本地執行一切OK上環境就不行了呢?可能的原因:

  1. 本地執行依賴於node,js可動態的改變css引入
  2. css檔案屬於靜態檔案,webpack先打包靜態檔案,存為靜態資源(伺服器快取靜態資源)

於是乎這個實現方式被否定了,另闢蹊徑。

實現方案2

既然JS解決不了的問題能不能交給css來處理?採用css作用域的方式來控制控制作用域下面的子元素顯示不同的顏色。

運用less預處理的函式方法,將顏色值設為變數,用函式的形式管理css作用域

// assets/less/theme.less
.theme(@primary-color: #CC5D5B, @btn-primary-color: #fff, @header-logo-width: 45px, @header-logo-height: 45px){
  .ivu-btn-primary {
    background-color: @primary-color;
    border-color: @primary-color;
    color: @btn-primary-color;
  }
  .ivu-btn-text {
    color: @primary-color;
  }
  .ivu-btn-default:hover {
    border-color: @primary-color;
    color: @primary-color;
  }
  .ivu-btn-primary:focus {
    box-shadow: none;
  }
}
.brand_1{
  .theme(#F7BB03, #000000, 89px, 36px); //不傳參,使用預設色
}
.brand_2 {
  .theme(#CC5D5B, #FFFFFF);
}
複製程式碼
// app.js
import './assets/less/theme.less'
// app.vue
    initTheme() {
      const brandName = this.global.brandName.toLowerCase();
      document.getElementsByTagName('body')[0].className = brandName || 'brand_1';
    },
複製程式碼

樣式編寫搞定,run dev,沒問題。 打包上線,ok搞定。

思考:既然顏色值,畫素值可以設為變數,那圖片可以設為變數否?一切皆是變數。

// assest/less/theme.less
.welcome{
    background-size: 50% auto;
    background-repeat: no-repeat;
    background-position: center;
}
.themeImg(@welcome-img) {
  .welcome{
    background-image: url(@welcome-img);
  }
}

@welcome-img_brand_1: '../img/brand_1/welcome.png';
@welcome-img_brand_2: '../img/brand_2/welcome.png';

.brand_1{
  .theme(#F7BB03, #000000, 89px, 36px); //不傳參,使用預設色
  .themeImg(@welcome-img_brand_1);
}

.brand_2 {
  .theme(#CC5D5B, #FFFFFF);
  .themeImg(@welcome-img_brand_2);
}
複製程式碼

關於頁面程式碼,優化前:

<template>
  <div class="welcome">
    <img :src="require(`../assets/img/welcome_${global.brandName}.png`)" alt="Welcome!" class="welcome-img"/>
  </div>
</template>
<style scoped lang="less">
  .wrapper .main-container .page-view.welcome{
    left: 0;
    right: 0;
    padding-bottom: 0;
    background: #F4F7F8;
    background-repeat: no-repeat;
    background-size: 100% 20%;
    background-position: 0 100%;
    img {
      width: 800px;
      position: absolute;
      right: 50%;
      bottom: 50%;
      transform: translate(50%, 40%);
    }
  }
  .welcome-img {
    max-width: 100%;
  }
</style>
<script>
import { mapGetters } from 'vuex';
export default {
  components: {},
  computed: {
    ...mapGetters({
      global: 'getGlobal',
    }),
  },
};
</script>

複製程式碼

程式碼優化後:

<template>
  <div class="welcome"></div>
</template>
複製程式碼

同理,我們的logo,aside圖示,welcome頁面,error頁面,disable頁面都可以採用這種方式,不用js判斷,CSS就能搞定。

優點

  1. 同時滿足了主題色和圖片圖示的適配
  2. 一套程式碼,多品牌適配(如需適配更多品牌,只需要傳不同的變數即可)
  3. 簡化程式碼結構

缺點

  1. 需要適配主題色的元件需要手動適配到.theme(){}內
  2. 圖片是以div背景圖的形式處理的,所以需要預先設定div高寬,不能按照圖片大小自動撐開
  3. 編碼時,必須將樣式顏色值單獨提取,不能隨意寫死

結語

就此,實現是本專案的需求,期間踩過一些坑(方案1),分享出來,避免同樣問題困擾其他小夥伴,其中表述的觀點可能不正確,還需小夥伴們指出討論,關注我GitHub賬號Henry-boter,掘金賬號Henry-boter

相關文章