鼓捣 blog 的时候一直觉得夜间模式和阅读模式啥的挺好,Typecho 倒是有插件可以实现,但是 hexo 好像就比较难了,因为我用的主题没有 pjax 的技术,所以每次刷新都可能使这些样式失效。但是我又挺想整一个,于是找到了一个神奇的方案:JavaScript + CSS + PHP + Cookie。

主要思路

使用搜狐的 PV 统计的 API 获取到的 IP,通过 ajax 提交数据,将 IP 映射到大致的经纬度,然后用 PHP 在后端计算日出日落的时间戳,存在 Cookie 里,然后每次刷新用 JavaScript 读取 Cookie 然后判断是否需要渲染遮罩。(依赖 JavaScript 加载会导致每次刷新的时候遮罩没有及时加载而有“闪烁”的效果)通过 PHP 文件读取 Cookie 来判断输出对应的 CSS 样式。

缺陷

在页面中包含部分由 JavaScript 实现的动画的时候,可能会导致元素在动画区间内没有被遮罩覆盖。

实现

相关接口

首要解决的问题就是,使用 JavaScript 完成 IP 的获取以及 IP 到经纬度的映射。这里提供两个咱找到的接口。

1
https://pv.sohu.com/cityjson?ie=utf-8

直接使用 script 标签包括并放在页面的 html 代码中,就能得到如下数组。之后可以用 JavaScript 直接读取。

1
var returnCitySN = {"cip": "YourIPAddressHere", "cid": "cidHere", "cname": "YourLocationHere"};

至于 IP 到经纬度的映射,我找到了一个 GeoIP 库,但是速度并不是太快,这也是我使用 Cookie 来存储数据的原因之一——减少请求次数。此接口只需要修改 GET 参数中的 ip 即可。

1
http://api.ipinfodb.com/v3/ip-city/?key=c9dcc88453e33a9e63ebad8d65f91583e87abd8185dd95f09fbeef6c62264f7d&ip=YourIPHere

返回的结果中会包括大致地点和大致经纬度以及时区

1
OK;;8.8.8.8;US;United States of America;California;Mountain View;94043;37.406;-122.079;-08:00
PHP 实现部分
α. 日出日落时间计算

这一部分主要就是根据前端的请求数据映射位置并计算日出日落的时间戳。关于时间戳的计算,PHP 提供了两个现成的函数 不愧是 PHP。具体请参照 PHP Manual。

PHP Manual: data_sunrise()

PHP Manual: data_sunset()

β. CSS 输出部分

这里首先要做的就是在 hexo 模板文件的你喜欢的位置加入一个 div,并指定一个 div。这样就可以在 CSS 中使用 id 作为筛选器而避免影响到其他元素。因为日出日落时间已经存在 Cookie 里了,所以只需要在 PHP 里读取 Cookie 来判断即可。这里贴上我随便码的代码(真就随便码的,上线测试没几个小时就下线了)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header('Content-Type: text/css; charset=utf-8');
echo "#layerShade{";
echo "position:fixed;";
if(time() > $_COOKIE['sunset'] || time() < $_COOKIE['sunrise']){
echo "background-color:#000;";
}else{
echo "background-color:transparent;";
}
echo "top:0;";
echo "left:0;";
echo "z-index:2147483647;";
echo "pointer-events:none;";
echo "opacity:0.33;";
echo "width:100%;";
echo "height:100%;";
echo "}";

主要需要注意的地方就是 header,带上这个才能输出为 text/css,否则是 plain text,就不会被识别成 stylesheet。使用的时候只需要像一般的 CSS 一样用 link 标签在 html 中引用即可。

JavaScript 实现部分

由于要使用 JavaScript 来操作 Cookie 和发起请求,所以用 jQuery 会方便不少。因此,可以在模板中加上如下两条语句。

1
2
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>

主要就是判断 Cookie 是否存在、发起请求、保存 Cookie 这样几个步骤,其中的细节不再赘述,直接放上之前测试的时候写的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
let timeNow = new Date();
let timestampNow = timeNow.getTime();
if($.cookie('sunrise') === undefined || $.cookie('sunset') === undefined || returnCitySN["cip"] !== $.cookie('ip')) {
console.log("IsNight? Run!");

//request data process
var details = {};
details["TIME"] = parseInt(new Date().getTime() / 1000);
details["IP_ADDRESS"] = returnCitySN["cip"];
let data = JSON.stringify(details);
data = BASE64.encode(data);
data = "PLKOZ23476/" + data;

//request for sunrise/sunset timestamp
$.ajax({
type: "GET",
async: false,
url: "https://api.lemonprefect.cn/?" + data,
success: function (data) {

//Setup Cookie expires till the next day
let isNigntArray = data.split(",");
let timeExpire = new Date(timeNow.toLocaleDateString()).getTime() - 1;
let timeLeftTillExpire = 24 * 60 * 60 * 1000 - (timestampNow - timeExpire);
let cookieValidTime = new Date();
cookieValidTime.setTime(timeLeftTillExpire + timestampNow);
$.cookie('sunrise',isNigntArray[0],{expires: cookieValidTime,path: '/'});
$.cookie('sunset',isNigntArray[1],{expires: cookieValidTime,path: '/'});
$.cookie('ip',returnCitySN["cip"],{expires: cookieValidTime,path: '/'});
},
error: function () {
console.log("Is Night?");
}
});
}
let timeNowCompare = parseInt(timestampNow / 1000);
if(timeNowCompare > $.cookie('sunset') || timeNowCompare < $.cookie('sunrise')){
console.log("Good Evening!");
}else{
console.log("Maybe it's daytime now.");
}
console.log("Timestamp Sunrise:" + $.cookie('sunrise'));
console.log("Timestamp Sunset:" + $.cookie('sunset'));

至此,将所有代码写好之后上线,就能组合在一起正常运作。