Hugo 实现深浅主题的切换

4 minute

实现深浅主题切换的方法不只一种,这里记录我所使用的一种。

生成深浅主题对应的 css 文件

配置入口文件

我的博客通过 sass 构建样式,可以将写好的 scss 文件组合生成一个 css 文件。

而组合成为 css 文件需要一个入口文件,在入口文件里指定主题。

比如我在 assets/scss/colors 中指定了两种色调 dark.scsslight.scss, 那么可以在对应的入口文件中 import:

1// assets/scss/styles_dark.scss
2@import "colors/dark";
3
4@import "variables";
5@import "util";
6@import "mixins";
7// ...
1// assets/scss/styles_light.scss
2@import "colors/light";
3
4@import "variables";
5@import "util";
6@import "mixins";
7// ...

这样,就有了 styles_dark.scssstyles_light.scss 这两个入口文件。

通过 hugo 生成目标文件

接着,在 <head></head> 中指定入口文件进行操作:

1<!-- head.html -->
2{{ $opts := (dict "targetPath" "css/styles_dark.css" "outputStyle" "compressed") }}
3{{ $styles_dark := resources.Get "scss/styles_dark.scss" | resources.ExecuteAsTemplate "scss/styles_dark.scss" . | resources.ToCSS $opts }}
4
5{{ $opts := (dict "targetPath" "css/styles_light.css" "outputStyle" "compressed") }}
6{{ $styles_light := resources.Get "scss/styles_light.scss" | resources.ExecuteAsTemplate "scss/styles_light.scss" . | resources.ToCSS $opts }}

这里通过 hugo 操作之后,即可生成深浅主题对应的两个 css 文件,位置在 css 目录下。

接着,将地址赋值到 $dark$light 中:

1<!-- head.html -->
2{{ $dark := $styles_dark.Permalink }}
3{{ $light := $styles_light.Permalink }}

保存地址变量到隐藏域

接着,通过隐藏域保存这两个地址以便后续使用:

1<!-- head.html -->
2<input value="{{ $dark }}" id="styles-dark" style="display: none;" />
3<input value="{{ $light }}" id="styles-light" style="display: none;" />

设定初始主题

一开始的主题应由网站发布者指定,读取 config 文件中的变量即可:

1<!-- head.html -->
2{{ if eq .Site.Params.colortheme "dark" }}
3<input value="dark" id="theme-current" style="display: none;" />
4<link id="theme-css" rel="stylesheet" href="{{ $dark }}" />
5{{ else if eq .Site.Params.colortheme "light" }}
6<input value="light" id="theme-current" style="display: none;" />
7<link id="theme-css" rel="stylesheet" href="{{ $light }}" />
8{{ end }}

这里又配置了一个隐藏域,用于后续确认和修改主题。

通过 js 动态设置主题

加载主题

加载步骤如下:

  • 读取用户 localStorage 中保存的主题变量;
  • 读取当前主题隐藏域值;
  • 读取 css 文件地址隐藏域值;
  • 修改主题 css 标签中的地址以加载主题。

其中,用户 localStorage 中保存的主题变量是在用户首次切换主题之后生成的,在下一步中进行介绍。

 1// theme.js
 2function setTheme(theme) {
 3  let stylesDark = document.getElementById("styles-dark").value;
 4  let stylesLight = document.getElementById("styles-light").value;
 5  let themeCss = document.getElementById("theme-css");
 6  let setThemeStyle = theme === "dark" ? stylesDark : stylesLight;
 7  themeCss.setAttribute("href", setThemeStyle);
 8}
 9
10function getCurrentTheme() {
11  // user theme
12  let userTheme = localStorage.getItem("userTheme");
13  let userThemeExpire = localStorage.getItem("userThemeExpire");
14  if (
15    userTheme !== null &&
16    userTheme != "" &&
17    userThemeExpire !== null &&
18    userThemeExpire > new Date().getTime()
19  ) {
20    document.getElementById("theme-current").setAttribute("value", userTheme);
21  }
22  return document.getElementById("theme-current").value;
23}
24
25setTheme(getCurrentTheme());

修改主题

每次修改主题都需要修改当前主题隐藏域中的值,并且在用户 localStorage 中存储或修改用户选择的主题,而设置超时时间则是一个可选项:

1// theme.js
2function changeTheme(theme) {
3  setTheme(theme);
4  document.getElementById("theme-current").setAttribute("value", theme);
5  localStorage.setItem("userTheme", theme);
6  // 设置超时时间: 24 小时
7  let expire = new Date().getTime() + 1000 * 3600 * 24;
8  localStorage.setItem("userThemeExpire", expire);
9}

将脚本放在合适的位置

theme.js 脚本需要在脚本所用到的元素加载完毕后,才能正确执行,否则将导致主题修改失败。

由于浏览器加载页面是从上到下按顺序执行的,所以只要不将脚本放在任一依赖项的上方即可让脚本顺利执行。

绑定事件到按钮

脚本编写完毕后,则将其绑定到某个按钮上即可,示例如下:

定义一个按钮:

1<a class="icon" id="theme-change"><i class="fas fa-adjust"></i></a>

绑定事件:

 1// main.js
 2// button: change theme
 3$("#theme-change").click(function () {
 4  let newTheme;
 5  if (getCurrentTheme() === "light") {
 6    newTheme = "dark";
 7  } else {
 8    newTheme = "light";
 9  }
10  changeTheme(newTheme);
11});

抚摸主页上的猫猫,美妙的事情会发生哦~