我是怎么让网页跟随系统主题变化的

自从去年Apple发布的macOS Mojave支持深色模式以来,包括Win10、Android、iOS、QQ、微信在内的各大系统、各大软件都陆续推出了深色模式。

除了在各种系统原生应用上实现深色模式,网页的深色模式也值得我们关注。想要在网页上实现深色模式,其中有两个技术要点:一是如何实现主题的切换,二是如何自动触发这个切换。

接下来我将以图可视化工具这个网页做为例子,记录我为其提供深色模式的思路。

网页如何切换主题

但凡有一点Web开发基础,就肯定会听说过CSS (Cascading Style Sheets 层叠样式表)。所谓切换主题其实就是改变网站的样式,而实现深色模式就是编写深浅两套样式,在浅色模式应用浅色样式,在深色模式应用深色样式。

一开始我们先擅自为<html>定义一个theme="dark"属性,以表示当前整个网页切换到了深色模式,而如果没有这个theme属性则表示我们当前为浅色模式,这样我们就可以用js脚本简单地修改属性值而切换深浅模式了。

:root {
    --main-bg-color: rgb(255, 255, 255);    /* 浅色背景 */
    --main-text-color: rgb(54, 54, 54);     /* 深色文字 */
}

:root[theme="dark"] {
    --main-bg-color: rgb(47, 58, 68);       /* 深色背景 */
    --main-text-color: rgb(197, 197, 197);  /* 浅色文字 */
}

body { /* 使用定义好的变量 */
    background-color: var(--main-bg-color);
    color: var(--main-text-color);
}

这部分的核心不过就是编写两套主题。而在主题的切换中,主要变化的是网页各部分的颜色,实现中还使用了css变量这个特性,会显得比较方便。

然后就可以在网页上添加一个“深色主题切换按钮”了 ,先在HTML中写出这个按钮<button onclick="switchTheme()">switch theme</button>,然后定义这个函数。

function switchTheme() {
    let de = document.documentElement;
    let isdark = de.getAttribute('theme') == 'dark';
    de.setAttribute('theme', isdark ? 'light' : 'dark');
}

很好,现在我们得到了一个按钮,通过按这个按钮可以切换网站的主题。

如何让主题自动切换

但这不是最棒的,不是吗?我晚上打开网页的时候为什么要在一片亮光中找一个小小的切换按钮?我们想要的是自动的切换

可是燕雀安知鸿鹄之志哉,网页怎么知道系统的主题呢?

还好,率先发布macOS深色模式的Apple公司提出了一个叫prefers-color-scheme的5级CSS媒体查询,可以被用来检测用户的系统主题是浅色或深色。它有三种可能的值:

  • no-preference
    表示用户未指定操作系统主题。其作为 布尔值 时以false输出。
  • light
    表示用户的操作系统是浅色主题。
  • dark
    表示用户的操作系统是深色主题。

真好,现在我们就可以用这个媒体查询来让网页自动跟随系统主题了。

@media (prefers-color-scheme: dark) {
    :root {
        --main-bg-color: rgb(47, 58, 68);       /* 深色背景 */
        --main-text-color: rgb(197, 197, 197);  /* 浅色文字 */
    }
}

等等!有办法保留我们前面的theme="dark"吗?有时候我还是想在白天切换到深色模式!

第一次尝试

:root {
    --main-bg-color: rgb(255, 255, 255);        /* 浅色背景 */
    --main-text-color: rgb(54, 54, 54);         /* 深色文字 */
}

:root[theme="dark"] {
    --main-bg-color: rgb(47, 58, 68);           /* 深色背景 */
    --main-text-color: rgb(197, 197, 197);      /* 浅色文字 */
}

@media (prefers-color-scheme: dark) {
    :root {
        --main-bg-color: rgb(47, 58, 68);       /* 深色背景 */
        --main-text-color: rgb(197, 197, 197);  /* 浅色文字 */
    }
}

emmmmm, it works. 但是为什么深色的样式要写两次啊!!!我要把这两份合并!

怎么才能让“theme="dark"@media (prefers-color-scheme: dark)”成立时启用深色模式呢?正当我为这个“或”关系要如何表示一筹莫展的时候,你猜我想到了什么!

德·摩根定律:¬(P⋀Q) = (¬P) ⋁ (¬Q)

那就可以设P=theme="dark",Q=@media (prefers-color-scheme: dark)。P⋀Q就可以写为¬((¬P) ⋁ (¬Q)),哇咔咔∠( ᐛ 」∠)_

看结果

:root {
    --main-bg-color: rgb(47, 58, 68);           /* 深色背景 */
    --main-text-color: rgb(197, 197, 197);      /* 浅色文字 */
}

@media not all and (prefers-color-scheme: dark) {
    :root:not([theme="dark"]) {
        --main-bg-color: rgb(255, 255, 255);    /* 浅色背景 */
        --main-text-color: rgb(54, 54, 54);     /* 深色文字 */
    }
}

如此,js可以根据时间设置theme属性值,在浏览器不支持prefers-color-scheme的情况下自动切换到深色。

if (window.matchMedia('prefers-color-scheme').media == 'not all') {
    let hour = new Date().getHours();
    if (hour < 6 || hour > 19) // 晚上7点到上午6点之间,设置为深色主题
        document.documentElement.setAttribute('theme', 'dark');
} // 这段代码我可没测试过,如果它不work欢迎联系我