动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

网上关于设置网站主题的文章很多,很多文章还都介绍了很多种方法,咱也不整那么多花里胡哨的,本文就介绍我自己在公司项目中使用的方法。达到的效果是让一套给多个商户使用的前台网站,不同的商户可以通过后台管理系统动态地设置其前台网站的主题色。另外,对于涉及到的 sass 知识点我也会做尽可能详细的说明。

整体思路

  1. 根据实际需求,与后端约定好几个可选的颜色供管理后台设置,比如有红、蓝和深蓝:

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

  1. 管理员选择的某个颜色,会通过接口将对应颜色的名称传递给前台网站,比如选择了蓝色,那么就会传递 domainSetting.site.theme:"blue"
  2. 对于前台网站,则是想办法在页面的诸如 <html><body> 这样的外层节点,添加上一个能表明主题色信息的类名,比如蓝色对应的类名就是 theme-blue,红色对应的就是 theme-red
  3. 准备好所有在第 1 步中约定的可选颜色相应的类名,并在其中使用 css 变量定义主题色,那么页面之中所有的子节点就都可以使用 css 变量来表示主题色了。

具体实现

整体思路中的前两步没啥好说的,下面说说第 3 步的具体实现。

给外层节点添加类名

SSR 网站

前台的 PC 端我使用的是 nuxt 开发的 ssr 网站,因为 nuxt 项目里所有页面都会加载在 layouts 目录下的布局页面中的 <nuxt /> 标签内,所以我直接在 layouts\default.vue 这样的布局页面的最外层节点添加对应主题颜色的类名:

<template>
  <div class="layout-default" :class="'theme-' + domainSetting.site.theme">
    <!-- ... -->
  </div>
</template>

domainSetting.site.theme 就是从接口获取的关于网站的主题色信息,比如为 ‘blue’。渲染后的结果如下所示:

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

CSR 网站

前台的移动端我使用的是 uniapp 开发的 csr 网站,直接通过 document.querySelector() 获取 <html> 标签添加类名:

const html = document.querySelector('html')
html.classList.add('theme-' + domainSetting.site.theme)

编译后得到结果如下:

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

使用 sass 编写样式文件

对于整体思路的第 4 步,最终效果大概是要定义如下这样的 css 代码:

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

但是如果我们直接使用 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 文件

  • @import './base'; 导入了 _base.scss 文件,导入时后缀名 .scss 可以省略。虽然文件名中还有个 _ 前缀,但是导入时可以省略不写。_ 的作用在于让 sass 编译器不要把 base.scss 单独编译生成 base.css,因为该文件会被导入到 theme.scss 内;
  • 如果 @import 导入的是 .css 文件,或者是采用 @import url('./base.scss'); 写法,则都会将这一句代码作为普通的 css 语句原封不动地复制在编译后的 css 文件中。

定义变量

  • $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 种类型:
    • 数字;
    • 字符串;
    • 颜色;
    • 布尔型:truefalse,可以结合流程控制指令 @if@else if@else 做判断,比如设置暗黑模式的时候:
$dark-mode: true;
.container {
  @if $dark-mode {
    background-color: #000;
  } @else {
    background-color: #fff;
  }
}
  • 空值: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 的源码中就能看到这样的写法:

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

我们可以自己定义一个 $--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#163b8ered

插值语句 #{}

  • 在 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()

sass 为我们提供了一些内置的函数,比如操作颜色的 mix() 函数。通过 mix(#fff, $color, 10%),将 #fff 和变量 $color 所对应的颜色进行混合,从而得到变量 --primary-color-light 的值。第 3 个参数 10% 表示颜色混合的权重比例,如果不传则为默认的 50%,该值需要是一个介于 0% 和 100%(包括 0% 和 100%)之间的数字。如果这个数字越大,则表示 #fff 使用的就越多,越小则表示 $color 使用的越多。elementUI 中也是这样根据某一主色确定相应色簇的:

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

混合指令 @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-primarytheme-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)。

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

动态设置网站的主题,顺便狠狠地介绍相关 sass 用法

原文链接:https://juejin.cn/post/7311881316051435554 作者:亦黑迷失

(0)
上一篇 2023年12月15日 上午11:09
下一篇 2023年12月15日 下午4:00

相关推荐

发表回复

登录后才能评论