前端变色方案

随着IOS深色模式的普及,很多App支持了白天/深色主题。当然Web页面也可以做到。

dark_mode

方案

变色的核心,是色值和背景图的改变,以下是一些实现方案:

CSS滤镜 filter: brightness()

brightness() - CSS(层叠样式表) | MDN

懒人做法,可以通过改变<html>标签即整个页面的输出亮度实现夜晚变暗的效果,值为0-1,0表示全黑。

简单粗暴、效果单一,兼容性尚可。但是有个坑,即元素background不能为none或未设置,否则brightness()无效果。

brightness

效果:




JS变量

配置js全局变量存储颜色,写内联样式

1
2
3
4
5
6
7
8
9
10
11
12
13
const theme = {
color: "yellow",
backgroundColor: "green"
}

const mode = "night"

get themeStyle() {
return {
// 通过图片命名来区分不同主题
backgroundImage: require(`./assets/icon-name-${mode}.png`)
}
}
1
2
<p :style="{ color: theme.color }">text</p>
<p :style="themeStyle">text</p>

优点: 无兼容性

缺点:除了写起来很麻烦,在处理图片时,为了兼容明暗两种图片,需要单独处理一次,工作量大且不易维护。不建议使用。

Sass/Less/Stylus等预编译器函数

通过预编译器变量和函数生成多套主题CSS,这里以Scss为例

定义Scss变量

1
2
3
4
5
6
7
8
9
10
11
// variable.scss

// 主题色
$color-primary: #25d4d0;
$color-primary-night: #20a6af;

$color-white: #fff;
$color-white-night: #12121f;

$color-text-default: #474747;
$color-text-default-night: #686881;

定义主题map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// theme.scss

// 白天模式色值map
$theme-daytime: (
// 背景色
bg-default: $color-white,
// 边框色
border-primary: $color-primary,
// 文本色
text-default: $color-text-default
);

// 深色模式色值map
$theme-daytime: (
// 背景色
bg-default: $color-white-night,
// 边框色
border-primary: $color-primary-night,
// 文本色
text-default: $color-text-default-night
);

// 主题名map
$themes: (
daytime: $theme-daytime,
night: $theme-night,
...
);

mixin 方法生成多种主题样式

处理颜色

参数:

  • $type 颜色相关属性,默认为background-color,可以是colorborder-color
  • $typeColor 色值,为主题模式色值map中的key,默认bg-default
  • $alpha 透明度,值为 0-1
1
2
3
4
5
6
7
@mixin setThemes($type: background-color, $typeColor: 'bg-default', $alpha: 1) {
@each $themename, $theme in $themes {
.theme-#{$themename} & {
#{$type}: rgba(map-get($map: $theme, $key: $typeColor), $alpha);
}
}
}

使用:

1
2
3
.header {
@include setThemes(color, text-default);
}

最终生成:

1
2
3
4
5
6
.theme-daytime .header {
color: #474747;
}
.theme-night .header {
color: #686881;
}
处理图片

图片也存在深色模式时,通过图片命名来区分:

icon-success-daytime.png

icon-success-night.png

参数:

  • $imgUrl 图片地址前缀
  • $imgType 图片格式后缀,默认png
1
2
3
4
5
6
7
8
9
10
// 设置背景
@mixin setThemesBgImg($imgUrl, $imgType: 'png') {
@each $themename, $theme in $themes {
.theme-#{$themename} & {
@if $imgUrl {
background-image: url(#{$imgUrl}-#{$themename}.#{$imgType});
}
}
}
}

使用:

1
2
3
4
5
6
7
.header {
@include setThemesBgImg('./assets/icon-success');
}

.footer {
@include setThemesBgImg('./assets/icon-success', gif);
}

最终生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
.theme-daytime .header {
background-image: url('./assets/icon-success-daytime.png');
}
.theme-night .header {
background-image: url('./assets/icon-success-night.png');
}

.theme-daytime .footer {
background-image: url('./assets/icon-success-daytime.gif');
}
.theme-night .footer {
background-image: url('./assets/icon-success-night.gif');
}

同理也可以处理box-shadow

改变主题

通过改变根DOM的class来应用主题

1
2
3
4
5
6
7
<div class="theme-daytime">
<header></header>
</div>

<div class="theme-night">
<header></header>
</div>

优缺点

优点: 从上面可以容易看到,通过该方法实现变色无兼容性问题,且切换主题方便处理图片也毫不费力。

缺点: 但当主题非常多时,会生成很多主题样式代码,全部打到包里,造成样式代码冗余,即使用户并不用这些主题。

CSS变量

使用CSS变量 - CSS(层叠样式表) | MDN

定义变量

1
2
3
4
5
6
7
8
9
:root {
--color: #fff;
--bgColor: #25d4d0;
}

.header {
color: var(--color);
background: var(--bgColor);
}

改变主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
changeTheme(mode: "daytime" | "night") {
const theme = {
daytime: [
"--color: #000",
"--backgroundColor: green"
],

night: [
"--color: #eee",
"--backgroundColor: #000"
]
}
document.documentElement.setAttribute('style', theme[mode].join(";"))
}

优点:

通过js动态改变css变量的好处之一,就是可以通过后端接口获取主题变量色值,通过后台可配置无限种主题,且不会产生冗余的CSS代码。

缺点:

  • 不能支持可变背景图片,因为CSS变量不支持写路径
  • 兼容性堪忧,尤其是国内的环境

css_var

但是Apple官网用了。

apple

支持可变背景图片

结合上面的Scss方案,写可变背景图时,使用上面提到的Scss的 setThemesBgImg mixin方法

结论

在主题不多时,如仅有白天&深色模式,适合用预编译器方案;

在主题很多,且不考虑兼容性时(如国际C端),用 css变量写颜色 + Scss方法写背景图;

在主题很多且考虑兼容性时(如国内C端),使用预编译器方案写样式。再开发一个webpack打包插件把单个主题样式代码抽离出来打包成单文件,切换主题时动态加载样式文件。