Article

前端暗模式切换和用户偏好存储

深色模式为目前网络发展的一大趋势,可以看到大量的网站为了提高网站的体验都添加了深色模式。深色模式在光线不足的情况下看起来不会那么刺眼,能够很好的保护我们的眼睛。 在这边文章中主要讲如何使用CSS和JS实现深色模式和浅色模式的任意切换

分析需求

假设有这么一个页面,我们需要自由切换深色模式和浅色模式。那么就需要在不同模式使用不同的 css,这里可以通过两种方式一种是直接引入不同的 css 文件,另外一种通过更改 css 变量值的方式进行更改样式。

具体实现

首先定义浅色模式的变量名和变量值

:root {
  --primary-bg: #eee;
  --primary-fg: #000;
  --secondary-bg: #ddd;
  --secondary-fg: #555;
  --primary-btn-bg: #000;
  --primary-btn-fg: #fff;
  --secondary-btn-bg: #ff0000;
  --secondary-btn-fg: #ffff00;
}

当切换场景的时候需要更改 css 变量的值,更改如下:

:root {
  --primary-bg: #282c35;
  --primary-fg: #fff;
  --secondary-bg: #1e2129;
  --secondary-fg: #aaa;
  --primary-btn-bg: #ddd;
  --primary-btn-fg: #222;
  --secondary-btn-bg: #780404;
  --secondary-btn-fg: #baba6a;
}

可以看到当切换到深色模式的时候,变量使用了更加暗的颜色,从而实现深色模式

更改 css

如何切换到暗模式有多种解决方法,在这里我们使用媒体查询,prefers-color-scheme 这个媒体查询能够获取到用户的系统是否切换到了深色主题,具体如下:

@media (prefers-color-scheme: dark) {
  :root {
    --primary-bg: #282c35;
    --primary-fg: #fff;
    --secondary-bg: #1e2129;
    --secondary-fg: #aaa;
    --primary-btn-bg: #ddd;
    --primary-btn-fg: #222;
    --secondary-btn-bg: #780404;
    --secondary-btn-fg: #baba6a;
    --image-opacity: 0.85;
  }
}

如果希望用户可以通过选择系统的设置来切换浅色模式还是深色模式,那么上面这种方式就足够了。

浏览的网站能够通过系统设置选择不同的样式,但是上面这种方式存在一个问题,就是用户希望这个页面的模式不要跟随系统配置的更改而更改。用户可以主动更改网站的模式,那么上面这种方式就不合适了。

偏好模式

思路就是通过控制 js 来给元素添加不同的 class,不同的 class 拥有不同的样式。首先添加在 html 中添加一个按钮用于切换不同的模式

<button id="toggle-button">toggle</button>
<script>
  const toggleButton = document.querySelector('#toggle-button')
</script>

然后需要地方存储用户的偏好设置,这里使用 localStorage 来存储用户的选择。给按钮添加事件用于切换主题,下面是具体的代码:

const toggleButton = document.querySelector('#toggle-button')

toggleButton.addEventListener('click', (e) => {
  darkMode = localStorage.getItem('theme');
  if (darkMode === 'dark') {
    disableDarkMode();
  } else {
    enableDarkMode();
  }
});

function enableDarkMode() {
  localStorage.setItem('theme', 'dark');
}

function disableDarkMode() {
  localStorage.setItem('theme', 'light');
}

现在我们就可以存储这个用户的偏好设置。然后不同的主题下给 body 元素添加不同的 class,具体如下:

function enableDarkMode() {
 document.body.classList.add("dark-mode")
  localStorage.setItem('theme', 'dark');
}

function disableDarkMode() {
  document.body.classList.remove("dark-mode")
  localStorage.setItem('theme', 'light');
}

和媒体查询一样,在dark-mode的情况下更改css变量的属性值,具体如下:

.dark-mode {
  --primary-bg: #282c35;
  --primary-fg: #fff;
  --secondary-bg: #1e2129;
  --secondary-fg: #aaa;
  --primary-btn-bg: #ddd;
  --primary-btn-fg: #222;
  --secondary-btn-bg: #780404;
  --secondary-btn-fg: #baba6a;
  --image-opacity: 0.85;
}

同时在进入这个页面的时候需要获取到用户的偏好设置,从 localStorage 中读取

let darkMode = localStorage.getItem("theme")

if (darkMode === "dark") enableDarkMode()

这次就可以在页面刷新以后仍然拿到用户的偏好设置。但是这种方案仍然存在一定的问题,就是我们期望用户没有选择模式的时候,页面的模式能够跟随用户本身系统的设置而更改。

最终方案

window 上有个方法叫 matchMedia,我们可以通过它获取当前用户系统处于什么模式,具体代码如下:

window
  .matchMedia("(prefers-color-scheme: dark)")
  .addListener(e => (e.matches ? enableDarkMode() : disableDarkMode()))

这样就能保证用户在没有设置偏好的时候使用默认的系统主题,完整代码如下:

const toggleButton = document.querySelector("#dark-mode-toggle")
  let darkMode = localStorage.getItem("theme")

  if (darkMode === "dark") enableDarkMode()

  toggleButton.addEventListener("click", e => {
    darkMode = localStorage.getItem("theme")
    if (darkMode === "dark") {
      disableDarkMode()
    } else {
      enableDarkMode()
    }
  })

function enableDarkMode() {
  document.body.classList.add("dark-mode")
  localStorage.setItem("theme", "dark")
}

function disableDarkMode() {
  document.body.classList.remove("dark-mode")
  localStorage.setItem("theme", "light")
}
window
.matchMedia("(prefers-color-scheme: dark)")
.addListener(e => (e.matches ? enableDarkMode() : disableDarkMode()))

其他

以下代码适用于单页面

<style>
  body.dark {
    --bg-color: #000;
    --bg-secondary-color: #131316;
    --font-color: #f5f5f5;
    --color-grey: #ccc;
    --color-darkGrey: #777;
  }
</style>
<script>
  if (window.matchMedia &&
      window.matchMedia('(prefers-color-scheme: dark)').matches) {
    document.body.classList.add('dark');
  }
</script>