如何优雅的实现深色浅色主题

1,225 阅读7分钟

深浅主题色最佳实践

前言

现在越来越多的网站能够支持深色浅色两种主题颜色了

系统换肤我们公司的主要的方案就是设置 :root 中的颜色值,在使用到颜色样式的时候使用 css 变量进行替换。

那我们很快就可以想到,深色和浅色的颜色主题也能使用这种方式实现。在 :root 中写好两套颜色值,然后用户切换主题的时候,更改:root变量就可以了。

那么本次文章就分享到这了,我们下次再见。(doge)

自动换肤

在我们浏览上面支持换肤的网站的时候,我们很快就能发现,如果我们系统当前是浅色主题的时候,网站默认就会显示浅色模式,如果我们系统是深色主题的时候,网站就会显示深色模式。

甚至在我们切换主题色的时候,网站能够不需要刷新自动的跟随我们主题的颜色。

so cool!

那今天我们就来探索一下这个是如何实现。

color-scheme

color-schemecss3 中的一个属性。

前言

在我介绍它之前,我们先思考一下,在我们的系统切换深浅色的时候,我们的使用软件的主题都会跟随切换主题色。作为一个前端开发,我们着重考虑一下 谷歌浏览器。

首先 看一下谷歌浏览器在深浅色下的表现。

  • 系统主题浅色下的谷歌浏览器

系统主题浅色下的谷歌浏览器

  • 系统主题深色下的谷歌浏览器

02.png

首先我们最直观的就能发现,F12 的控制台背景色在浅色模式下是浅色的,在深色模式下是深色的。

ok,第一个比较重要的点来了,上面的表现,是不是说明我们的谷歌浏览器他内置了深浅色的主题。

如果谷歌浏览器有深浅色的配色,那有没有什么办法我们可以获取到呢?

详细介绍

好的,我们就来介绍一下 color-scheme 属性

color-scheme: normal; // 使用默认配色
color-scheme: light; // 使用浅色配色
color-scheme: dark; // 使用暗色配色
color-scheme: light dark; // 支持两种配色,跟随系统切换

现在我们的系统使用浅色模式。

dom结构如下

import { useState } from 'react';
import reactLogo from './assets/react.svg';
import './App.css';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <h1>深浅主题色最佳实践</h1>
      <div className="container">
        <button>按钮</button>
      </div>
    </div>
  );
}

export default App;

我们现在修改 css ,来观察浏览器的表现

  • color-scheme: light; 的表现

03.png

浏览器默认就是浅色的,所以设置了这个也没啥区别

  • color-scheme: dark; 的表现

04.png

这个就可以很明显的看出来区别了,背景变成黑色,h1 文字是白色,整体就是使用了深色的主题

  • color-scheme: dark light; 的表现

05.png

我们发现,当属性值写成 dark light 的时候,即使dark在前面,也没有使用深色主题。那写成两个和写成一个的时候区别在哪呢?

属性值是一个固定值的时候,我们发现,不管怎么切换系统的主题色,页面的表现都是跟随属性值来的,但是属性值写成两个的时候,页面的表现就是根据系统来的,系统切换主题的时候,页面也会切换主题色。

原理浅析

设置 color-scheme: dark light; 样式的时候,我们发现页面中的元素颜色是会跟随系统的主题色的。

或者你的系统是浅色,设置 color-scheme: dark; 样式的时候,浏览器会使用深色模式。

我们直接选中元素,看一下他的值是什么。

先看一下 h1

06.png

我们发现。设置深色和浅色的时候,h1 标签的color属性会自动变化

浅色模式下,文字的颜色是黑色,深色模式下,文字的颜色是白色。

再来看一下 button

07.png

我们发现 button 的颜色并不是一些颜色值,而是一个个的变量。

类比 我们自己定义 css 变量,这些变量是谷歌浏览器内置的,在深浅主题下会去取不同的值,当然,这些变量我们也可以使用,这里就不做演示了。

总结

  • color-schemecss3 中的一个属性,用于当前界面的主题色控制
  • 默认是浅色主题
  • 在我们设置 color-scheme: dark light; 之后,当前界面可以根据系统主题进行切换颜色

prefers-color-scheme

前言

使用 color-scheme,我们确实可以控制当前的主题色,也可以做到跟随系统的主题切换界面的主题色,但是我们不能很好的定制 , 深色浅色的主题 具体的颜色是用的谷歌浏览器的默认值。

那我们现在想要一些丰富的,或者高度自定义的颜色,这个好办,我们写两套 css 变量就能实现。

那么我们如何做到跟随系统的换肤呢?

准备工作

我们先实现一个可以点击按钮进行颜色切换的界面。

// 简单的需求分析

// 界面上有 标题 按钮 超链接

// 浅色模式下
// 背景白色,标题文字黑色
// 超链接是 绿色

// 深色模式下
// 背景黑色,标题文字白色
// 超链接是 红色

// 按钮的主题跟随默认,点击按钮能够切换主题

代码实现

function App() {
  const toggleTheme = () => {
    document.documentElement.classList.toggle('dark');
  };

  return (
    <div className="App">
      <h1>深浅主题色最佳实践</h1>
      <div className="container">
        <div>
          <button onClick={toggleTheme}>按钮</button>
        </div>
        <div>
          <a href="#">我是一个超链接</a>
        </div>
      </div>
    </div>
  );
}
/* 默认主题色 */
:root {
  --bg: #fff;
  --text-color: #000;
  --link-color: green;
}
/* 深色 */
.dark {
  color-scheme: dark;
  --bg: #000;
  --text-color: #fff;
  --link-color: red;
}
body {
  background: var(--bg);
}
h1 {
  color: var(--text-color);
}
a {
  color: var(--link-color);
}

实现起来也很简单,切换主题的时候设置 class,然后用 dark class 去覆盖 :root中的css变量

至此,主动换肤的功能实现呢,那么如何实现根据 系统的主题来做换肤呢?

详细介绍

css 实现

好的,我们就来介绍一下 prefers-color-scheme ,它是一个媒体查询,用来检测用户系统的颜色。

那么现在 css 可以修改成如下写法。

/* 默认主题色 */
:root {
  --bg: #fff;
  --text-color: #000;
  --link-color: green;
}
/* 深色 */
.dark {
  color-scheme: dark;
  --bg: #000;
  --text-color: #fff;
  --link-color: red;
}
body {
  background: var(--bg);
}
h1 {
  color: var(--text-color);
}
a {
  color: var(--link-color);
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --bg: #000;
    --text-color: #fff;
    --link-color: red;
  }
}

我们默认浅色的,使用媒体查询,如果用户系统是深色的,我们就改变 :root中的值,这样就能够做到用户的界面跟随系统主题了。

但是现在有一个问题,就是css选择器的优先级问题,导致用户不能通过点击按钮设置主题色了。

js 实现

媒体查询的弊端就是css的样式一直存在,查询结果为true的时候样式就覆盖不了了,

其实我们只需要在页面最开始渲染的时候获取到一次当前用户的主题,以及用户切换主题的时候切换页面主题就好了。

代码如下

css

/* 默认主题色 */
:root {
  --bg: #fff;
  --text-color: #000;
  --link-color: green;
}
/* 深色 */
.dark {
  color-scheme: dark;
  --bg: #000;
  --text-color: #fff;
  --link-color: red;
}
body {
  background: var(--bg);
}
h1 {
  color: var(--text-color);
}
a {
  color: var(--link-color);
}

js

// 根据 prefersDarkScheme 媒体查询值设置主题
function setThemeByPrefersDarkScheme(matches) {
  if (matches) {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
}

function App() {
  const toggleTheme = () => {
    document.documentElement.classList.toggle('dark');
  };

  useEffect(() => {
    const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
    // 初始化的时候设置一下
    setThemeByPrefersDarkScheme(prefersDarkScheme.matches);
    // 设置监听
    prefersDarkScheme.onchange = (e) => {
      setThemeByPrefersDarkScheme(e.matches);
    };
    return () => {
      // 置空监听
      prefersDarkScheme.onchange = null;
    };
  }, []);

  return (
    <div className="App">
      <h1>深浅主题色最佳实践</h1>
      <div className="container">
        <div>
          <button onClick={toggleTheme}>按钮</button>
        </div>
        <div>
          <a href="#">我是一个超链接</a>
        </div>
      </div>
    </div>
  );
}

总结

  • color-scheme 能够设置界面当前的主题色,设置 color-scheme: dark light; 页面的主题色就可以根据系统变化
  • 缺点就是不能够自定义,深色主题下的颜色是浏览器内置的
  • 使用 prefers-color-scheme 媒体查询能够获取到当前的主题颜色
  • window.matchMedia('(prefers-color-scheme: dark)'); 能获取媒体查询的结果以及设置监听事件
  • 配合 css 变量,就能做到完美的 深浅色主题