Featured image of post 开源项目 sub-store 之主题

开源项目 sub-store 之主题

前言

这段时间参与了一个开源项目 Sub-Store,前端为 PWA(Progressive Web Apps) 实现,新版前端由我从 0 开始搭建,技术栈为 Vite2、Vue3、Pinia,期间有很多问题,但也逐一解决了。这篇是第一篇总结,关于 前端项目自定义主题 的一些解决方案。

由于 颜值即正义,而且 萝卜青菜,各有所爱,所以要实现一个所有用户都喜欢的主题样式是很难的,并且我也是 UI 设计出身,自然而然的就想到了自定义主题。目前此项目实现了 自动主题和固定主题切换、主题分类选择(黑夜/白天)

通过 CSS 变量实现主题切换

最初搭建项目时的不同主题实现方案是在 body 添加 theme 属性,使用 SCSS 嵌套选择器来指定颜色样式,但是这种方案不够灵活,因为它只能指定一个主题,而且它的样式不能被继承。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.dark {
  .title {
    color: $dark-title-color;
  }

  .content {
    color: $dark-content-color;
  }
}

.light {
  .title {
    color: $light-title-color;
  }

  .content {
    color: $light-content-color;
  }
}

显然我太天真。这种方法有很大的问题:

  1. 由于使用了 scss 预处理器,它实际上会被编译为 css,scss 变量会变成实际的值,并且多个主题都会同时写入运行时 css 文件,这样会如果项目过大或主题过多时 css 文件过大。
  2. 这种写法当可选主题很多,十个二十个时候,每种主题都需要单独的 scss 文件,这样会导致项目的代码量大,而且项目的维护也会比较麻烦。
  3. 如果用户更换主题,那么所有的样式都会被重新计算,这样会导致页面的渲染,这是一个比较消耗性能的操作。

由于两套选择器内要写不同的变量名,而且最终变量还是会变成常量,自然而然的就会发问了:有没有什么办法可以让他在运行时也是变量,并且可以动态切换变量值呢?

答案当然是有的 —— css 变量。

使用 CSS 变量

首先,我们需要定义一组 css 变量,然后在不同的 class 内对变量指定对应的的值,这样我们切换主题时只需要切换根节点的 class 即可实现动态切换主题。

1
2
3
4
5
<body class="theme_dark">
  <div class="container">
    <p>Test</p>
  </div>
</body>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
:root{
  --background: #fff;
  --color: #000;
}

.theme_dark {
  --background: #000;
  --color: #fff;
}
.theme_light {
  --background: #fff;
  --color: #000;
}

.container {
  background: var(--background);
  color: var(--color);
}

自动化

以上的方案还是不够灵活,我每一套主题都要在 css 文件里写一套变量值,且扩展其他功能时也不是很方便。

此项目是 Vue3 技术栈,我使用了自定义 hooks 并配合一个 themes 文件夹来实现更加方便的主题管理。

查看 hook 源码

查看主题文件夹

theme 文件示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
export default {
  meta: {
    name: '基础日间',
    author: 'DesnLee',
    label: 'light',
    extend: '',
  },
  colors: {
    'primary-color': '#478EF2',
    'primary-color-end': '#496AF2',
    // ...更多变量
  },
};

这里的 meta 字段是主题的信息,name author 这两个字段是在页面中需要展示的,label 字段主要是用来区分主题类型,以便展示在对应的 select 列表中,extend 则是为了其他人进行自定义主题时更加方便,在遍历时会读取 extend 字段,找到对应的主题文件,然后基于该主题进行扩展。

整体主题切换思路

  1. hooks 中对 themes 文件夹遍历读取,根据 label 字段进行分类放进对应的选择器中,并根据 name author 生成选择器中的显示名称;
  2. 读取当前主题,根据当前主题的 extendcolor 生成当前主题的变量列表,遍历修改 root 中的变量值实现初始化主题;
  3. 当用户切换主题时,根据主题的 extendcolor 新主题的变量列表,遍历修改 root 中的变量值实现切换主题。

这里我根据需要实现了自动和手动切换主题功能,并且添加新主题无需做其他修改,仅需添加一个主题文件,按规范填写字段后,重新打包即可生效。

这套方案目前来说还是比较符合这个项目的需求,一步一步根据项目需求摸索出来的方式,可能不是很通用,仅供参考。