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引入之前:
引入my-theme之後:優點
- 簡單快速,只需要修改變數,無需單個配置
缺點
- 不能改變圖示顏色
- 不能動態切換(本地開發能根據js判斷切換主題,打包後失效)
- 不能切換頁面圖片
2.Element UI
1.主題編輯器
Element 預設提供一套主題,CSS 命名採用 BEM 的風格,方便使用者覆蓋樣式。我們提供了四種方法,可以進行不同程度的樣式自定義。 使用線上主題編輯器,可以修改定製 Element 所有全域性和元件的 Design Tokens,並可以方便地實時預覽樣式改變後的視覺
主題編輯完成,下載等到一個檔案theme檔案import Vue from 'vue'
import Element from 'element-ui'
import './assets/theme/index.css'
Vue.use(Element)
複製程式碼
看例子: 預設主題
主題編輯器例子:
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 變數例子
優點
- 提供線上編輯器,可直觀的看到各個元件的樣式
- 直接引入樣式檔案,操作簡單
- 可改變圖示顏色
缺點
- 未實現動態改變主題樣式
- 圖示只支援字型格式的圖示(unicode,font-class),且需純色圖示
- 不能改變頁面圖片(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'
複製程式碼
例子: 未修改前
修改後:優點
- 無需準備多套主題,可以自由動態換膚
- 官方提供元件,使用者可直接操作
缺點
- 自定義不夠,只支援基礎顏色的切換(一種色系)
- 不能更改圖示和圖片
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渲染
優點
- 可更換任意頁面元件顏色
- 官方提供主題生成庫 element-theme 可根據需求進行了相應的改造。
缺點
- 樣式元件只能是element UI,其他UI元件不生效
- 本地存放多套樣式程式碼,程式碼冗餘
- 圖片不行
我們專案怎樣實現
需求:我們在套程式碼裡實現兩個品牌的主題色適配,適配內容全域性的主題色更換(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上環境就不行了呢?可能的原因:
- 本地執行依賴於node,js可動態的改變css引入
- 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就能搞定。
優點
- 同時滿足了主題色和圖片圖示的適配
- 一套程式碼,多品牌適配(如需適配更多品牌,只需要傳不同的變數即可)
- 簡化程式碼結構
缺點
- 需要適配主題色的元件需要手動適配到.theme(){}內
- 圖片是以div背景圖的形式處理的,所以需要預先設定div高寬,不能按照圖片大小自動撐開
- 編碼時,必須將樣式顏色值單獨提取,不能隨意寫死
結語
就此,實現是本專案的需求,期間踩過一些坑(方案1),分享出來,避免同樣問題困擾其他小夥伴,其中表述的觀點可能不正確,還需小夥伴們指出討論,關注我GitHub賬號Henry-boter,掘金賬號Henry-boter