动态设置网站的主题,顺便狠狠地介绍相关 sass 用法
网上关于设置网站主题的文章很多,很多文章还都介绍了很多种方法,咱也不整那么多花里胡哨的,本文就介绍我自己在公司项目中使用的方法。达到的效果是让一套给多个商户使用的前台网站,不同的商户可以通过后台管理系统动态地设置其前台网站的主题色。另外,对于涉及到的 sass 知识点我也会做尽可能详细的说明。
整体思路
- 根据实际需求,与后端约定好几个可选的颜色供管理后台设置,比如有红、蓝和深蓝:
- 管理员选择的某个颜色,会通过接口将对应颜色的名称传递给前台网站,比如选择了蓝色,那么就会传递
domainSetting.site.theme:"blue"
; - 对于前台网站,则是想办法在页面的诸如
<html>
或<body>
这样的外层节点,添加上一个能表明主题色信息的类名,比如蓝色对应的类名就是theme-blue
,红色对应的就是theme-red
; - 准备好所有在第 1 步中约定的可选颜色相应的类名,并在其中使用 css 变量定义主题色,那么页面之中所有的子节点就都可以使用 css 变量来表示主题色了。
具体实现
整体思路中的前两步没啥好说的,下面说说第 3 步的具体实现。
给外层节点添加类名
SSR 网站
<template>
<div class="layout-default" :class="'theme-' + domainSetting.site.theme">
<!-- ... -->
</div>
</template>
domainSetting.site.theme
就是从接口获取的关于网站的主题色信息,比如为 'blue'。渲染后的结果如下所示:
CSR 网站
const html = document.querySelector('html')
html.classList.add('theme-' + domainSetting.site.theme)
编译后得到结果如下:
使用 sass 编写样式文件
对于整体思路的第 4 步,最终效果大概是要定义如下这样的 css 代码:
但是如果我们直接使用 css 写,将来要添加新的颜色时,就会比较麻烦(因为每种主题色还有其它一些 css 代码要写,比如对框架的处理等,而不是只有 2 个 css 变量属性)。所以我将主题相关的样式代码使用 sass 编写在了 theme.scss,借助 sass 的编程能力来方便之后的维护工作。
theme.scss
@import './base';
$color-theme: (
'blue': #3d82d1,
'dark_blue': #163b8e,
'red': red
);
@each $theme, $color in $color-theme {
.theme-#{$theme} {
--primary-color: #{$color};
--primary-color-light: #{mix(#fff, $color, 10%)};
@include theme($color, mix(#fff, $color, 10%));
}
}
下面对 theme.scss 中的语法做一些解释:
导入 sass 文件
定义变量(包含 @if
及 if()
函数介绍)
$color-theme
是使用$
定义了名为color-theme
的 sass 变量,变量名注意不要使用数字开头,可以包含字母、数字、_
和-
,使用_
和-
定义的同名变量是同一个变量,比如$color-theme
和$color_theme
是相等的;- 由于是定义在最外层的,所以为全局变量;如果定义在某个选择器内,则为局部变量,但是可以在值后面添加
!global
提升为全局变量:.wrap { $red: red !global; }
; - 其值为
:
后边的('blue': #3d82d1, 'dark_blue': #163b8e, 'red': red)
,是类型为 maps 的值,用圆括号包围,相当于 js 中的 object。sass 中变量的值支持 6 种类型:- 数字;
- 字符串;
- 颜色;
- 布尔型:
true
或false
,可以结合流程控制指令@if
、@else if
与@else
做判断,比如设置暗黑模式的时候:
$dark-mode: true;
.container {
@if $dark-mode {
background-color: #000;
} @else {
background-color: #fff;
}
}
// 也可以直接使用 if() 函数,类似 js 中的三元运算
.container {
background-color: if($dark-mode, #000, #fff);
}
编译结果均为:
.container {
background-color: #000;
}
- 空值:
null
; - 数组(用空格或逗号作分隔符):比如
$theme: red blue green;
。可以结合@each
使用,通过函数index()
获取某个值在数组中的位置(从 1 开始,而不是 js 中数组的下标从 0 开始):
@each $color in $theme {
$index: index($theme, $color);
.text-#{$index} {
color: $color;
}
}
// 编译成 css 后得到的为
.text-1 {
color: red;
}
.text-2 {
color: blue;
}
.text-3 {
color: green;
}
- maps:maps 中的值除了可以像 theme.scss 那样使用
@each
遍历,也可以使用map-get()
获取(如果获取不到,则直接忽略该属性):
.container {
background-color: map-get($color-theme, 'blue');
// $color-theme 中 获取不到 key 为 'yellow' 的值,编译成 css 时会直接忽略 color
color: map-get($color-theme, 'yellow');
}
// 编译成 css 后得到的为
.container {
background-color: #3d82d1;
}
- 定义变量时还可以使用
!default
表示默认值。在 elementUI 的源码中就能看到这样的写法:
我们可以自己定义一个 $--color-primary
,然后导入 elementUI 的样式文件,elementUI 的组件有用到 $--color-primary
的地方就会使用我们自己定义的值。如果我们没有自定义 $--color-primary
则使用默认的 #409EFF
。
@each
指令
@each
与 @import
同为指令,其格式是 $var in <list>
,$var
为变量,<list>
为值列表。在 theme.scss 中,<list>
为 maps 类型的 $color-theme
,而 $var
就是 $theme, $color
。
@each $theme, $color in $color-theme {}
是对 $color-theme
的值进行遍历,$theme
分别为 'blue'
、'dark_blue'
和 'red'
,对应的 $color
就是 #3d82d1
、#163b8e
与 red
。
插值语句 #{}
- 在 theme.scss 中的
@each
指令内,我定义了类名.theme-#{$theme}
,其中#{$theme}
为插值语句,从而能够在选择器的名称中使用变量$theme
。编译成 css 后得到的就是对应的.theme-blue
、.theme-dark_blue
和.theme-red
; - 在定义 css 变量
--primary-color
时,其值也使用了插值语句#{$color}
,如果写成--primary-color: $color;
则编译后的 css 会直接照搬过去,也是--primary-color: $color;
而导致无效。
mix()
操作颜色的 mix()
函数来自于 sass 为我们提供的内置模块 sass:color,mix
其实是 color.mix
的全局别名。通过 mix(#fff, $color, 10%)
,将 #fff
和变量 $color
所对应的颜色进行混合,从而得到变量 --primary-color-light
的值。第 3 个参数 10%
表示颜色混合的权重比例,如果不传则为默认的 50%
,该值需要是一个介于 0% 和 100%(包括 0% 和 100%)之间的数字。如果这个数字越大,则表示 #fff
使用的就越多,越小则表示 $color
使用的越多。elementUI 中也是这样根据某一主色确定相应色簇的:
混合指令 @mixin
与引用混合样式 @include
在 theme.scss 中使用 @include
引用了在 _base.scss 中,使用混合指令 @mixin
定义的混合名称为 theme
的混合样式。
参数
@mixin theme()
是接收 2 个参数的,在 @include theme()
时,如果直接传递 2 个值,则会按顺序作为参数 $--primary-color
和 $--primary-color-light
的值。如果你浑身反骨,不想按顺序传递,也可以像下面这样指定参数:
@include theme(
$--primary-color-light: #{mix(#fff, $color, 10%)},
$--primary-color: $color
);
默认值
在定义 @mixin
时,也可以指定默认值,这样就不需要在 @include
传递所有参数了:
@mixin theme($--primary-color: blue, $--primary-color-light: skyblue) {}
参数变量
对于有些样式属性可能有多个值的情况,比如 border: 1px solid #efefef;
,在定义参数时可以使用 ...
:
@mixin border-style($color, $border...) {
div {
color: $color;
border: $border;
}
}
通过 @include border-style(red, 1px solid #efefef);
引用时,1px solid #efefef
都会传递给 $border
。
_base.scss
在 _base.scss 中,则是使用混合指令,定义了一些诸如 theme-primary
和 theme-primary-bg
这种方便项目中使用的样式类,以及对一些框架的样式的重置:
@mixin theme($--primary-color, $--primary-color-light) {
.theme-primary,
.nuxt-link-exact-active {
color: $--primary-color !important;
}
.theme-primary-bg {
background-color: $--primary-color;
}
// elementUI ↓
.el-button--primary {
background-color: $--primary-color;
border-color: $--primary-color;
}
// ...
// elementUI ↑
}
对于 _base.scss 的说明如下:
注释
在 sass 中,注释可以使用 /* */
或者 //
:
/*
我是注释,
并且可以写多行
*/
// 我是单行注释
它们除了多行与单行的区别外,使用 /* */
注释的内容,在编译后生成的 css 文件中也会被保留,而使用 //
写的注释则会被删除。
设置 elementUI 的主题色
似乎由于 sass 需要在编译期间转换成 css 文件的工作原理,对于 elementUI 主题色的动态设置,我目前只能采取用到了哪个组件,如果该组件涉及到了主题色,就对应的添加样式进行重置的办法。
小结
之后如果添加了某种主题色,只需在 theme.scss 文件的 $color-theme
中添加键值对即可。当然也别忘了在项目中合适的地方引用 theme.scss,比如 nuxt 项目就是在 nuxt.config.js 里配置 styleResources.scss
:
styleResources: {
scss: [
// ...
'~/assets/scss/theme/theme.scss'
]
},
对于 uniapp 项目则是在 uni.scss 文件中导入 @import '@/assets/scss/theme/theme.scss';
。
Sass 与 Scss
最后顺带介绍下 Sass 与 Scss 的关系。在一段时间内,我一直有个疑问 —— 为什么同为 css 预处理器的 less, 其文件的后缀名就是 .less,而 sass 文件的后缀名却是 .scss?后来才知道,这是因为 sass 是由 ruby 开发的,借用 sass 中文网的一句话:
它使用“缩进”代替“花括号”表示属性属于某个选择器,用“换行”代替“分号”分隔属性。
这对于习惯了 css 语法的人而言就不是很适应,而且也不能直接将 css 的代码加到 sass 文件中去。所以从 sass3 开始,sass 对语法做出了改变,也开始采用“花括号”和“分号”等写法,并且完全兼容了 css 代码,是为 scss(sassy css)。
转载自:https://juejin.cn/post/7311881316051435554