基于vue的换肤功能的示例代码


Posted in Javascript onOctober 10, 2017

最近在做的一个几月vue的移动端小demo,其中有一块是实现各个页面的统一换肤功能的。想着写一篇文章,来写一写实现过程中遇到的一些问题。

项目github地址

项目github地址

一 先看一下实现效果吧

基于vue的换肤功能的示例代码 

设置主题颜色

讲道理这么一个功能,我觉得这么几点可以说下,分步实现:

1. 色值的选取

2. scss 的一些小众用法(多变量CSS值的批量设置)

3. 全局事件巴士的应用

1 色值的选取和原则

推荐大家看下蚂蚁金服的设计指引,里面对常见的交互和界面设计有一套不错的指引和建议,喜欢看书的也可以看看《写给大家看的设计书》。

对于界面中的色彩元素,我们一般要保持视觉的连续性,即同一套色彩,尽量采取同一个色环上的色值

基于vue的换肤功能的示例代码 

同一个圆环上的色值作为一套颜色会显得更协调

所以这里采取ant design 的建议,取某一列色值作为我们的系列主题颜色(具体色值参照它的官网吧~)

基于vue的换肤功能的示例代码 

而在某些特殊场合,需要表现出颜色的差异,如抛硬币页面的两个颜色,

基于vue的换肤功能的示例代码 

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) ,将选取的颜色设置到全局,我的代码结构是这样的

基于vue的换肤功能的示例代码 

子组件

<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
  })
 }

详细的实现请参照项目代码,这里我只挑一些比较清奇的点出来讨论,项目和代码的一些规范和习惯还是挺重要的,希望有好的实践能互相借鉴进步~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用roll.js实现的图片自动滚动+鼠标触动的特效
Mar 18 Javascript
心扬JS分页函数代码
Sep 10 Javascript
jQuery Tips 为AJAX回调函数传递额外参数的方法
Dec 28 Javascript
通过url查找a元素并点击
Apr 09 Javascript
javascript数字时钟示例分享
Apr 23 Javascript
js 获取页面高度和宽度兼容 ie firefox chrome等
May 14 Javascript
JavaScript实现为指定对象添加多个事件处理程序的方法
Apr 17 Javascript
JavaScript在Android的WebView中parseInt函数转换不正确问题解决方法
Apr 25 Javascript
Js获取当前日期时间及格式化代码
Sep 17 Javascript
JavaScript计算值然后把值嵌入到html中的实现方法
Oct 29 Javascript
JS冒泡事件与事件捕获实例详解
Nov 25 Javascript
vue.js动态数据绑定学习笔记
May 19 Javascript
seajs中模块依赖的加载处理实例分析
Oct 10 #Javascript
利用JS hash制作单页Web应用的方法详解
Oct 10 #Javascript
认识jQuery的Promise的具体使用方法
Oct 10 #jQuery
JavaScript如何获取到导航条中HTTP信息
Oct 10 #Javascript
使用Dropzone.js上传的示例代码
Oct 10 #Javascript
React中上传图片到七牛的示例代码
Oct 10 #Javascript
基于对象合并功能的实现示例
Oct 10 #Javascript
You might like
一个数据采集类
2007/02/14 PHP
PHP下一个非常全面获取图象信息的函数
2008/11/20 PHP
强烈声明: 不要使用(include/require)_once
2013/06/06 PHP
PHP常用技巧汇总
2016/03/04 PHP
php单链表实现代码分享
2016/07/04 PHP
JavaScript Archive Network 集合
2007/05/12 Javascript
javascript学习(一)构建自己的JS库
2013/01/02 Javascript
使用JavaScript的AngularJS库编写hello world的方法
2015/06/23 Javascript
JS定时器用法分析【时钟与菜单中的应用】
2016/12/21 Javascript
jQuery.Ajax()的data参数类型详解
2017/07/23 jQuery
JS实现的碰撞检测与周期移动完整示例
2019/09/02 Javascript
layui清空,重置表单数据的实例
2019/09/12 Javascript
node省市区三级数据性能测评实例分析
2019/11/06 Javascript
vue项目中使用vue-layer弹框插件的方法
2020/03/11 Javascript
浅谈Vue3 Composition API如何替换Vue Mixins
2020/04/29 Javascript
js实现弹幕飞机效果
2020/08/27 Javascript
基于openlayers实现角度测量功能
2020/09/28 Javascript
JS pushlet XMLAdapter适配器用法案例解析
2020/10/16 Javascript
[20:46]Ti4循环赛第三日VG vs DK
2014/07/12 DOTA
django 自定义过滤器(filter)处理较为复杂的变量方法
2019/08/12 Python
python实现抠图给证件照换背景源码
2019/08/20 Python
Python 如何提高元组的可读性
2019/08/26 Python
python使用sklearn实现决策树的方法示例
2019/09/12 Python
PyCharm 在Windows的有用快捷键详解
2020/04/07 Python
Python参数传递机制传值和传引用原理详解
2020/05/22 Python
如何用python实现一个HTTP连接池
2021/01/14 Python
详解HTML5 Canvas绘制时指定颜色与透明度的方法
2016/03/25 HTML / CSS
公司建议书怎么写
2014/05/15 职场文书
2014年学习全国道德模范事迹思想汇报
2014/09/15 职场文书
部门经理迟到检讨书
2015/02/16 职场文书
春节慰问简报
2015/07/21 职场文书
感恩的心主题班会
2015/08/12 职场文书
2016年大学生暑期社会实践方案
2015/11/26 职场文书
HR必备:超全面的薪酬待遇管理方案!
2019/07/12 职场文书
jQuery实现影院选座订座效果
2021/04/13 jQuery
mysql查看表结构的三种方法总结
2022/07/07 MySQL