页面增加 dark/light 模式

#JavaScript #CSS #HTML 2022/04/21 07:48:07
目录
  1. CSS 一键反转
  2. @media 查询
  3. 通过 JS 控制主题
  4. JS 操作与媒体查询
  5. 其他

一直想着给 babyblue 添加黑夜模式,终于拖延到今天动手,在未开始之前总觉得修改东西会很麻烦,真正做起来倒觉得事情都很简单,还学到了新东西,趁此总结一下。

CSS 一键反转
html {
  filter: invert(1) hue-rotate(180deg);
}
  • filter CSS 属性 filter 将模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像、背景和边框的渲染。 blur(1px) 函数用于模糊图像;brightness(0.5) 函数 用于调整图像的亮度;contrast(2) 函数用于调整图像的对比度;grayscale(1) 函数用于调整图像的灰度;hue-rotate() 函数用于旋转图像的色相;invert(1) 函数用于反转图像的颜色;opacity(0.5) 函数用于调整图像的不透明度;saturate(100) 函数用于调整图像的饱和度;sepia(0.5) 函数用于调整图像的褐色;drop-shadow(x 偏移,y 偏移,模糊大小,色值) 函数用于为图像添加阴影。
@media 查询

prefers-color-scheme CSS 媒体特性用于检测用户是否有将系统的主题色设置为亮色或者暗色。

  • no-preference 表示系统未得知用户在这方面的选项。在布尔值上下文中,其执行结果为 false。
  • light 表示用户已告知系统他们选择使用浅色主题的界面。
  • dark 表示用户已告知系统他们选择使用暗色主题的界面。
@media (prefers-color-scheme: dark){
    .theme-icon
         background-image: url('/images/dark.png')
}
@media (prefers-color-scheme: light) {
     .theme-icon
        background-image: url('/images/light.png')
 }

如上,可以将媒体查询放在一个 CSS 样式文件里。

为了更好的修改和维护,也可以分别设置 dark.css、light.css 文件,style.css 存放为其他通用的样式,使用 CSS 变量定义不同的主题颜色。

/* dark.css */
:root {
  --background-color: #1f1f1f;
  --text-color: #ccc;
  --link-color: #ececec;
}

/* light.css */
:root {
  --background-color: #fff;
  --text-color: #404040;
  --link-color: #2196f3;
}

/* style.css */
body {
  background-color: var(--background-color);
  color: var(--text-color);
}
a {
  color: var(--link-color);
}
<!-- 引入样式 -->
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="/dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/light.css" media="(prefers-color-scheme: no-preference),(prefers-color-scheme: light)"

:root 这个 CSS 伪类匹配文档树的根元素。对于 HTML 来说,:root 表示 <html> 元素,除了优先级更高之外,与 html 选择器相同,通常用来声明全局 CSS 变量。

通过 JS 控制主题

通过媒体查询系统的主题色决定网页的主题很方便,但是想要用户能够主动交互切换主题时,媒体查询就无能为力了,这个时候就要靠 JS 操作。

前面分别为 light/dark 模式设置了不同颜色的变量,为了控制在不同模式下响应的变量,可以通过属性选择器控制根节点 CSS 变量。

html[data-theme='dark']:root {
  --progress-color: linear-gradient(to right, #c2e59c, #64b3f4) --background-color: #1f1f1f --text-color: #ccc;
}

html[data-theme='light']:root {
  --progress-color: linear-gradient(to right, #c2e59c, #64b3f4) --background-color: #fff;
  --text-color: #404040;
}

在页面初始化时通过 JS 获取系统的主题方案以修改 data-theme 的值,同时也可以响应用户的点击切换 light/dark 模式

<script>
  //切换主题
  const toggleTheme = (isDarkMode) => {
    document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light');
  };
  const theme = localStorage.getItem('theme');
  //获取系统的主题方案
  const themeMedia = window.matchMedia('(prefers-color-scheme: dark)');
  if (theme) {
    toggleTheme(theme === 'dark');
  } else {
    // 页面初始化切换
    toggleTheme(themeMedia.matches);
  }
  //监听系统主题切换
  themeMedia.addEventListener('change', (e) => {
      toggleTheme(e.matches);
  });
</script>
  • matchMedia() Window 的 matchMedia() 方法返回一个新的 MediaQueryList 对象,表示指定的媒体查询字符串解析后的结果。返回的 MediaQueryList 可被用于判定 Document 是否匹配媒体查询,或者监控一个 document 来判定它匹配了或者停止匹配了此媒体查询。
    当 document 满足此次媒体查询条件的时候,MediaQueryList 对象的 matches 属性将返回 true,否则返回 false,如果需要在系统切换主题色后,页面也能跟随立即切换,就需要监听 MediaQueryList 对象的 change 事件,它会在媒体查询发生变化时响应。

    window.matchMedia('(max-width: 600px)').addEventListener('change', (e) => {
      console.log('execute')
    })

现在可以通过监听用户点击事件交互切换 dark/light 模式。

const htmlEl = document.documentElement
const buttonEl = document.getElementById('btn')
buttonEl.addEventListener('click', () => {
  const currentTheme = htmlEl.getAttribute('data-theme')
  const nextTheme = currentTheme === 'dark' ? 'light' : 'dark'
  htmlEl.setAttribute('data-theme', nextTheme)
})
JS 操作与媒体查询

鉴于能用 CSS 解决的问题咱就不用 JS 原则,在站点初次加载时,可以优先使用媒体查询的方式获取系统的主题,设置页面的 light/dark 模式,在用户未主动交互前将一直使用这种方式,直到用户主动选择了页面的 light/dark 模式。
用户作出主题选择之后,将为 html 设置 data-theme 属性,此后页面的 light/dark 模式将根据该属性设置。

<script>
  //获取 localStorage 检查用户是否主动选择了 light/dark
  const theme = localStorage.getItem('theme');
  if (theme) {
    document.documentElement.setAttribute('data-theme', theme);
  }
</script>
/* theme-data 判断 */
html[data-theme='dark']:root {
  --progress-color: linear-gradient(to right, #c2e59c, #64b3f4);
  --background-color: #1f1f1f;
  --text-color: #ccc;
}
html[data-theme='light']:root {
  --progress-color: linear-gradient(to right, #c2e59c, #64b3f4);
  --background-color: #fff;
  --text-color: #404040;
}
/* 媒体查询判断 */
@media (prefers-color-scheme: dark) {
  :root {
    --progress-color: linear-gradient(to right, #c2e59c, #64b3f4);
    --background-color: #1f1f1f;
    --text-color: #ccc;
  }
}
@media (prefers-color-scheme: light) {
  :root {
    --progress-color: linear-gradient(to right, #c2e59c, #64b3f4);
    --background-color: #fff;
    --text-color: #404040;
  }
}
其他
  • theme-color 移动设备浏览器将根据所设定的建议颜色来改变用户界面。
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1f1f1f" />
  • color-scheme 当用户选择其中一种配色方案时,操作系统会对用户界面进行调整。这包括表单控件、滚动条和 CSS 系统颜色的使用值。 如果用户样式表里已经设定了相应的颜色,则会优先应用用户的样式表设置。
<meta name="color-scheme" content="dark light" />

参考链接:

一文搞懂前端多主题适配方案