(By: Kath & kimmy)
最近在做的一個幾月vue的移動端小demo,其中有一塊是實現(xiàn)各個頁面的統(tǒng)一換膚功能的。想著寫一篇文章,來寫一寫實現(xiàn)過程中遇到的一些問題。
項目在線demo
項目github地址
一 先看一下實現(xiàn)效果吧
設(shè)置主題顏色
講道理這么一個功能,我覺得這么幾點可以說下贿肩,分步實現(xiàn):
1. 色值的選取
2. scss 的一些小眾用法(多變量CSS值的批量設(shè)置)
3. 全局事件巴士的應(yīng)用
1 色值的選取和原則
推薦大家看下螞蟻金服的設(shè)計指引,里面對常見的交互和界面設(shè)計有一套不錯的指引和建議失仁,喜歡看書的也可以看看《寫給大家看的設(shè)計書》。
對于界面中的色彩元素们何,我們一般要保持視覺的連續(xù)性萄焦,即同一套色彩,盡量采取同一個色環(huán)上的色值
同一個圓環(huán)上的色值作為一套顏色會顯得更協(xié)調(diào)
所以這里采取ant design 的建議冤竹,取某一列色值作為我們的系列主題顏色(具體色值參照它的官網(wǎng)吧~)
而在某些特殊場合拂封,需要表現(xiàn)出顏色的差異,如拋硬幣頁面的兩個顏色鹦蠕,
2 將格式色值轉(zhuǎn)換成十六進(jìn)制顏色值
這里我們通過設(shè)置主題顏色的透明度來實現(xiàn)區(qū)分不同顏色冒签, 然后我們是通過存儲一個諸如#123456的16進(jìn)制顏色全局變量作為我們主題,這里就需要我們把這樣一個格式的色值轉(zhuǎn)化成 rgba 表示的顏色值啦钟病,代碼如下萧恕,備用
hexToRgba (hex, opacity = 0.3) {
let color = []
let rgb = []
hex = hex.replace(/#/, '')
for (let i = 0; i < 3; i++) {
color[i] = '0x' + hex.substr(i * 2, 2)
rgb.push(parseInt(Number(color[i])))
}
return `rgba(${rgb.join(',')},${opacity})`
}
3 scss 的一些小眾用法
我們最終拿到這么一串我們想要的主題顏色
$colors: #f04134, #00a854, #108ee9, #f5317f, #f56a00, #7265e6, #ffbf00, #00a2ae, #2e3238;
一個很直接的思路,我們需要在各個view頁面里面肠阱,去定義我們需要設(shè)置主題的元素的顏色票唆,比如文字和icon的color, 以及頭部的background 等。 于是我們在app 里面定義一個color變量屹徘,派發(fā)到各個view組件里面去走趋,通過這個全局的變量來控制所有路由頁面的顏色,以實現(xiàn)不同的主題效果噪伊。
派發(fā)的實現(xiàn)在下一個部分說簿煌,這里我們先來完成我們的第一步氮唯,我們可以容易提取出我們的需求:
4 設(shè)置并保存一個全局顏色
界面的小事:
我在首頁直接實現(xiàn)這個功能,項目中我引入了mint-ui 框架(餓了么團(tuán)隊的移動端框架姨伟,稍微遺憾使用感覺沒有element.ui 的舒服)惩琉, 設(shè)置的交互就用彈層 mt-popup 的形式好了,然后直接點擊色塊便設(shè)置對應(yīng)顏色值
<!-- 設(shè)置顏色 -->
<mt-popup v-model="changColor" position="bottom" class="color-panel">
<div class="color-items">
<span class="color-item" v-for="(item, $index) in colors" :key="$index" @click="chooseColor(item)">
<span class="color-cycle" :class="'bg-color' + ($index + 1)"></span>
</span>
</div>
</mt-popup>
接著就是色塊div的呈現(xiàn)授滓,從上面代碼發(fā)現(xiàn)琳水,我會很容易出現(xiàn)類似這樣的css樣式表
.bg-color1 {background: #f04134}
.bg-color2 {background: #f04134}
.bg-color3 {background: #f04134}
.bg-color4 {background: #f04134}
···
寫代碼時候如果我們一般發(fā)現(xiàn),一件類似的東西重復(fù)出現(xiàn)了般堆,就總隱隱覺得可以開始表演了在孝,然后可預(yù)見的是,這樣的情況意味著在項目增長后淮摔,還可能出現(xiàn)許多單一設(shè)置字體顏色或border顏色的樣式表私沮,諸如color1, borderColor1···,這樣每種形式的表現(xiàn)我們都需要根據(jù)我們主題顏色的數(shù)組去逐條書寫和橙,修改成本也會變高 仔燕。于是我的書寫風(fēng)格是這樣的,
// mixin.scss:
$colors: #f04134, #00a854, #108ee9, #f5317f, #f56a00, #7265e6, #ffbf00, #00a2ae, #2e3238;
// setColor.vue:
@import '~@/assets/mixin.scss';
···
@for $i from 1 to 10 {
.bg-color#{$i} {
background-color: nth($colors, $i)
}
}
scss 除了常用的類名嵌套書寫外魔招,還有許多···低調(diào)奢華的語法晰搀, 對于這類需要重復(fù)書寫的樣式類型,我的約定是添加一個scss變量在mixin 文件中, 在需要書寫重復(fù)循環(huán)樣式時候作為變量引入办斑,并在書寫樣式時候外恕,利用sass的循環(huán),引用其中對應(yīng)的值乡翅,這樣無論設(shè)置顏色的樣式怎么拓展和變化鳞疲,變成顏色背景邊框都好,我都只需要維護(hù)一份mixin的的文件里的色值就行了蠕蚜, 同樣的實踐也可以應(yīng)用于項目里面字體大小和間距值的統(tǒng)一之類尚洽,總之我們多嘗試體驗下吧
5 邏輯的小事
這個項目里面localstorage 基本被當(dāng)成數(shù)據(jù)庫使用了,所以點擊色塊設(shè)置主題時候靶累,我們假裝發(fā)出請求腺毫,在localstorage存儲我們改變的顏色就好了( ./static/api.json 是一個返回helloword 的json, 為了寫實在這里這么用挣柬,$bus 事件巴士下面說拴曲, 作用就是設(shè)置全局的主題顏色變量,localStorage 模擬我們把設(shè)置存儲到后臺凛忿,每次重新打開頁面就去獲取這些設(shè)置值)澈灼, 目前為止,我們的設(shè)置頁面就大致完成了
// 假裝調(diào)用接口設(shè)置顏色
chooseColor (color) {
this.$axios.get('./static/api.json')
.then((data) => {
this.$bus.$emit('set-theme', color)
this.changColor = false
localStorage.setItem('themeColor', color)
})
.catch((data) => {
console.log(data)
})
}
6 事件巴士的運用
在上一步最后我們有個關(guān)鍵的東西沒完成, this.$bus.$emit('set-theme', color)
叁熔,將選取的顏色設(shè)置到全局委乌,我的代碼結(jié)構(gòu)是這樣的
<setColor>
是home 頁面
的一個子組件,而在一開始我們已經(jīng)說了荣回,我們想在我們在app.vue (home.vue和其他view的父組件) 里面定義一個color變量遭贸,派發(fā)到各個view組件里面去。 于是這其實就是個心软,從setColor
觸發(fā)app.vue
的設(shè)置顏色事件壕吹, 子組件向父組件通信的問題。
我們可以很直接地用綁定事件配合emit()
的做法删铃,在app.vue
定義一個setglobalColor
方法耳贬, 并綁定到router-view(包含了home.vue),接著在home組件繼續(xù)定義一個setglobalColor
方法猎唁, 實現(xiàn)的功能就是 emit('setglobalColor') 去觸發(fā)app.vue的方法咒劲, 并把home.vue
的這個setglobalColor
繼續(xù)綁定到<setColor>組件, 組件里面點選顏色時候诫隅,直接emit這個方法就行了腐魂。
為什么我想用事件巴士 .vue 的事件巴士和 vuex, 在一些有追求的程序員手里總是小心翼翼的逐纬,我也一樣蛔屹,因為作為涉及全局的東西,一般覺得能不用就不用豁生,代碼能精簡就精簡兔毒,我們經(jīng)常用一個詞,不提倡沛硅。
可是有朝一日我經(jīng)常在想眼刃,代碼的可讀性可維護(hù)性绕辖,和性能以及“風(fēng)險”相對比摇肌,到底哪個更重要。對于事件巴士和vuex 這類全局性質(zhì)的方案的主要擔(dān)憂大部分在于仪际, 他們是全局的围小,可能因為一個事件名變量名一致就造成沖突,在小型項目還會造成冗余和額外開銷树碱。 但事實上肯适,事件和變量的命名我們都可以通過約定去規(guī)范,而在表現(xiàn)上成榜,使用了事件巴士和vuex的項目框舔,在性能上和直接props
傳遞數(shù)據(jù),emit 回調(diào)事件的項目相比,其實并沒有太大區(qū)別刘绣,反而是無止境的props 和 emit
樱溉,給人一種麻煩難以維護(hù)的感覺。 像上述的setglobalColor
, 僅僅是跨越了兩層組件纬凤, 過程就顯得繁瑣了福贞。所以我建議在出現(xiàn)兩級以上組件層次,數(shù)據(jù)流稍微多的項目中都可以這么去做停士,定義一個全局的事件巴士
export default (Vue) => {
let eventHub = new Vue()
Vue.prototype.$bus = {
$on (...arg) {
eventHub.$on(...arg)
},
$off (...arg) {
eventHub.$off(...arg)
},
$emit (...arg) {
eventHub.$emit(...arg)
}
}
}
將事件巴士綁定到當(dāng)前vue對象挖帘,使用時候只需要:
this.$bus.$on('set-theme', (color) => {··· })
this.$bus.$emit('set-theme', '#000000')
在這個demo中,我在app.vue 綁定了
this.$bus.$on('set-theme', (color) => {
this.loadingColor = color
this.userinfo.color = color
})
而在setColor.vue則在點擊顏色塊時候觸發(fā) this.$bus.$emit('set-theme', color)恋技,
則能實現(xiàn)我們設(shè)置全局顏色的效果拇舀。這樣的好處在于,對于跨了多個層次猖任,或者兄弟組件的通信你稚,我們不再需要太繁瑣的props,比如我在header.vue 也綁定了this.$bus.$on('set-theme', (color) => { })
朱躺,在 this.$bus.$emit
發(fā)生時候刁赖,header 的背景顏色就能直接改變,而不需要等待app.vue 將 全局的color值props傳遞到header.vue里面(僅做示例长搀,這里header.vue
只是app.vue
的下一層級宇弛,通過props數(shù)據(jù)流會更清晰)
而對于其他路由頁面組件,和app.vue
都是直接上下級關(guān)系源请,我們依然采用props保持一個清晰的數(shù)據(jù)流向下傳遞枪芒,demo
里我是將color
存在userinfo(以后還有其他數(shù)據(jù)), userinfo傳到每個子路由谁尸, 最后舅踪,每個頁面在創(chuàng)建時候,通過拿到這個全局的顏色良蛮,再用dom去更改對應(yīng)的樣式就好啦抽碌,例如
mounted () {
this.$nextTick(() => {
// 綁定設(shè)置主題的事件,一旦觸發(fā)修改主題决瞳,則將當(dāng)前字體顏色改為對應(yīng)顏色
this.$el.querySelector('.myTitle').style.color = this.userinfo.color
this.$el.querySelector('.weui-btn_primary').style.backgroundColor = this.userinfo.color
this.$el.querySelector('.add_icon').style.color = this.userinfo.color
})
}
詳細(xì)的實現(xiàn)請參照項目代碼货徙,這里我只挑一些比較清奇的點出來討論,項目和代碼的一些規(guī)范和習(xí)慣還是挺重要的皮胡,希望有好的實踐能互相借鑒進(jìn)步~