最近有人问起本站的夜间模式是如何实现的,虽说原理挺简单,但毕竟三言两语只能说个大概
今日得闲,把实现思路写成文章,以方便后来者
​​‌‌​​​‌‌​‌​​‌‌‍​‌​‌‌‌​​‌‌‌‌​‌​‍​‌​​‌​​​‌​​​‌‌​‍​‌​‌‌​​​‌‌​​​​​‍​​‌​‌‌‌‌‌‌‌‌​​​‍​‌‌​​‌‌‌​‌‌​​‌‌‌‍​‌‌​​​‌‌‌​​​‌​‌‍​​‌‌‌‌‌‌‌‌​​‌‌‍​​​​​​‌​​‌​​‌‌​‍​‌​‌‌​​‌​‌​​‌​‌‍​‌​​‌​​​‌​‌‌‌​‌‍​​‌​​‌​​​​​​‌​​‍​‌​‌‌​‌​‌​‌‌‌‌‌‍​‌​​‌‌​‌‌‌​​​‌‌‍​‌‌​‌​‌​​​​​‌​‌‌‍​​‌​‌​‌‌‌​‌‌‌‌​‍​‌​​​​​‌‌‌‌​​​​‍​​‌‌‌‌‌‌‌‌​​‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​​​‌​‌​‌‌​​‌‌‌​‍‌​​‌​‌​‌‍‌​​‌​​​​‍‌​​​‌​‌​‍‌​​​‌‌​‌‍‌​​‌​​​‌‍‌​​‌‌​‌​‍‌​​​​‌‌​‍‌‌​‌​​​‌‍‌​​‌‌‌‌​‍‌​​‌‌​‌‌‍​‌‌​​​‌​‌‌‌​​​‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​​‌‌‌​‍‌‌​​​‌‌‌‍‌‌​‌​​‌​‍‌‌​​‌‌‌‌‍‌‌​​‌‌​‌‍‌‌​‌​​‌​‍‌‌​​‌‌​‌‍‌‌​​​‌‌‌‍​‌​‌‌​‌‌‌‌​​‌​​‍​‌‌​​​​‌​‌​​​‌‌‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌‌​‌​​‌‌‌‌​​​‍​‌​‌​​​‌‌​​‌‌‌‌‍​‌​‌​​​‌​‌‌‌‌‌‌‍​​​​​​​​‌‌‌​​‌​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​​‌‌‌‌‍‌​​​‌‌​​‍‌‌​​​‌​‌‍‌​‌​​​‌‌‍‌​‌​​​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌​‌​‌‍‌​​‌‌‌‌​‍‌​​‌‌​‌‌‍‌‌​‌​​​‌‍‌​​‌‌‌​​‍‌​​‌​​​‌‍‌​‌​​​‌‌‍‌​​‌‌‌‌​‍‌​​​‌‌​‌‍‌​​‌‌‌​​‍‌​​‌​‌‌‌‍‌​​‌​‌‌​‍‌​​​‌​​‌‍‌​​‌‌​‌​‍‌​​​‌‌​​‍‌​‌​​​‌‌‍‌​​‌‌‌​​‍‌​​‌​​​​‍‌​​‌‌​‌‌‍‌​​‌‌​‌​‍‌​‌​​​‌‌‍‌​​‌‌‌‌​‍‌​​‌‌​‌‌‍‌​​‌‌​‌‌‍‌‌​‌​​‌​‍‌​​‌​​​‌‍‌​​‌​‌‌​‍‌​​‌‌​​​‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌‌​‌​​‌​‍‌​​‌​​‌​‍‌​​‌​​​​‍‌​​‌‌​‌‌‍‌​​‌‌​‌​‍‌‌​‌​​‌​‍‌​​​‌​‌‌‍‌​​‌​​​​‍‌‌​‌​​‌​‍‌​​‌‌‌​‌‍‌​​‌​​‌‌‍‌​​‌​​​​‍‌​​‌‌​​​
你可以点击文章标题下方和左下角看板娘菜单的眼睛按钮来切换夜间模式,或者点击这里查看效果

实现思路

本质上讲,“夜间模式”就是在指定时间段内修改页面配色、图片亮度等,因此我们把问题拆开为三部分进行分析:

动态修改页面配色、图片亮度
在指定时间段内修改
具体的样式Style内容
动态修改页面配色、图片亮度 既然提到了动态,就免不了要用到JavaScript了。
我们知道样式有三种书写形式,分别为外联、内联和内嵌。使用JS修改样式,就要从这三个地方入手:

引入一个新的外联CSS文件,以覆盖原有样式  /不影响原有样式文件、方便维护/
不引入新的CSS,而是在原有CSS上追加样式,以class名区分  /方便JS操作,实现优雅、方便维护/
在页面中插入style标签Tag,在其中书写样式  /不增加HTTP连接数、方便维护/
修改元素的style属性,即内嵌样式  /不推荐/
第四种方法违反了“结构与表现分离的设计思想”,且内嵌样式优先级过高
而且真正实现的话,需要对大量元素编写JS代码,执行效率低下,维护困难,因此一般不作考虑
前三种方法都有其适用场景,在这里选择第二种

以class来区分样式,实现了功能与表现的分离。对于JS代码来说,问题变成了如何操作元素的class,而不必关心样式的具体表现。

使用JS控制元素class

JS中Element.classList的add()和remove()方法,正好满足我们的需求,且支持绝大多数浏览器(IE10以上支持)
但先别急着写,还有一个细节:夜间模式这个状态应被保存下来一直生效,或持续到本次会话Session结束,cookie可以满足我们的要求
所以事情就很明朗了,编写代码

function switchNightMode(){
    var night = document.cookie.replace(/(?:(?:^|.*;\s*)night\s*\=\s*([^;]*).*$)|^.*$/, "$1") || '0';
    if(night == '0'){
        document.body.classList.add('night');
        document.cookie = "night=1;path=/"
        console.log('夜间模式开启');
    }else{
        document.body.classList.remove('night');
        document.cookie = "night=0;path=/"
        console.log('夜间模式关闭');
    }
}

即可实现夜间模式的切换和状态保存

在指定时间段内修改

思路很简单,页面加载后判断时间,在指定范围内修改元素class
这里有一个知识点:立即调用函数

(function(){
    if(document.cookie.replace(/(?:(?:^|.*;\s*)night\s*\=\s*([^;]*).*$)|^.*$/, "$1") === ''){
        if(new Date().getHours() > 22 || new Date().getHours() < 6){
            document.body.classList.add('night');
            document.cookie = "night=1;path=/";
            console.log('夜间模式开启');
        }else{
            document.body.classList.remove('night');
            document.cookie = "night=0;path=/";
            console.log('夜间模式关闭');
        }
    }else{
        var night = document.cookie.replace(/(?:(?:^|.*;\s*)night\s*\=\s*([^;]*).*$)|^.*$/, "$1") || '0';
        if(night == '0'){
            document.body.classList.remove('night');
        }else if(night == '1'){
            document.body.classList.add('night');
        }
    }
})();

具体的样式内容

在上面的代码中,我们控制了body的class,这是所有页面元素的祖先元素。启用夜间模式时,body的class值含有night
因此在编写夜间模式的样式时,在样式选择器前加body.night即可,可酌情使用!important
例如

body.night{
    background-color: #263238;
    color: #aaa;
}

就可改变夜间模式下body的背景色和字体颜色

图片亮度

使用CSS滤镜中的brightness()滤镜(仅支持现代浏览器)

body.night img {
    filter: brightness(30%);
}

后端处理

因为是在本地通过JS操控样式,所以在JS加载前后样式不一样,会导致页面闪烁。
可在博客主题中加上判断,当检测到cookie含有相关字段后可直接输出body class为night,如:

<body class="<?php echo($_COOKIE['night'] == '1' ? 'night' : ''); ?>">

参考

Element.classList - Web API 接口Interface | MDN
Document.cookie - Web API 接口 | MDN
filter - CSS | MDN

作者:https://imjad.cn/archives/code/add-night-mode-to-blog