Skip to content
快看这页儿写了啥...

模式切换思路

模式切换一般比较基础的情况都会去做两个模式白色和黑色,因为这两种基础色可以和系统配色模式匹配,本文的重点就是怎么在使用中切换模式,接下来我们简单说一下常用的几种方案。

类名切换

这种方式其实就是在 body 元素上通过不同的 class 去控制不同主题,切换时我们切换 body 元素类名就可以了,如下:

html
<body class="dark || light">

假如我们有一断代码如下:

html
<body>
  <button class="button-toggle">点击切换主题模式</button>
  <p>hello world!</p>
  <a href="#">isboyjc</a>
</body>

按照这种方式的思路,接下来我们在不同的主体类中定义不同的样式:

css
/* 基础(白色)模式样式 light */
body {
  color: #333;
  background: #fff;
}
a {
  color: #666;
}

/* 黑色模式样式 dark */
body.dark {
  color: #eee;
  background: #111;
}
body.dark a {
  color: #ccc;
}

OK,我们使用 JS 来切换一下类名如下:

jsx
const btn = document.querySelector(".button-toggle");

btn.addEventListener("click", function () {
  document.body.classList.toggle("dark");
});

如上代码,由于 light 是基础默认色,所以我们直接忽略掉就可以,因为上面 CSS 写的时候就没有写这个类。使用 JS 获取按钮元素之后监听一下点击事件,在用户点击切换按钮时判断 body 元素中有没有 dark 类,有就删除,没有就增加。dark 类存在时,由于 CSS 级联的特性,就会覆盖掉默认的 light 样式,以此来实现主题模式切换。

码上掘金在线预览:https://code.juejin.cn/pen/7167678380098191390

但是大家有没有想过,如果我们把几种主题样式文件都放在一个文件中,使用类名去切换,虽然没问题,但是初始化时,我们必须要加载所有主题的样式,样式非常多的话首次加载其实会很浪费时间从而影响体验。

样式文件切换

我们也可以将各个主题的样式放到单独的文件中,然后使用 link 标签 href 属性引入一个默认的主题样式文件,再使用 JS 去切换 href 属性地址以此来实现主题模式切换,和上面不一样的是,这里我们直接切换整个样式文件而不是类,也就是说初始化时只需要加载一份样式就可以了。

来写个例子:

创建 light.css 文件代表基础白色模式:

css
body {
  color: #333;
  background: #fff;
}
a {
  color: #666;
}

再创建一个 dark.css 文件代表黑色模式:

css
body {
  color: #eee;
  background: #111;
}
body a {
  color: #ccc;
}

再创建一个 HTML 文件,我们在 HTML <head> 标签中通过 link href 引入一个默认主题样式文件,如下:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- 默认样式 -->
  <link href="light.css" rel="stylesheet" id="theme-mode-link">
</head>

<body>
  <button class="button-toggle">点击切换主题模式</button>
  <p>hello world!</p>
  <a href="#">isboyjc</a>
</body>

</html>

上面其实可以看到,我们给 link 标签加上了一个 ID,这是为了我们在做切换时能够获取到这个元素,接下来我们写一下切换的逻辑:

jsx
const btn = document.querySelector(".button-toggle");
const theme = document.querySelector("#theme-mode-link");

btn.addEventListener("click", function() {
  if (theme.getAttribute("href") == "light.css") {
    theme.href = "dark.css";
  } else {
    theme.href = "light.css";
  }
});

上面切换的逻辑很简单,就是获取一下触发按钮元素和引入样式文件的 link 元素,监听按钮点击事件,使用 getAttribute API 获取并判断 link 标签中的 href 属性值来区分当前是哪个主题模式并切换即可。

码上掘金在线预览:https://code.juejin.cn/pen/7167685422686928936

直接切换样式文件其实也会有些问题,其实大家应该可以发现,每次切换文件时都要先请求到资源,加载资源文件是需要一点点时间的,如果样式文件过大的话,这个时间给用户带来的体验很不好,点击切换系统主题模式,过了一会才切换成功,哪怕是短暂的空档,也会给用户造成系统操作不够圆滑的印象。

自定义属性切换

自定义属性又叫 CSS 变量,随着浏览器兼容性的不断提升,目前此方案已经是主流。

先来看看怎么用吧,还是那个 HTML

html
<body>
  <button class="button-toggle">点击切换主题模式</button>
  <p>hello world!</p>
  <a href="#">isboyjc</a>
</body>

接着写 CSS,注意,我们要把需要切换的动态值抽离成 CSS 变量:

css
/* 基础(白色)模式变量 light */
body {
  --text-color: #333;
  --bkg-color: #fff;
  --anchor-color: #666;
}

/* 黑色模式变量 dark */
body.dark {
  --text-color: #eee;
  --bkg-color: #111;
  --anchor-color: #ccc;
}

/* 样式代码 */
body {
  color: var(--text-color);
  background: var(--bkg-color);
}
a {
  color: var(--anchor-color);
}

如上,我们抽离了 CSS 变量,var() 函数可以代替元素中任何属性中的值的任何部分。该函数不能作为属性名、选择器或其他除了属性值之外的值(这样做通常会产生无效的语法或者一个没有关联到变量的值)。关于 CSS 变量语法如果有不了解的同学,建议百度、谷歌刷下文档哈。

JS 切换主题模式和第一种类名切换一致:

css
const btn = document.querySelector(".button-toggle");

btn.addEventListener("click", function () {
  document.body.classList.toggle("dark");
});

码上掘金在线预览:https://code.juejin.cn/pen/7167707327213076516

虽然看着和第一种方案差不多,但是如果只把需要切换的值抽离成变量的话,其实由于系统风格统一,这些变量并不会有太多,哪怕是一次加载完所有主题模式的变量,也不会给首次加载带来太大影响,而且后期在配置其他主题色的时候也会大大提高效率,当然如果你的项目非常大并且变量超级多的话,也可以尝试把不同的主题变量抽离成单个文件,并且使用预加载的方式来优化切换加载这块儿的时间。

通常情况下我们会使用 HTML 的自定义属性加上属性选择器而不是使用 calss 来做切换,因为主题模式标识并不与 DOM 元素关联,我们只是通过一个标识去让浏览器选择不同的变量样式,使用 calss 一眼看上去让人觉得它是一个样式,语义会很不明确,当然你要是硬要用 calss 那也不是不可以。

接下来我们使用 HTML 的自定义属性加上属性选择器修改一下上面代码,HTML 方面没有改动,还是原来的样子:

html
<body>
  <button class="button-toggle">点击切换主题模式</button>
  <p>hello world!</p>
  <a href="#">isboyjc</a>
</body>

JS 切换时,我们使用 getAttribute & setAttribute 方法获取 body 标签的自定义属性 theme 并设置主题模式:

jsx
const btn = document.querySelector(".button-toggle");

btn.addEventListener("click", function () {
	if(document.body.getAttribute("theme") === 'dark'){
		document.body.setAttribute('theme', '')
	}else{
		document.body.setAttribute('theme', 'dark')
	}
});

CSS 也许要换成属性选择器语法 body[theme=dark]

jsx
/* 基础(白色)模式变量 light */
body {
  --text-color: #333;
  --bkg-color: #fff;
  --anchor-color: #666;
}

/* 黑色模式变量 dark */
body[theme=dark] {
  --text-color: #eee;
  --bkg-color: #111;
  --anchor-color: #ccc;
}

body {
  color: var(--text-color);
  background: var(--bkg-color);
}
a {
  color: var(--anchor-color);
}

关于 CSS 属性选择器,不了解的同学可以看看 菜鸟教程-属性选择器 哈!

当选择 dark 模式时,HTML 如下:

jsx
<body theme="dark">
  <button class="button-toggle">点击切换主题模式</button>
  <p>hello world!</p>
  <a href="#">isboyjc</a>
</body>

码上掘金在线预览:https://code.juejin.cn/pen/7167719012283973669

当然我们不一定非要把属性挂在 body 上,挂在 html 上也可以,写 CSS 变量时使用 html{…} 或者 :root{…} 都可以,:root 是一个伪类,表示文档根元素。

思路大概就是这样,当然具体使用那种还是要看需求决定,一般情况下我们会使用第三种,接下来我们项目实践中其实也是基于这种方式。

最后

之前写项目时我们说使用 ArcoDesignCSS 变量后面我们做黑白模式切换时会很方便想必大家现在理解什么意思了吧,因为如果我们不使用 ArcoDesignCSS 变量的话,想要做模式切换,就得自己写一套 CSS 变量,当然这也不是想写就写的,它需要专业的设计师设计一套合适的主题色,我们一切从简,所以就直接使用 UI 库定义的 CSS 变量了。

ArcoDesign 暗黑模式文档 上其实写的很清楚,主题的模式切换我们只需要在 body 元素中设置 自定义属性 arco-theme 即可,如下:

jsx
// 设置为暗黑主题
document.body.setAttribute('arco-theme', 'dark')

// 恢复亮色主题
document.body.removeAttribute('arco-theme');

所以,我们不需要考虑 CSS 变量的问题,只需要做切换就 OK 了!除此之外,我们还需要做一个跟随系统的选项,因为目前用户的操作系统中都允许用户直接在系统中设置深色和浅色主题模式,我们可以使用 JS 浏览器 APIJS Bom ) 的 matchedMedia 方法来检测用户的系统配色偏好,以此来展示默认主题模式。

你可以尝试在浏览器控制台输出如下代码:

jsx
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");

if(prefersDarkScheme.matches){
	console.log("dark")
}else{
	console.log("light")
}

不正经的前端