给博客适配全局深色模式

给博客适配全局深色模式

请注意,本文编写于  171  天前,最后修改于  155  天前,其中某些信息可能已经过时。

写在前面

最近给博客美化的想法比较多,很多 idea 也是突然一下子就冒了出来,想着不如多写点博客记录一下。当前主流的操作系统不管是 Windows10、MacOS,还是我们手机端使用的 IOS13 和 Android10,都已经完全支持深色模式了。给博客适配深色模式,已经成为当前大多数主题开发者的基本要求。

什么是深色模式

深色模式(Dark Mode)又被称为『暗黑模式』,顾名思义,它给人的最直观感受就是「黑」,虽然它本意不是纯粹的夜间模式,但是我们往往将它当作夜间模式使用。实际中,『夜间模式』是用于弱光环境,主要目的是保护眼睛,减少强光刺激,而『深色模式』可以看作是一个黑色主题,在大白天也可以正常使用。

在实际用户体验报告中,深色模式也绝不是简单的将原来的「白底黑字」变成「黑底白字」,Google 在 Material Design 的设计指导中对于深色模式中列出的设计规范中,第一条就是「不要使用 100% 的纯黑」。

为什么呢?因为纯白色会反射所有波长的光线,而纯黑色会吸收所有光线,这是对比度最大的两种颜色,白底黑字时,文字过于刺眼,而黑底白字时,文字又可能难以辨认。

深色模式功能实现思路和方法

在谈如何选择深色模式的颜色前,需要先了解一下如何在博客中实现深色模式切换功能。由于我对 CSS 研究比较浅显,我这里只给出我知道的两个解决思路。

  1. 使用 CSS 媒体查询
  2. 使用全局变量开关

以下具体展开来说。

使用 CSS 媒体查询

CSS 的媒体查询在深色模式得到广泛使用后,也推出了一个名为 prefers-color-scheme 的媒体特征,它可以判断当前系统的主题从而改变当前网站的主题(也就是亮色模式和深色模式)。当前这个媒体特征在各大浏览器的兼容性如下:

原则上只要系统支持深色模式,这个特性就可以正常使用,不过缺点就是只能跟随系统的主题变化而变化。我们可以使用 CSS Variable 的方式举例:

//正常情况下(亮色主题)的文字颜色
:root {
  --color-text: #000;
}

//媒体查询到系统为深色模式,将自动切换为 dark 下的 css
@media (prefers-color-scheme: dark) {
  :root {
    --color-text: #fff;
  }
}

//使用 Variable 机制获取文字颜色的实际值
body {
  color: var(--color-text);
}

在官方文档中,prefers-color-scheme 给出了三个选择的值:

  • no-preference:表示系统未得知用户在这方面的选项。在布尔值上下文中,其执行结果为 false。
  • light:表示用户已告知系统他们选择使用浅色主题的界面。
  • dark:表示用户已告知系统他们选择使用暗色主题的界面。

如果你是 MacOS 用户,又正巧使用的是支持该特征的浏览器的话,可以通过切换系统的外观色调整博客的主题颜色。

总而言之,prefers-color-scheme 只是帮助博客做到随系统自动变化而已,网站的深浅配色系统本身还是要开发者预先设计好。而且不支持单独的深色模式开关,所以这种方式局限性较大,因为大多数人在使用浏览器时,不会主动去切换浏览器的主题模式。

使用全局变量开关

标题可能表述的意思不准确,实际上这个全局变量的意思就是在全局 HTML 的 body 上增加一个 classnight 的属性表示开启了深色模式,通过这个全局属性值重新写一份深色模式需要用到的 CSS 就可以实现点击深色模式开关切换了。

开启了深色模式的 HTML 页面:

<html>
      <head>
	  <meta charset="utf-8">
	  <title>Sanarous's Blog</title>
          <style type="text/css">
               //亮色模式下的字体颜色
              .post-content{
                  color: #222;
               }
               
              //对应的深色模式下的字体颜色
              .night .post-content{
                  color: #fff
              } 
          </style>
      </head>
      <body class="night">
         <button id="nightModeBtn">深色模式开关按钮</button>
         <div class="post-content">
             这里是博客文章内容
         </div>

      //引入jquery
      <script type="text/javascript">
          //保存一个所有页面共享的全局变量
          //可放在浏览器的 cookie 中,1-深色模式  0-亮色模式
          var darkModeFlag = 0;

          //按钮点击事件,切换模式开关
          $("#nightModeBtn").click(function(){
              if(darkModeFlag == 0){
                   //给body加属性
                   $("body").addClass("night");
              }else{
                   //移除属性
                  $("body").removeClass("night");
              }
          });          
      </script>
      </body>
</html>

以上只是一个简单的实现思路,实际中要比这个复杂一些,比如需要把深色模式的标志放入浏览器的 Cookie 中,以及可以实现定时开启深色模式,甚至通过读取地理位置判断当地日出日落时间从而使得深色模式跟随开启。我使用的 HanShan 主题同样是使用了这个思路实现的深色模式。

深色模式颜色的选择

有了实现思路后,现在又有一个世纪难题,如何选择合理的颜色?

  • 深色颜色需要兼顾白天和晚上的使用场景
  • 上面已经提到过,深色模式并不是一劳永逸的设置绝对黑底白字,两者的对比度是最高的(21:1),这容易产生阅读疲劳,不利于长时间阅读。

所以我们不妨看一下现有的应用中,深色模式是怎么选取颜色的,然后再根据自己的喜好和实际情况决定使用哪种方案。

背景色

背景色往往不是真正的黑色,除去一些特殊情况,比如 IOS 的系统深色模式界面就是采用的 #000000 ,这样做的好处就是省电,我们还是不妨看一下使用人数较多的应用或网站是怎么选择的:

  1. Google Material Design 在深色主题开发中建议使用的是 #121212,说是深灰色相比纯黑可以更好表达
  2. 微信在深色模式下的聊天背景用的是 #181818
  3. Twitter 的黑是带有深蓝色的 #15202b
  4. VSCode 的默认暗色背景的颜色是 #1E1E1E
  5. JetBrain 系 IDE 比如 IDEA 和 PyCharm 的默认暗色背景的颜色是 #282828,也是我经常用到的背景色
  6. .......

我的博客采用的略带灰色的 #292a2d,这个就是纯粹根据自己的喜好来决定。

文字颜色

文字颜色主要是亮度选择不要太高,稍微暗一些就比较合适。

Material Design 中已经给出了文字颜色建议:

When light text appears on dark backgrounds it should use the following opacity levels:
  • High-emphasis text has an opacity of 87%
  • Medium-emphasis text and hint text have opacities of 60%
  • Disabled text has an opacity of 38%

也可以总结如下:

  • 正文颜色使用 hsla(0,0%,100%,.87)
  • 标签、时间等辅助元素使用 hsla(0,0%,100%,.6)

对比度

选取好颜色后接下来就是仔细检查网站上各个元素的颜色是否符合 WCAG 2.0 标准。该标准主要是针对有视觉障碍用户,保证你的内容对他们来说是可读的。主要需要满足以下两点:

  • AA 级:文字与背景的对比度至少为 4.5:1,大号文字为 3:1。
  • AAA 级:文字与背景的对比度至少为 7:1,大号文字为 4.5:1。

可以用 Chrome Devtools 逐一查看各个元素的对比度:

如果有不符合标准的颜色,可以通过 这个网站 选取合适对比度的颜色。

层级表现

深色模式与亮色模式不同,层级表现无法通过阴影效果 box-shadow 表现,那么一般都通过调整 background-color 背景色来实现层级显示。

图片之类的处理

这个直接使用 CSS 效果的 filter 属性实现,比如在深色模式下把图片亮度调到 70%:

.night img {
    -webkit-filter: brightness(.7)!important;
    filter: brightness(.7)!important;
}

如何自己适配深色模式

以下需要具备一定的前端知识,最好掌握了常用的 CSS 和 jQuery 再试一试哦

以我的博客深色模式开关为例,首先需要定制一个开关按钮,我使用的是 FontAwesome 图标作为按钮,class 中的属性值分别为 fa-lightbulb-ofa-moon-o,代表「开灯」和「关灯」样式。如下所示:

//在深色模式下显示开灯的按钮
<i class="fa fa-lightbulb-o" id="nightMode" aria-hidden="true"></i>

//在亮色模式下显示关灯的按钮
<i class="fa fa-moon-o" id="nightMode" aria-hidden="true"></i>

注意上面是同一个按钮在点击下的不同显示,我们需要操作的逻辑是在亮色模式下,点击「关灯」按钮后按钮的样式就变成了「开灯」,反之同理,这个逻辑在 js 或者 jQuery 中完成即可,以上的按钮样式都可以自己去定义。

实现过程

以下的内容参考自 cungudafa 的博客,实现的步骤如下:

  1. 点击触发切换主题模式,先给一个切换动画,具体的会在 body 容器内增加一个 Cuteen_DarkSky 的容器来包装 CSS 动画(动画内容是日出日落切换)
  2. 再给 body 容器添加一个 night 的 class 属性,用于 css 样式切换
  3. 用标志位记录当前的状态,并记录到 Cookie 中,以保证在切换页面时,标志位状态不变(1 表示开启了深色模式,0 表示关闭了深色模式)
  4. 可以设置一个自动定时切换深色模式的功能,比如在 20 点到 6 点自动开启深色模式。

根据上面的步骤,我们给出具体的实现方式。

JS 的实现部分(需要引入 jQuery)

我们以上面使用 Fontawesome 图标为例,HTML 初始样式为:

<a onclick="switchNightMode()">
  <i class="fa fa-moon-o" id="nightMode" aria-hidden="true"></i>
</a>

点击上述图标后,进入 js 的实现逻辑:

//点击按钮后切换模式
function switchNightMode() {
        $('<div class="Cuteen_DarkSky"><div class="Cuteen_DarkPlanet"></div></div>').appendTo($("body")), setTimeout(
            function () {
                var DarkMode = document.cookie.replace(/(?:(?:^|.*;\s*)DarkMode\s*\=\s*([^;]*).*$)|^.*$/, "$1") ||
                    '0';
                (DarkMode == '0') ? ($("body").addClass("night"), document.cookie = "DarkMode=1;path=/", $('#nightMode').removeClass("fa-moon-o").addClass("fa-lightbulb-o")) : ($("body").removeClass(
                        "night"), document.cookie = "DarkMode=0;path=/", $('#nightMode').removeClass("fa-lightbulb-o").addClass("fa-moon-o")), setTimeout(function () {
                    $(".Cuteen_DarkSky").fadeOut(1e3, function () {
                        $(this).remove()
                    })
                }, 2e3)
            }), 50
    }
    
    //检查当前主题模式和图标是否对应
    function checkNightMode() {
        if ($("body").hasClass("night")) {
            $('#nightMode').removeClass("fa-moon-o").addClass("fa-lightbulb-o");
            return;
        }else{
            $('#nightMode').removeClass("fa-lightbulb-o").addClass("fa-moon-o");
            return;
        }
        
        //如果没有切换过,从 cookie 中获取标志位判断
        if (document.cookie.replace(/(?:(?:^|.*;\s*)DarkMode\s*\=\s*([^;]*).*$)|^.*$/, "$1") === '') {
            if (new Date().getHours() >= 20 || new Date().getHours() < 6) {
                $("body").addClass("night");
                document.cookie = "DarkMode=1;path=/";
                console.log('夜间模式开启');
                $('#nightMode').removeClass("fa-moon-o").addClass("fa-lightbulb-o");
            } else {
                $("body").removeClass("night");
                document.cookie = "DarkMode=0;path=/";
                console.log('夜间模式关闭');
                $('#nightMode').removeClass("fa-lightbulb-o").addClass("fa-moon-o");
            }
        } else {
            var DarkMode = document.cookie.replace(/(?:(?:^|.*;\s*)DarkMode\s*\=\s*([^;]*).*$)|^.*$/, "$1") || '0';
            if (DarkMode == '0') {
                $("body").removeClass("night");
                 $('#nightMode').removeClass("fa-lightbulb-o").addClass("fa-moon-o");
            } else if (DarkMode == '1') {
                $("body").addClass("night");
                $('#nightMode').removeClass("fa-moon-o").addClass("fa-lightbulb-o");
            }
        }
    }
    checkNightMode();

CSS 的实现部分

以下 CSS 可以放到任意全局自定义配置 CSS 文件中。

<style type="text/css">
    /* 深色模式的颜色设置,需要自己手动逐个添加并适配 */
    .night #page,
    .night #colophon,
    .night #vcomments .vbtn,
    .night .art-content #archives .al_mon_list .al_mon,
    .night .art-content #archives .al_mon_list span,
    .night body,
    .night .art-content #archives .al_mon_list .al_mon,
    .night .art-content #archives .al_mon_list span,
    .night button,
    .night .art .art-content #archives a,
    .night textarea,
    .night strong,
    .night a,
    .night p,
    .night .label {
        color: rgba(255, 255, 255, .6);
    }

    .night #page,
    .night body,
    .night #colophon,
    .night #main-container,
    .night #page .yya,
    .night #content,
    .night #contentss,
    .night #footer {
        background-color: #12121c;
    }
    .night strong,
    .night img {
        filter: brightness(.7);
    }

    /* CSS 切换的日出日落样式,不需要修改,如果不需要动画可以手动删除这一部分 */
    .Cuteen_DarkSky,
    .Cuteen_DarkSky:before {
        content: "";
        position: fixed;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        z-index: 88888888
    }

    .Cuteen_DarkSky {
        background: linear-gradient(#feb8b0, #fef9db)
    }

    .Cuteen_DarkSky:before {
        transition: 2s ease all;
        opacity: 0;
        background: linear-gradient(#4c3f6d, #6c62bb, #93b1ed)
    }

    .DarkMode .Cuteen_DarkSky:before {
        opacity: 1
    }

    .Cuteen_DarkPlanet {
        z-index: 99999999;
        position: fixed;
        left: -50%;
        top: -50%;
        width: 200%;
        height: 200%;
        -webkit-animation: CuteenPlanetMove 2s cubic-bezier(.7, 0, 0, 1);
        animation: CuteenPlanetMove 2s cubic-bezier(.7, 0, 0, 1);
        transform-origin: center bottom
    }

    @-webkit-keyframes CuteenPlanetMove {
        0% {
            transform: rotate(0)
        }

        to {
            transform: rotate(360deg)
        }
    }

    @keyframes CuteenPlanetMove {
        0% {
            transform: rotate(0)
        }

        to {
            transform: rotate(360deg)
        }
    }

    .Cuteen_DarkPlanet:after {
        position: absolute;
        left: 35%;
        top: 40%;
        width: 9.375rem;
        height: 9.375rem;
        border-radius: 50%;
        content: "";
        background: linear-gradient(#fefefe, #fffbe8)
    }
    /* sun and noon.end */
</style>

总结

深色模式的实现并不难,甚至可以根据自己的想法添加很多新奇的功能。但是在选择合适的颜色上,深色模式的适配一直是一个较难的问题,这也是为什么现在大多数 APP 迟迟不全面覆盖深色模式的原因,也有很多 APP 刚上架不久又立马下线深色模式。不过好在对于个人博客网站,深色模式不是必需品,如果有的话算是锦上添花,没有的话也不会影响什么,毕竟大多数人还是在「白底黑字」的情况下浏览你的博客内容。

参考文章

  1. Google Material Design
  2. 给博客增加深色模式支持
  3. 暗色模式上线
  4. MDN 中的 prefers-color-scheme
  5. 利用 CSS 媒体查询让网页配色跟随 macOS 深色模式

本文由 Sanarous 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可,转载前请务必署名
本文链接:https://bestzuo.cn/posts/blog-night-mode.html
最后更新于:2020-07-01 20:27:00

切换主题 | SCHEME TOOL  
>