首先介绍怎么跟随系统:

  • 比如win10可以设置浅色/深色颜色模式
  • window.matchMedia('(prefers-color-scheme:dark)').matches 的结果来得知当前系统的颜色模式(这是一个布尔值)
let dom = document.getElementById("mode");
//获取media信息对象
let media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
  //可用此方式加载不同的css样式文件,达到自动匹配系统样式功能
  dom.innerText = "监听到模式: 黑暗";
} else {
  dom.innerText = "监听到模式: 亮色";
}
  • 通过监听matchMedia的change事件可判断用户是否切换了暗黑/亮色模式
let media = window.matchMedia('(prefers-color-scheme:dark)');
//监听样式切换
let callback = e => {
  let prefersDarkMode = e.matches;
  if (prefersDarkMode) {
    console.log("黑暗模式");
    dom.innerText = "监听到模式切换: 黑暗";
  } else {
    console.log("亮色模式");
    dom.innerText = "监听到模式切换: 亮色";
  }
}
if (typeof media.addEventListener === 'function') {
  media.addEventListener('change', callback);
} else if (typeof media.addListener === 'function') {
  media.addListener(callback);
}

HTML 5 深色模式适配 (prefers-color-scheme)

具体的逻辑如下

/**
 * 两种颜色模式
 */
const colorModeOption = ["light", "dark"] as const;
/**
 *  颜色模式 在 localStorage里边的 存储键名
 */
const storageKey = "color_mode";

/**
 *
 * 手动切换模式:保存 color_mode 到cache后还能获取cache里的color_mode,并对应修改app的页面颜色,
 * 根据系统模式:能根据系统颜色模式来对应修改app的页面颜色,
 *
 * this Class's static methods can help you change your app's color theme/color mode based on
 * the variable saved in the localStorage or os preference.
 */
export default class ChangeAppColorMode {
  /**
   * 我这里用了 tailwind css 的prefix ,我的前缀是 tw,你的前缀不一样,就要该这个变量
   *
   * per as my own tailwind config,
   * i use 'tw-' as the prefix before every default tailwind utility class name.
   * tweak this when your prefix is different。
   *
   */
  private static readonly darkModeClassName = "tw-dark";
  /**
   *
   * @returns  获取local storage(缓存)里的颜色模式,如果缓存里没有颜色模式,返回undefined,说明在使用跟随系统
   */
  public static getMode() {
    const mode = localStorage.getItem(storageKey) as
      | typeof colorModeOption[number]
      | null;
    // 判空
    if (mode === null) {
      return undefined;
    }
    // 判符合要求
    if (colorModeOption.includes(mode)) {
      return mode;
    }
    // 对异常状态,返回undefined
    return undefined;
  }

  /**
   *
   * @param mode 设置缓存里的颜色模式
   */
  public static setMode(mode: "light" | "dark") {
    localStorage.setItem(storageKey, mode);
  }
  /**
   * 清除缓存里的颜色模式
   */
  public static removeMode() {
    localStorage.removeItem(storageKey);
  }
  /**
   * when prefer os is selected,use this fn
   */
  public static preferOsColorMode() {
    ChangeAppColorMode.removeMode();
    ChangeAppColorMode.setColorModeByOsConfig();
  }
  /**
   *
   *
   * change app's page color,manual mode
   *
   * 切换网页的颜色模式 手动模式
   * @returns void
   */
  public static changeColorMode() {
    // 获取当前 mode
    const mode = ChangeAppColorMode.getMode();
    //previously was using prefer os  mode
    if (mode === undefined) {
      // 手动互斥
      // manual exclusion
      let currentIsDark =
        document.documentElement.classList.contains("tw-dark");
      if (currentIsDark) {
        ChangeAppColorMode.setMode("light");
        ChangeAppColorMode.removeDarkModeClassName();
        return;
      } else {
        ChangeAppColorMode.setMode("dark");
        ChangeAppColorMode.addDarkModeClassName();
        return;
      }
    }

    //  extra things ,can be deleted。。。
    if (mode === "light") {
      ChangeAppColorMode.setMode("dark");
      ChangeAppColorMode.addDarkModeClassName();
    } else {
      ChangeAppColorMode.setMode("light");
      ChangeAppColorMode.removeDarkModeClassName();
    }
  }

  /**
   * initial app's page color theme according to cache or os preference
   *
   * 根据localStorage的缓存或者是以跟随系统的形式来初始化页面的背景颜色
   * @returns void
   */
  public static initialColorMode = () => {
    // 获取当前 mode
    const mode = ChangeAppColorMode.getMode();
    // supervise possible os color mode change
    ChangeAppColorMode.removeAndAddOsColorModeChangeEventListener();
    // mode 为空 ,则跟随系统
    if (mode === undefined) {
      ChangeAppColorMode.setColorModeByOsConfig();
      return;
    }

    if (mode === "light") {
      ChangeAppColorMode.removeDarkModeClassName();
      return;
    } else {
      ChangeAppColorMode.addDarkModeClassName();
    }
  };
  // the following fns are helpers....... that mustn't be used out of the ColorModeStorage Class
  // 下方是 私有的helper 方法
  private static setColorModeByOsConfig() {
    const osIsDarkMode = window.matchMedia(
      "(prefers-color-scheme:dark)"
    ).matches;

    if (osIsDarkMode) {
      ChangeAppColorMode.addDarkModeClassName();
      return;
    } else {
      ChangeAppColorMode.removeDarkModeClassName();
      return;
    }
  }
  /**
   *
   * when os's color mode changes, check if app's manual color change mode is enabled,
   * if not enabled ,change app's color mode using the os's colorMode
   *
   * 当操作系统的颜色模式切换的时候(会触发一个事件,这是对应的事件处理函数),检查一下app的手动颜色调整模式是不是开启的(有手动切换模式和跟随系统模式),
   * 如果手动颜色调整模式没开启,那就认为处于跟随系统模式,根据对应的媒体查询结果(系统颜色模式)来切换app的颜色
   */
  private static osPreferColorChangeHandler(e: MediaQueryListEvent) {
    let prefersDarkMode = e.matches;
    let mode = ChangeAppColorMode.getMode();
    if (mode === undefined) {
      if (prefersDarkMode) {
        ChangeAppColorMode.addDarkModeClassName();
      } else {
        ChangeAppColorMode.removeDarkModeClassName();
      }
    }
  }
  /**
   * when operating system's color mode change ,this fn will catch
   * the change event and attach necessary event handler
   * (and because i used addEventListener, i have to remove possible redundant handler)
   *
   * 运行下方的函数之后,当操作系统的颜色模式变化的时候会触发一个事件,这个事件会被监听到,
   * 而且相对应的处理函数:osPreferColorChangeHandler 会被添加处理这个事件的处理函数
   */
  private static removeAndAddOsColorModeChangeEventListener() {
    let media = window.matchMedia("(prefers-color-scheme:dark)");

    media.removeEventListener(
      "change",
      ChangeAppColorMode.osPreferColorChangeHandler
    );
    media.addEventListener(
      "change",
      ChangeAppColorMode.osPreferColorChangeHandler
    );
  }
  private static addDarkModeClassName(
    darkModeClassName: string = ChangeAppColorMode.darkModeClassName
  ) {
    document.documentElement.classList.add(darkModeClassName);
    return;
  }
  private static removeDarkModeClassName(
    darkModeClassName: string = ChangeAppColorMode.darkModeClassName
  ) {
    document.documentElement.classList.remove(darkModeClassName);
    return;
  }
}

// example;
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
// if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
//   document.documentElement.classList.add('dark')
// } else {
//   document.documentElement.classList.remove('dark')
// }

// // Whenever the user explicitly chooses light mode
// localStorage.theme = 'light'

// // Whenever the user explicitly chooses dark mode
// localStorage.theme = 'dark'

// // Whenever the user explicitly chooses to respect the OS preference
// localStorage.removeItem('theme')