Explorer's Guide

Explorer's Guide

构建与托管

本站使用 HEXO 构建生成静态网站文件,托管在云服务器创建的 Plesk 面板中。

Plesk 中配置了 Webhook,当我提交变更到我的 Github 私有仓库以后,触发 Actions 构建网站,变更的内容将自动同步到 Plesk 网站文件夹进行更新。

2024/02/06 更新:因为虚拟主机宿主机多次被 DDoS 攻击,所以我把网站托管换到了 CloudFlare Pages

为什么用 HEXO?

很多年前当我还是一个大学生的时候,捣鼓 VPS、建站是一件很酷的事情。当时我用的是 BandWagon Host,使用 NGinx、PHP、MySQL等配套软件安装 WordPress 建立了自己的个人博客。当时还经历过数据库被勒索,丢失了我写的几篇文章(其实也没有什么,很初级的内容)。

重点在于,像 WordPress 这样依赖数据库的博客非常需要维护,对比之下,静态生成的博客如 HEXO、HUGO 只需关心自己的博客文章文件即可,同时 Markdown 格式又允许使用不同的设备随时编写、记录,没有什么迁移负担。

换到 HEXO 以后,整个博客站点托管在 GitHub,可以很方便地通过它的 Actions 自动生成新的网站文件,也不用担心丢失。

个性化

主题

使用 Icarus 主题。

大屏设备可以获得最佳阅读体验。

封面图片

有的来自 Unsplash,有的则是我自己用 Figma 绘制并导出的 SVG 文件。矢量图形清晰度非常完美,体积也极小。使用 Figma 后,整理素材更简单了。

字体

本站共计使用四种开源/免费字体:

  1. Source Serif
    The quick brown fox jumps over the lazy dog
  2. Source Han Serif CN
    The quick brown fox jumps over the lazy dog 敏捷的棕色狐狸跳过了懒狗
  3. LXGWWenKai
    The quick brown fox jumps over the lazy dog 敏捷的棕色狐狸跳过了懒狗
  4. Vivaldi
    The quick brown fox jumps over the lazy dog

以及系统字体:

font-family
1
2
font-family: SF Pro Text, SF Pro Icons, Helvetica Neue, Helvetica, Arial,
sans-serif;

字体文件由脚本收集所有用到字符简化而成,提高加载速度。

generate-font-files.js
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const fs = require("fs");
const _ = require("lodash");
const Fontmin = require("fontmin");

const directory = "source/_posts";
const scriptDirectory = "custom-scripts/font";
const fontFileDirectory = `${scriptDirectory}/files`;

const files = fs.readdirSync(directory).filter((item) => item !== ".DS_Store");
const fonts = fs
.readdirSync(fontFileDirectory)
.filter((item) => item !== "index.js");

const getLetters = () => {
const letterMap = {};

_.forEach(files, (file) => {
const fileContent = fs.readFileSync(`${directory}/${file}`, "utf8");
const letters = _.toArray(fileContent);
_.forEach(letters, (letter) => {
if (!letterMap[letter]) {
letterMap[letter] = 1;
}
});
});
return Object.keys(letterMap).join("");
};

const currentLetters = getLetters();

const lettersText = fs.existsSync(`${scriptDirectory}/letters.txt`)
? fs.readFileSync(`${scriptDirectory}/letters.txt`, "utf8")
: "";

if (currentLetters === lettersText) {
return;
}

fs.writeFileSync(`${scriptDirectory}/letters.txt`, getLetters());

const generateFontFile = (letters) => {
_.forEach(fonts, (font) => {
const task = new Fontmin()
.src(`${fontFileDirectory}/${font}`)
.dest("themes/icarus/source/fonts")
.use(
Fontmin.glyph({
text: letters,
hinting: false,
})
)
.use(Fontmin.ttf2woff2());

task.run(function (err, files) {
if (err) {
throw err;
}
});
});
};

generateFontFile(currentLetters);

插件

懒加载

使用了 hexo-lazyload-element 处理图片、视频、iframe 资源的懒加载,提升页面性能的同时避免消耗不必要的流量。

WebP图片

部分图片是上传到七牛云并使用 ?imageMogr2/format/webp 参数加载 WebP 图片格式资源。但是对于一些设备(iOS13以下,MacOS Mojave 以下)需要做兼容。

2024/02/22 更新:更换了图床,全站都没有再使用七牛云的资源了。

checkwebp.js
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*! modernizr 3.6.0 (Custom Build) | MIT *
* https://modernizr.com/download/?-webp-setclasses !*/
!(function (e, n, A) {
function o(e) {
var n = u.className,
A = Modernizr._config.classPrefix || "";
if ((c && (n = n.baseVal), Modernizr._config.enableJSClass)) {
var o = new RegExp("(^|\\s)" + A + "no-js(\\s|$)");
n = n.replace(o, "$1" + A + "js$2");
}
Modernizr._config.enableClasses &&
((n += " " + A + e.join(" " + A)),
c ? (u.className.baseVal = n) : (u.className = n));
}
function t(e, n) {
return typeof e === n;
}
function a() {
var e, n, A, o, a, i, l;
for (var f in r)
if (r.hasOwnProperty(f)) {
if (
((e = []),
(n = r[f]),
n.name &&
(e.push(n.name.toLowerCase()),
n.options && n.options.aliases && n.options.aliases.length))
)
for (A = 0; A < n.options.aliases.length; A++)
e.push(n.options.aliases[A].toLowerCase());
for (o = t(n.fn, "function") ? n.fn() : n.fn, a = 0; a < e.length; a++)
(i = e[a]),
(l = i.split(".")),
1 === l.length
? (Modernizr[l[0]] = o)
: (!Modernizr[l[0]] ||
Modernizr[l[0]] instanceof Boolean ||
(Modernizr[l[0]] = new Boolean(Modernizr[l[0]])),
(Modernizr[l[0]][l[1]] = o)),
s.push((o ? "" : "no-") + l.join("-"));
}
}
function i(e, n) {
if ("object" == typeof e) for (var A in e) f(e, A) && i(A, e[A]);
else {
e = e.toLowerCase();
var t = e.split("."),
a = Modernizr[t[0]];
if ((2 == t.length && (a = a[t[1]]), "undefined" != typeof a))
return Modernizr;
(n = "function" == typeof n ? n() : n),
1 == t.length
? (Modernizr[t[0]] = n)
: (!Modernizr[t[0]] ||
Modernizr[t[0]] instanceof Boolean ||
(Modernizr[t[0]] = new Boolean(Modernizr[t[0]])),
(Modernizr[t[0]][t[1]] = n)),
o([(n && 0 != n ? "" : "no-") + t.join("-")]),
Modernizr._trigger(e, n);
}
return Modernizr;
}
var s = [],
r = [],
l = {
_version: "3.6.0",
_config: {
classPrefix: "",
enableClasses: !0,
enableJSClass: !0,
usePrefixes: !0,
},
_q: [],
on: function (e, n) {
var A = this;
setTimeout(function () {
n(A[e]);
}, 0);
},
addTest: function (e, n, A) {
r.push({ name: e, fn: n, options: A });
},
addAsyncTest: function (e) {
r.push({ name: null, fn: e });
},
},
Modernizr = function () {};
(Modernizr.prototype = l), (Modernizr = new Modernizr());
var f,
u = n.documentElement,
c = "svg" === u.nodeName.toLowerCase();
!(function () {
var e = {}.hasOwnProperty;
f =
t(e, "undefined") || t(e.call, "undefined")
? function (e, n) {
return n in e && t(e.constructor.prototype[n], "undefined");
}
: function (n, A) {
return e.call(n, A);
};
})(),
(l._l = {}),
(l.on = function (e, n) {
this._l[e] || (this._l[e] = []),
this._l[e].push(n),
Modernizr.hasOwnProperty(e) &&
setTimeout(function () {
Modernizr._trigger(e, Modernizr[e]);
}, 0);
}),
(l._trigger = function (e, n) {
if (this._l[e]) {
var A = this._l[e];
setTimeout(function () {
var e, o;
for (e = 0; e < A.length; e++) (o = A[e])(n);
}, 0),
delete this._l[e];
}
}),
Modernizr._q.push(function () {
l.addTest = i;
}),
Modernizr.addAsyncTest(function () {
function e(e, n, A) {
function o(n) {
var o = n && "load" === n.type ? 1 == t.width : !1,
a = "webp" === e;
i(e, a && o ? new Boolean(o) : o), A && A(n);
}
var t = new Image();
(t.onerror = o), (t.onload = o), (t.src = n);
}
var n = [
{
uri: "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=",
name: "webp",
},
{
uri: "data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==",
name: "webp.alpha",
},
{
uri: "data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA",
name: "webp.animation",
},
{
uri: "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=",
name: "webp.lossless",
},
],
A = n.shift();
e(A.name, A.uri, function (A) {
if (A && "load" === A.type)
for (var o = 0; o < n.length; o++) e(n[o].name, n[o].uri);
});
}),
a(),
o(s),
delete l.addTest,
delete l.addAsyncTest;
for (var p = 0; p < Modernizr._q.length; p++) Modernizr._q[p]();
e.Modernizr = Modernizr;
})(window, document);

Modernizr.on("webp", function (result) {
if (result) {
// supported
} else {
// not-supported
const webpImages = document.querySelectorAll("img");
Array.prototype.filter
.call(
webpImages,
(item) =>
item.getAttribute("src") &&
item.getAttribute("src").includes("")
)
.forEach((img) => {
const currentSrc = img.getAttribute("src");
img.src = currentSrc.replace("?imageMogr2/format/webp", "");
});

const galleryItems = document.querySelectorAll(".gallery-item");
Array.prototype.filter
.call(
galleryItems,
(item) =>
item.getAttribute("href") &&
item.getAttribute("href").includes("")
)
.forEach((img) => {
const currentSrc = img.getAttribute("href");
img.href = currentSrc.replace("?imageMogr2/format/webp", "");
});

const lazyloadItems = document.querySelectorAll(".lazyload-wrap");
Array.prototype.filter
.call(
lazyloadItems,
(item) =>
item.dataset &&
item.dataset.content.includes("%3FimageMogr2%2Fformat%2Fwebp")
)
.forEach((item) => {
const currentSrc = item.dataset.content;
item.dataset.content = currentSrc.replace(
"?%3FimageMogr2%2Fformat%2Fwebp",
""
);
});
}
});

RSS

使用 hexo-generator-feed 生成 /feed.xml 便于 RSS 阅读器订阅。

你可以使用以下地址订阅内容。

RSS
1
https://lynan.cn/feed.xml

评论区

使用 Gitalk

广告

使用 Google AdSense,在边栏 Widget内(小屏设备则在文章最底部),对阅读影响比较小。如果它能覆盖我的 OSS 费用就好啦。

彩蛋

CSS 动画

例如

🎢

iframe 交互

页面和 iframe 之间使用了 postMessage 方式进行通信,丰富静态页面的功能。

单独设置的文章背景

例如关西旅行的枫叶背景。

本文的彩蛋背景。

暂时是这些,未来可能会有一些新的想法加入进来。

作者

Lynan

发布于

2022-01-01

许可协议

评论