vue移動助手實踐(一)——基於vue的換膚功能

katherine的小世界發表於2017-10-09

最近在做的一個幾月vue的移動端小demo,其中有一塊是實現各個頁面的統一換膚功能的。想著寫一篇文章,來寫一寫實現過程中遇到的一些問題。

專案線上demo

專案線上演示demo(切換到移動端除錯模式哦)

專案github地址

專案github地址

一 先看一下實現效果吧

設定主題顏色
講道理這麼一個功能,我覺得這麼幾點可以說下,分步實現:
1. 色值的選取
2. scss 的一些小眾用法(多變數CSS值的批量設定)
3. 全域性事件巴士的應用

1 色值的選取和原則

推薦大家看下螞蟻金服的設計指引,裡面對常見的互動和介面設計有一套不錯的指引和建議,喜歡看書的也可以看看《寫給大家看的設計書》。
對於介面中的色彩元素,我們一般要保持視覺的連續性,即同一套色彩,儘量採取同一個色環上的色值

同一個圓環上的色值作為一套顏色會顯得更協調

所以這裡採取ant design 的建議,取某一列色值作為我們的系列主題顏色(具體色值參照它的官網咖~)

而在某些特殊場合,需要表現出顏色的差異,如拋硬幣頁面的兩個顏色,

2 將格式色值轉換成十六進位制顏色值

這裡我們通過設定主題顏色的透明度來實現區分不同顏色, 然後我們是通過儲存一個諸如#123456的16進位制顏色全域性變數作為我們主題,這裡就需要我們把這樣一個格式的色值轉化成 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頁面裡面,去定義我們需要設定主題的元素的顏色,比如文字和icon的color, 以及頭部的background 等。 於是我們在app 裡面定義一個color變數,派發到各個view元件裡面去,通過這個全域性的變數來控制所有路由頁面的顏色,以實現不同的主題效果。
派發的實現在下一個部分說,這裡我們先來完成我們的第一步,我們可以容易提取出我們的需求:
4 設定並儲存一個全域性顏色
介面的小事:
我在首頁直接實現這個功能,專案中我引入了mint-ui 框架(餓了麼團隊的移動端框架,稍微遺憾使用感覺沒有element.ui 的舒服), 設定的互動就用彈層 mt-popup 的形式好了,然後直接點選色塊便設定對應顏色值

 <!-- 設定顏色 -->
    <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的呈現,從上面程式碼發現,我會很容易出現類似這樣的css樣式表

.bg-color1 {background: #f04134}
.bg-color2 {background: #f04134}
.bg-color3 {background: #f04134}
.bg-color4 {background: #f04134}
···複製程式碼

寫程式碼時候如果我們一般發現,一件類似的東西重複出現了,就總隱隱覺得可以開始表演了,然後可預見的是,這樣的情況意味著在專案增長後,還可能出現許多單一設定字型顏色或border顏色的樣式表,諸如color1, borderColor1···,這樣每種形式的表現我們都需要根據我們主題顏色的陣列去逐條書寫,修改成本也會變高 。於是我的書寫風格是這樣的,

// 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 除了常用的類名巢狀書寫外,還有許多···低調奢華的語法, 對於這類需要重複書寫的樣式型別,我的約定是新增一個scss變數在mixin 檔案中, 在需要書寫重複迴圈樣式時候作為變數引入,並在書寫樣式時候,利用sass的迴圈,引用其中對應的值,這樣無論設定顏色的樣式怎麼擴充和變化,變成顏色背景邊框都好,我都只需要維護一份mixin的的檔案裡的色值就行了, 同樣的實踐也可以應用於專案裡面字型大小和間距值的統一之類,總之我們多嘗試體驗下吧

5 邏輯的小事

這個專案裡面localstorage 基本被當成資料庫使用了,所以點選色塊設定主題時候,我們假裝發出請求,在localstorage儲存我們改變的顏色就好了( ./static/api.json 是一個返回helloword 的json, 為了寫實在這裡這麼用,$bus 事件巴士下面說, 作用就是設定全域性的主題顏色變數,localStorage 模擬我們把設定儲存到後臺,每次重新開啟頁面就去獲取這些設定值), 目前為止,我們的設定頁面就大致完成了

// 假裝呼叫介面設定顏色
    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 事件巴士的運用

在上一步最後我們有個關鍵的東西沒完成, this.$bus.$emit('set-theme', color),將選取的顏色設定到全域性,我的程式碼結構是這樣的

子元件
子元件

<setColor>home 頁面的一個子元件,而在一開始我們已經說了,我們想在我們在app.vue (home.vue和其他view的父元件) 裡面定義一個color變數,派發到各個view元件裡面去。 於是這其實就是個,從setColor 觸發app.vue的設定顏色事件, 子元件向父元件通訊的問題。

我們可以很直接地用繫結事件配合emit()的做法,在app.vue 定義一個setglobalColor方法, 並繫結到router-view(包含了home.vue),接著在home元件繼續定義一個setglobalColor方法, 實現的功能就是 emit('setglobalColor') 去觸發app.vue的方法, 並把home.vue的這個setglobalColor繼續繫結到元件, 元件裡面點選顏色時候,直接emit這個方法就行了。
為什麼我想用事件巴士 .vue 的事件巴士和 vuex, 在一些有追求的程式設計師手裡總是小心翼翼的,我也一樣,因為作為涉及全域性的東西,一般覺得能不用就不用,程式碼能精簡就精簡,我們經常用一個詞,不提倡。
可是有朝一日我經常在想,程式碼的可讀性可維護性,和效能以及“風險”相對比,到底哪個更重要。對於事件巴士和vuex 這類全域性性質的方案的主要擔憂大部分在於, 他們是全域性的,可能因為一個事件名變數名一致就造成衝突,在小型專案還會造成冗餘和額外開銷。 但事實上,事件和變數的命名我們都可以通過約定去規範,而在表現上,使用了事件巴士和vuex的專案,在效能上和直接props傳遞資料,emit 回撥事件的專案相比,其實並沒有太大區別,反而是無止境的props 和 emit,給人一種麻煩難以維護的感覺。 像上述的setglobalColor, 僅僅是跨越了兩層元件, 過程就顯得繁瑣了。所以我建議在出現兩級以上元件層次,資料流稍微多的專案中都可以這麼去做,定義一個全域性的事件巴士

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)
    }
  }
}複製程式碼

將事件巴士繫結到當前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則在點選顏色塊時候觸發 this.$bus.$emit('set-theme', color), 則能實現我們設定全域性顏色的效果。這樣的好處在於,對於跨了多個層次,或者兄弟元件的通訊,我們不再需要太繁瑣的props,比如我在header.vue 也繫結了this.$bus.$on('set-theme', (color) => { }),在 this.$bus.$emit發生時候,header 的背景顏色就能直接改變,而不需要等待app.vue 將 全域性的color值props傳遞到header.vue裡面(僅做示例,這裡header.vue只是app.vue的下一層級,通過props資料流會更清晰)
而對於其他路由頁面元件,和app.vue 都是直接上下級關係,我們依然採用props保持一個清晰的資料流向下傳遞,demo裡我是將color存在userinfo(以後還有其他資料), userinfo傳到每個子路由, 最後,每個頁面在建立時候,通過拿到這個全域性的顏色,再用dom去更改對應的樣式就好啦,例如

mounted () {
    this.$nextTick(() => {
      // 繫結設定主題的事件,一旦觸發修改主題,則將當前字型顏色改為對應顏色
      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
    })
  }複製程式碼

詳細的實現請參照專案程式碼,這裡我只挑一些比較清奇的點出來討論,專案和程式碼的一些規範和習慣還是挺重要的,希望有好的實踐能互相借鑑進步~

我是KimyAndKath ,希望好東西一起分享。

專案線上demo

專案線上演示demo(切換到移動端除錯模式哦)

專案github地址

專案github地址

相關文章