最近在作的一个几月vue的移动端小demo,其中有一块是实现各个页面的统一换肤功能的。想着写一篇文章,来写一写实现过程当中遇到的一些问题。javascript
项目github地址vue
设置主题颜色
讲道理这么一个功能,我以为这么几点能够说下,分步实现:
1. 色值的选取
2. scss 的一些小众用法(多变量CSS值的批量设置)
3. 全局事件巴士的应用java
推荐你们看下蚂蚁金服的设计指引,里面对常见的交互和界面设计有一套不错的指引和建议,喜欢看书的也能够看看《写给你们看的设计书》。
对于界面中的色彩元素,咱们通常要保持视觉的连续性,即同一套色彩,尽可能采起同一个色环上的色值ios
因此这里采起ant design 的建议,取某一列色值做为咱们的系列主题颜色(具体色值参照它的官网吧~)git
而在某些特殊场合,须要表现出颜色的差别,如抛硬币页面的两个颜色,
程序员
这里咱们经过设置主题颜色的透明度来实现区分不一样颜色, 而后咱们是经过存储一个诸如#123456的16进制颜色全局变量做为咱们主题,这里就须要咱们把这样一个格式的色值转化成 rgba 表示的颜色值啦,代码以下,备用github
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 的一些小众用法
咱们最终拿到这么一串咱们想要的主题颜色vuex
$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的的文件里的色值就好了, 一样的实践也能够应用于项目里面字体大小和间距值的统一之类,总之咱们多尝试体验下吧
这个项目里面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)
})
}复制代码
在上一步最后咱们有个关键的东西没完成, 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
})
}复制代码
详细的实现请参照项目代码,这里我只挑一些比较清奇的点出来讨论,项目和代码的一些规范和习惯仍是挺重要的,但愿有好的实践能互相借鉴进步~