JavaScript 根据用户的偏好设置网页的主题(亮色或暗色)

108 min read
---
import { pwaInfo } from 'virtual:pwa-info'

export interface Props {
  title: string;
}

const { title } = Astro.props;
---

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover">
    <link rel="icon" type="image/svg+xml" href="/icon.svg">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
    <link rel="mask-icon" href="/icon.svg" color="#FFFFFF">
    <meta name="theme-color" content="#212129">
    <meta name="generator" content={Astro.generator}>
    <title>{title}</title>
    <meta name="description" content="A Minimal web UI for GeminiPro Chat.">
    { import.meta.env.HEAD_SCRIPTS && <Fragment set:html={import.meta.env.HEAD_SCRIPTS } /> }
    { pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} /> }
    { import.meta.env.PROD && pwaInfo && <Fragment set:html={pwaInfo.registerSW.scriptTag} /> }
  </head>
  <body>
    <slot />
  </body>
</html>

<style is:global>
  :root {
    --c-bg: #fbfbfb;
    --c-fg: #444444;
    --c-scroll: #d9d9d9;
    --c-scroll-hover: #bbbbbb;
    scrollbar-color: var(--c-scrollbar) var(--c-bg);
  }

  html {
    font-family: system-ui, sans-serif;
    background-color: var(--c-bg);
    color: var(--c-fg);
  }

  html.dark {
    --c-bg: #212129;
    --c-fg: #ddddf0;
    --c-scroll: #333333;
    --c-scroll-hover: #555555;
  }

  main {
    max-width: 70ch;
    margin: 0 auto;
    padding: 6rem 2rem 4rem;
  }

  ::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  }
  ::-webkit-scrollbar-thumb {
    background-color: var(--c-scroll);
    border-radius: 4px;
  }
  ::-webkit-scrollbar-thumb:hover {
    background-color: var(--c-scroll-hover);
  }
  ::-webkit-scrollbar-track {
    background-color: var(--c-bg);
  }
</style>

<script>
const initTheme = () => {
  const darkSchema
    = window.matchMedia
    && window.matchMedia('(prefers-color-scheme: dark)').matches
  const storageTheme = localStorage.getItem('theme')
  if (storageTheme) {
    document.documentElement.classList.toggle(
      'dark',
      storageTheme === 'dark',
    )
  } else {
    document.documentElement.classList.toggle('dark', darkSchema)
  }
}

initTheme()
</script>

这段代码是一个 HTML 页面的结构,其中包含了一些用于构建网页的标准元素和一段 JavaScript 脚本。这个脚本是用来根据用户的偏好设置网页的主题(亮色或暗色)。我将解释这段脚本的工作原理,特别是 toggle 函数的作用。

JavaScript 脚本分析

脚本定义了一个 initTheme 函数,它负责设置页面的主题。

  1. 媒体查询检测:

    const darkSchema = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    

    这行代码检查用户系统是否设置了偏好暗色主题。window.matchMedia 是一个 JavaScript 函数,用于检查媒体查询的结果。如果用户系统设置为暗色主题,darkSchema 会被设置为 true

  2. 本地存储读取:

    const storageTheme = localStorage.getItem('theme');
    

    这行代码尝试从本地存储中读取主题设置。如果用户之前在网站上设置过主题(并存储在 localStorage 中),这里会获取到那个值。

  3. 主题应用 - toggle 函数的使用:

    document.documentElement.classList.toggle('dark', darkSchema);
    

    这是关键部分。classList.toggle 函数用于在元素上添加或移除一个类。这里它用于根据 darkSchema 的值来添加或移除 dark 类。

    • 如果 darkSchematrue,则 dark 类将被添加到 documentElement(即 <html> 标签),应用暗色主题样式。
    • 如果 darkSchemafalse,则 dark 类将被移除,使用亮色主题样式。

页面主题的动态应用

整个脚本的目的是根据用户的系统设置或之前的选择动态应用暗色或亮色主题。如果用户的系统偏好设置为暗色模式或者用户之前在网站上选择了暗色模式,页面将自动应用暗色主题样式。这种方式提升了用户体验,使网站能够更好地适应用户的偏好。

在这段代码中,toggle 方法用于根据条件动态添加或移除 dark 类。这是一个常见的用法,特别是在处理响应用户偏好设置的主题切换时。

classList.toggle 方法

classList 是一个 DOM 元素的属性,提供了对元素类名的访问和操作。toggle 方法是 classList 的一部分,用于切换元素的类名。

toggle 方法的语法

element.classList.toggle(className, condition);
  • className:要切换的类名。
  • condition:一个布尔值。当 true 时,添加类名;当 false 时,移除类名。

使用案例

document.documentElement.classList.toggle('dark', storageTheme === 'dark');
  • 目标元素: document.documentElement 指向文档的根元素,通常是 <html> 元素。

  • 类名: 'dark' 是要切换的类名。

  • 条件: storageTheme === 'dark' 是一个条件表达式。如果 storageTheme 的值等于 'dark'',表达式结果为 true,否则为 false`。

功能解释

  • 如果 storageTheme 等于 'dark'(意味着用户之前选择了暗色主题),toggle 方法将会确保 dark 类被添加到 <html> 元素上。

  • 如果 storageTheme 不等于 'dark'toggle 方法将会移除 <html> 元素上的 dark 类(如果它存在的话)。

这种方式允许网页根据用户之前的选择或偏好动态地应用或取消暗色主题。这是一种有效的方法,尤其在需要根据用户偏好改变网站外观的场合。