随着深色模式成为各类APP和网站的常见功能,越来越多的设计方案涌现。尽管市面上已有许多深色模式的实现方案,但我个人的设计理念与这些方案有所不同。
前段时间根据manus分析浅色系的配色,给Weisay Grace主题重新生成的深色模式的配色,不再是原来不同透明度的黑色,而是有了深蓝色元素,深浅色更加统一协调了。
常见的三种实现方案
基础切换款
这是一种简单的模式切换,仅包含浅色与深色的切换。当用户选择深色模式后,系统环境的模式不再影响网站,网站始终保持用户的选择。
三按钮切换款
在基础款的基础上,增加了一个“自动”选项,允许网站根据系统环境自动调整模式。如果用户手动选择了固定的颜色,系统环境变化时,网站主题则不再自动变化。
时间切换款
根据时间段自动切换深色模式。例如,在晚上自动切换为深色模式,白天则恢复浅色模式,用户也可以手动进行切换。
各种方案的优缺点
基础款:功能简单,但完全忽略了系统环境的变化。
三按钮切换款:增加了自动模式,但用户需要点击两次才能完成切换,稍显繁琐。
时间切换款:过于“霸道”,直接根据时间切换模式,可能不符合用户的个性化需求。
我的设计思路
我偏向一种单按钮切换的方式,具体如下:
首次访问时,网站默认跟随系统环境设置(深色或浅色);
用户手动切换后,网站会记住用户的选择,不再自动跟随系统的主题;
当系统主题色再次变化时,网站主题色系模式仍可同步更新,确保体验一致。
场景举例
- 初始状态
- 用户手动切换网站
- 修改系统为深色
- 再次修改系统为浅色
系统:浅色
网站:默认跟随系统 → 浅色
网站被手动切换为 → 深色
此时网站忽略系统主题,记住用户选择。
系统:深色
网站:因用户已手动选择深色 → 保持深色(不随系统改变)
系统:浅色
网站:恢复为浅色(因用户未手动切换,重新跟随系统)
这种设计方式通过用户的手动选择临时“覆盖”系统设置,同时在系统主题变化时能重新同步,确保用户体验的一致性。这种方案既能兼顾系统偏好,又能提供个性化的操作体验。
当然,这种方式的缺点在于,如果用户希望网站颜色一直保持固定,而不受系统环境变化的影响,这种设计就不太适用了。
先看演示
实现代码
在看下面的代码前,可以先参考 浅谈网页「深色模式」的实现 这篇文章,我想要的深色模式切换功能是在这个的基础上面修改来的。
通过使用了向 html标签 添加 dark/light 类,并通过点击切换按钮来实现深色模式切换的。
JavaScript
const rootElement = document.documentElement;
const darkModeClassName = "dark";
const darkModeStorageKey = "user-color-scheme";
const validColorModeKeys = { dark: true, light: true };
const invertDarkModeObj = { dark: "light", light: "dark" };
const setLocalStorage = (key, value) => {
try {
localStorage.setItem(key, value);
} catch (e) {}
};
const removeLocalStorage = (key) => {
try {
localStorage.removeItem(key);
} catch (e) {}
};
const getLocalStorage = (key) => {
try {
return localStorage.getItem(key);
} catch (e) {
return null;
}
};
const getModeFromCSSMediaQuery = () => {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
};
const setColorScheme = (mode) => {
rootElement.classList.remove(mode, invertDarkModeObj[mode]);
rootElement.classList.add(mode);
};
const resetRootDarkModeClassAndLocalStorage = () => {
rootElement.classList.remove(darkModeClassName, invertDarkModeObj[darkModeClassName]);
removeLocalStorage(darkModeStorageKey);
};
const applyCustomDarkModeSettings = (mode) => {
// 接受从「开关」处传来的模式,或者从 localStorage 读取
const currentSetting = mode || getLocalStorage(darkModeStorageKey);
if (currentSetting === getModeFromCSSMediaQuery()) {
// 当用户自定义的显示模式和 prefers-color-scheme 相同时重置、恢复到自动模式
resetRootDarkModeClassAndLocalStorage();
setColorScheme(currentSetting);
} else if (validColorModeKeys[currentSetting]) {
rootElement.classList.add(currentSetting);
rootElement.classList.remove(invertDarkModeObj[currentSetting]);
} else {
// 首次访问或从未使用过开关、localStorage 中没有存储的值,currentSetting 是 null
// 或者 localStorage 被篡改,currentSetting 不是合法值
resetRootDarkModeClassAndLocalStorage();
// 使用系统当前方案
setColorScheme(getModeFromCSSMediaQuery());
}
};
const toggleCustomDarkMode = () => {
let currentSetting = getLocalStorage(darkModeStorageKey);
if (validColorModeKeys[currentSetting]) {
// 从 localStorage 中读取模式,并取相反的模式
currentSetting = invertDarkModeObj[currentSetting];
} else if (currentSetting === null) {
// localStorage 中没有相关值,或者 localStorage 抛了 Error
// 从 CSS 中读取当前 prefers-color-scheme 并取相反的模式
currentSetting = invertDarkModeObj[getModeFromCSSMediaQuery()];
} else {
// 不知道出了什么幺蛾子,比如 localStorage 被篡改成非法值
return; // 直接 return;
}
// 将相反的模式写入 localStorage
setLocalStorage(darkModeStorageKey, currentSetting);
return currentSetting;
};
// 当页面加载时,将显示模式设置为 localStorage 中自定义的值(如果有的话)
applyCustomDarkModeSettings();
const onSystemSchemeChanged = (event) => {
// 获取新的系统主题方案
const newColorScheme = event.matches ? "dark" : "light";
// 用户主动配置了系统方案,清除用户之前记忆
resetRootDarkModeClassAndLocalStorage();
// 使用系统当前方案
setColorScheme(newColorScheme);
};
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
// recommended method for newer browsers: specify event-type as first argument
darkModePreference.addEventListener("change", onSystemSchemeChanged);
// deprecated method for backward compatibility
darkModePreference.addListener(onSystemSchemeChanged);
CSS
:root {
--text: #111;
--background: #eee;
}
.dark {
--text: #ccc;
--background: #111;
}
body {
color: var(--text);
background: var(--background);
}
其实用 :root
是一个很好的方式,通过定义全局 CSS 变量,实现样式统一管理。修改变量值即可全局生效,其实非常适合主题切换或响应式设计的,但是我就是用不习惯。
因为js会在页面的html标签里面动态加上一个 light
或 dark
的类,那么深色模式也可以用 .dark
开头来定义。
深色模式下的页面滚动条
只需在页面的
中添加下面的标签即可让页面滚动条的样式跟随深色模式变化。
<meta name="color-scheme" content="light dark" />
当然还要添加下面的css
:root {
color-scheme: light;
}
.dark {
color-scheme: dark;
}
我的博客实现方式差不多,也是单按钮,在不同的情况以不同的处理方式来切换 auto dark light