uniapp 主题切换的实践

如何更方便的实现uniapp-小程序的主题切换?

Posted by My on February 11, 2025

前言

「主题切换」在我的认知里有两种方法,一是使用类名,二是自定义变量。

  • 类名

    .light.dark 是不同颜色的两套样式文件,根据当前的主题,给根元素添加对应的类名。

  • 自定义变量

    即声明自定义变量,在 「style」中直接使用自定义变量。

我更加青睐于「自定义变量」,在网页端的项目里,曾有过实践。

网页端主题

核心是 「同一个自定义属性」,两种不同的颜色,在进行主题切换时,加载对应的颜色变量。

1
2
3
4
5
6
7
html[data-theme="dark"] {
  --main-text-color: #ffffff;
}

:root {
  --main-text-color: #333333;
}

这里使用的是 css 自定义变量,通过 var 来使用。 我也忘了当初为什么不用 scss 变量,应该是可以的。 例如,主题切换到 dark 时,给 html 添加 自定义属性 data-theme="dark" , 就能使用此设置下自定义变量。 而在 「style」中,使用更加方便,如

1
2
3
.text {
  color: var(--main-text-color);
}

uniapp 主题

在 「uniapp」 没有自定义属性,因此哪怕说通过控制按钮,手动地或者通过 api 拿到系统的主题,「uniapp」都无法加载这个环境,都很难给 「uniapp」添加主题。比如说,使用缓存,已经将「theme」设置为 dark ,那「uniapp」如何使用这个状态的样式。在官方文档中,主题切换有两方面的设计,一是「page.json」 中使用对应的颜色变量,而针对 css 则使用 「媒体查询」结合类名。

在此,「uniapp」的这一套设计,已经是限制了主题是跟随系统的,没有所谓的「白天、黑夜、跟随系统」三种模式。

初始方案

因为本项目微信小程序,使用了 「自定义 tabbar」和「自定义 navbar」,因此对于「page.json」的配置关注并不多,主要是针对 css 的。开始的方案也是使用了类名,样式如下:

1
2
3
4
5
6
7
8
9
10
@media (prefers-color-scheme: dark) {
  .main-text {
    color: #cdcdcd;
  }
}
@media (prefers-color-scheme: light) {
  .main-text {
    color: #333;
  }
}

这种情况下是完全 「跟随系统」的,当系统主题发生变化时,这一属性 prefers-color-scheme 会识别到系统的主题,并加载对应的设置,因此在此配置中,很难手动地去设置主题。比如说,系统主题是 dark,这时手动的设置 theme=dark, 那程序是如何识别这个变量并使用对应的配置,这是个难点。

回归正传,此次的主题切换,也默认使用了跟随主题,那来看看如何使用此配置的,在 .vue 中使用。

1
<text class="main-text">GPS编号</text>

给对应的标签加上所需要用到类名即可,也就是说,这种主题配置方案,主要是作用于 「template」 而与 「style」 关系不大。

随着方案的使用,逐渐发现了一种弊端:无法设置组件的样式。很容易理解,我们是通过给元素添加类名以达到颜色替换的效果,但是无法给组件添加类名,哪怕添加了类名,组件里的具体元素也无法识别到。

因此,我的思绪又回到以前 ———— 使用自定义变量。这时候,变量是作用于 「style」的,更加方便的使用。

最终方案

刚才是想使用 scss 变量,即

1
2
3
4
5
6
@media (prefers-color-scheme: dark) {
  $mian-text: #fff;
}
@media (prefers-color-scheme: dark) {
  $mian-text: #333;
}

App.vue 的 「style」中引入后,即可在任意 .vue文件里使用,如

在 「style」中使用

1
2
3
.text {
  color: $main-text;
}

理想是美好的,现实是残酷的,无法使用该变量。 image.png

查阅相应资料后,得出结论:

在 uniapp 中,@media (prefers-color-scheme: dark) 和 @media (prefers-color-scheme: light) 的 CSS 媒体查询内定义的 SCSS 变量 只能在其定义的作用域内使用,因此作用域是局限的,在外部无法访问。

因此,方案是使用 「css 自定义属性」来代替 「scss 变量」。

1
2
3
4
5
6
7
8
9
10
:root {
  --main-text: #333;
}

/* 根据暗黑模式调整颜色 */
@media (prefers-color-scheme: dark) {
  :root {
    --main-text: #cdcdcd;
  }
}

uni.scss 中导入此文件(在App.vue中导入无效)。 此时,已经能在 「style」中使用了

1
2
3
.text {
  color: var(--main-text);
}

当然了,如果不习惯通过 var 变量来使用,可以再包一层。

1
2
3
4
5
6
7
8
$bg: var(--bg);
$cell-bg: var(--cell-bg);
$color: var(--color);
$value-color: var(--value-color);
$label-color: var(--label-color);
$border: var(--border);
$input-bg: var(--input-bg);
$placeholder-color: var(--placeholder-color);
1
2
3
.text {
  color: $color;
}

问题

回来之前疑问,如果手动配置主题呢? 即跟随系统时,此时的主题是「dark」,在 @media (prefers-color-scheme: dark) 下如何使用 「light」主题