每个人对设计的偏好会有不同。网站支持换肤,可以极大的提升用户体验。
最近,朋友的一个管理后台项目要支持换肤。不同皮肤,主要的不同是颜色。不同皮肤,也存在页面结构不同的情况。
项目用的 Vue2,UI 库用的 Ant Design Vue。页面已经很多了,而且很多页面写了 UI 库之外的自定义样式。
基于这些情况,设计换肤方案时,要考虑以下的点:
- 换肤导致每个页面的代码改动要聚集在一起,要少。因为页面多,每个页面多改一处,整个项目的多改动的是倍数级的。
- 样式不污染其他页面。还是用 Scoped CSS 来实现。
换肤方案
换肤方案要支持如下功能:
- 项目的所有页面和组件都能获取和修改皮肤值。
- 皮肤值变化时,样式 和 页面结构做对应的变化。
我们下面具体来实现这 2 个功能。
获取和修改皮肤值
这边用 provide / inject
API 来实现页面和组件能获取和修改皮肤值。provide / inject
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深。
在项目根组件上,提供皮肤值和修改皮肤值的方法。
export default {
provide () {
return {
// 皮肤值
theme: this.theme,
// 修复皮肤值的方法
changeTheme: this.changeTheme
}
},
data () {
return {
theme: {
name: 'red-theme'
}
}
},
methods: {
changeTheme (themeName) {
this.theme.name = themeName
}
}
}
上面代码 provide 提供的 theme 是一个对象,是为了保证皮肤是可响应的。因为 provide 和 inject 绑定并不是可响应的。传入了一个可监听的对象,那么其对象的 property 是可响应的。
页面和组件中可以获取和修改皮肤值。
export default {
inject: ['theme', 'changeTheme']
}
当然也可以用 Vuex 来做。
皮肤值变化时的处理
页面和组件中都能拿到当前的皮肤值。皮肤值变化时,样式的变化,通过改组件根元素的类名为皮肤值来实现。
HTML:
<div :class="theme.name">
...
</div>
皮肤样式都以皮肤做为父选择器:
/* 红色皮肤 */
.red-theme .text{
color: #f00;
}
/* 黑色皮肤 */
.black-theme .text{
color: #333;
}
如果 CSS 这么写,有如下问题:
- 某个皮肤值的样式变化后,需要改很多个文件,工作量大,还容易漏改。如红色皮肤的颜色值从
#f00
改成#e00
。该颜色值可能存在于 color,background-color,border-color 等地方。 - 要做一个新的皮肤。可能要在很多页面加 CSS。
我们下面来谈谈怎么优化。
优化方案
优化方案主要用 Sass 来代替 CSS。Sass 支持变量,循环,函数等 CSS 不具备的特性(变量除外)。这些特性有助于让程序高内聚,低耦合。
1 抽取变量
我们将不同皮肤的不同的样式值抽取出来,单独放在不同的文件。用 Sass 的 Map 来存这些变量。
红色皮肤:
// assets/theme/red/_var.scss
$red-theme: (
primary-color: #f00,
...
)
黑色皮肤:
// assets/theme/black/_var.scss
$black-theme: (
primary-color: #333,
...
)
2 处理自定义 UI 组件库的皮肤
我们以改 Ant Design 的 Primary Btn 的背景色为例。
红色皮肤:
// assets/theme/red/ant-design.scss
@import './var';
.red-theme {
& /deep/ .ant-btn-primary {
background-color: map-get($red-theme, primary-color);
border-color: map-get($red-theme, primary-color);
}
}
黑色皮肤:
// assets/theme/black/ant-design.scss
@import './var';
.black-theme {
& /deep/ .ant-btn-primary {
background-color: map-get($black-theme, primary-color);
border-color: map-get($black-theme, primary-color);
}
}
其中 /deep/
是 Scoped CSS 的深度作用选择器。深度作用选择器能作用到子组件中的样式。在这里用来重置 Ant Design 的样式。
3 处理具体页面: 同一个样式属性,不同皮肤设置不同值
我们可以循环皮肤,生成样式。这样,以后加了一个新的皮肤,只需改下配置,并不需要动页面的样式。
我们以改页面中的文字值为例。
@import '@/assets/theme/page';
@each $theme in $all-themes {
.#{$theme} .text {
color: getValue($theme, primary-color);
}
}
其中
$all-themes
是 Sass 中的数组。值是:(red-theme black-theme)
。getValue
是封装的一个 Sass 函数。用来返回皮肤下的某个变量值。
上面的 Sass 会生成如下的 CSS 内容。
.red-theme .text[data-v-a9956c70] {
color: #f00;
}
.black-theme .text[data-v-a9956c70] {
color: #333;
}
4 处理具体页面: 某个皮肤有自定义的样式或页面结构不同
这个比较简单。直接写就好。改某个皮肤的自定义样式:
.red-theme {
.text {
font-weight: bold;
}
}
.black-theme {
.text {
font-style: italic;
}
}
改结构:
<div v-if="theme.name === 'red-theme'">
红色皮肤时才显示
</div>
到这里,我的换肤方案就介绍完了。你有更好的方案吗?推荐给我吧~
关注我们的公众号,回复“换肤方案”,获取源码~