Hexo-Fluid主题添加相册功能

本文最后更新于:1 个月前

Hexo-Fluid主题添加相册功能

先找到了这篇文章:hexo的fluid主题添加瀑布流懒加载相册功能 - AIGISSS,但这篇文章没有给出自动生成包含图片尺寸等信息的json文件的代码(后来发现在作者的GitHub上有:https://github.com/Cenergy/images)。于是又参考了[Hexo NexT 博客增加瀑布流相册页面 | 频率](https://pinlyu.com/post/31/)的方法。最后融合了一下两种方法的优点,如果你也想添加相册功能可以参考以下步骤。

新建图集页

博客目录打开终端,输入:

1
hexo new page photos

然后需要修改主题配置,在menu下面添加这一行:

1
2
menu:
- { key: "photos", link: "/photos/", icon: "iconfont icon-images" }

但是这样在导航栏里显示的会是英文photos,如果想要显示成中文图集的话需要搭配中文翻译。(当然如果不考虑多语言的话,其实可以直接把key: "photos"换成key: "图集"就行了)在themes/hexo-theme-fluid/languages/zh-CN.yml文件中添加下面的内容即可:

1
2
3
4
photos:
menu: '图集'
title: '图集'
subtitle: '图集'

获取图片尺寸

我是把source/photos/images作为图片根目录,再将图片按类别放进不同的文件夹里。

为了获取图片尺寸,需要在博客目录打开终端,安装image-size

1
npm install image-size --save

然后我们需要新建文件scripts/createPhotoList.js,将图片根目录下的图片进行扫描,调用image-size来获取图片尺寸信息,并和图片的路径信息一起保存在一个json文件中。

这里我使用了构造函数生成一个Album类,使得图片根目录下的子文件夹也能被检测到。

.DS_Store是Mac OS为了文件夹索引自动生成的隐藏文件,会导致出错,非常烦人,所以我添加了对.DS_Store文件的忽略。

scripts/createPhotoList.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
'use strict';
const fs = require('fs');
const sizeOf = require('image-size');
const originPath = 'source/photos/images';
const output = 'source/photos/galleryList.json';
const arr = [];

var Album = function (FolderPath) {
this.AlbumObject = {};
this.test = function test(path) {
this.files = fs.readdirSync(path);
if (!this.files.length) return;
this.AlbumObject["name"] = path.split('/').pop()
this.AlbumObject.children = [];

this.files.forEach(v => {
const vPath = path + '/' + v;
const stats = fs.statSync(vPath);
if (v === ".DS_Store") {
;
}
else if (stats.isFile()) {
const imgSize = sizeOf(vPath);
this.AlbumObject.children.push(imgSize.width + '.' + imgSize.height + ' ' + vPath.replace(originPath+'/', ''))
} else {
new Album(vPath);
};
});
if (this.AlbumObject.children.length) { arr.push(this.AlbumObject); };
};
this.test(FolderPath);
}

try {
new Album(originPath);
fs.writeFileSync(output, JSON.stringify(arr, null, '\t'));
} catch (err) {
console.log(err);
}

设置容器样式

编辑文件source/photos/index.md,我调整了下图片框的颜色使得在日间和夜间模式下都能清晰地显示出图片名称:

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
---
title: 图集
layout: gallery
comments: true
---

<style>
.ImageGrid {
width: 100%;
max-width: 1040px;
margin: 0 auto;
text-align: center;
}
.card {
overflow: hidden;
transition: .3s ease-in-out;
border-radius: 8px;
background-color: rgba(180,180,180,0.2);
padding: 1.4px;
}
.ImageInCard img {
padding: 0;
border-radius: 8px;
width:100%;
height:100%;
}
@media (prefers-color-scheme: dark) {
.card {background-color: rgba(180,180,180,0.2);}
}
</style>
<div id="imageTab"></div>
<div class="ImageGrid"></div>

绘制图片容器

图片下显示的文字即为图片的文件名。

新建文件source/js/photoWall.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
var imgDataPath = "/photos/galleryList.json"; //图片名称高宽信息json文件路径
var imgPath = '/photos/images/'; //图片访问路径
// var imgPath = "https://cdn.jsdelivr.net/gh/Cenergy/images/gallery/"; //图片访问路径
var imgMaxNum = 50; //图片显示数量

var windowWidth =
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;
if (windowWidth < 768) {
var imageWidth = 145; //图片显示宽度(手机端)
} else {
var imageWidth = 250; //图片显示宽度
}

const photo = {
page: 1,
offset: imgMaxNum,
init: function () {
var that = this;
$.getJSON(imgDataPath, function (data) {
that.render(that.page, data);
//that.scroll(data);
that.eventListen(data);
});
},
constructHtml(options) {
const {
imageWidth,
imageX,
imageY,
name,
imgPath,
imgName,
imgNameWithPattern,
} = options;
const htmlEle = `<div class="card lozad" style="width:${imageWidth}px">
<div class="ImageInCard" style="height:${
(imageWidth * imageY) / imageX
}px">
<a data-fancybox="gallery" href="${imgPath}${imgNameWithPattern}"
data-caption="${imgName}" title="${imgName}">
<img class="lazyload" data-src="${imgPath}${imgNameWithPattern}"
src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
onload="lzld(this)"
lazyload="auto">
</a>
</div>
<p>${imgName}</p>
</div>`;
// 如果不希望显示图片名称的话删去<p>${imgName}</p>这一行即可。
return htmlEle;
},
render: function (page, data = []) {
this.data = data;
if (!data.length) return;
var html,
imgNameWithPattern,
imgName,
imageSize,
imageX,
imageY,
li = "";

let liHtml = "";
let contentHtml = "";

data.forEach((item, index) => {
const activeClass = index === 0 ? "active" : "";
liHtml += `<li class="nav-item" role="presentation">
<a class="nav-link ${activeClass} photo-tab" id="home-tab" photo-uuid="${item.name}" data-toggle="tab" href="#${item.name}" role="tab" aria-controls="${item.name}" aria-selected="true">${item.name}</a>
</li>`;
});
const [initData = {}] = data;
const { children = [],name } = initData;
children.forEach((item, index) => {
imgNameWithPattern = item.slice(item.indexOf(" ")+1);
imgName = imgNameWithPattern.split("/").pop();
imageSize = item.split(" ")[0];
imageX = imageSize.split(".")[0];
imageY = imageSize.split(".")[1];
let imgOptions = {
imageWidth,
imageX,
imageY,
name,
imgName,
imgPath,
imgNameWithPattern,
};
li += this.constructHtml(imgOptions);
});
contentHtml += ` <div class="tab-pane fade show active" role="tabpanel" aria-labelledby="home-tab">${li}</div>`;

const ulHtml = `<ul class="nav nav-tabs" id="myTab" role="tablist">${liHtml}</ul>`;
const tabContent = `<div class="tab-content" id="myTabContent">${contentHtml}</div>`;

$("#imageTab").append(ulHtml);
$(".ImageGrid").append(tabContent);
this.minigrid();
},
eventListen: function (data) {
let self = this;
var html,
imgNameWithPattern,
imgName,
imageSize,
imageX,
imageY,
li = "";
$('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {
$(".ImageGrid").empty();
const selectId = $(e.target).attr("photo-uuid");
const selectedData = data.find((data) => data.name === selectId) || {};
const { children,name } = selectedData;
let li = "";
children.forEach((item, index) => {
imgNameWithPattern = item.split(" ")[1];
imgName = imgNameWithPattern.split("/").pop();
imageSize = item.split(" ")[0];
imageX = imageSize.split(".")[0];
imageY = imageSize.split(".")[1];
let imgOptions = {
imageWidth,
imageX,
imageY,
name,
imgName,
imgPath,
imgNameWithPattern,
};
li += self.constructHtml(imgOptions);
});
$(".ImageGrid").append(li);
self.minigrid();
});
},
minigrid: function () {
var grid = new Minigrid({
container: ".ImageGrid",
item: ".card",
gutter: 12,
});
grid.mount();
$(window).resize(function () {
grid.mount();
});
},
};
photo.init();

注入代码

引用这类外部资源时最好都在//前加上https:前缀,否则有些资源可能无法被浏览器获取。

新建scripts/injector.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
const { root: siteRoot = "/" } = hexo.config;
// layout为gallery的时候导入这些js与css
hexo.extend.injector.register(
"body_end",
`
<link rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css">
<script src="https://cdn.jsdelivr.net/npm/minigrid@3.1.1/dist/minigrid.min.js"></script>
<script src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/lazyloadjs/3.2.2/lazyload.js"></script>
<script defer src="${siteRoot}js/photoWall.js"></script>
`,
"gallery"
);

Hexo-Fluid主题添加相册功能
https://4dtree.github.io/2022/06/21/Hexo-Fluid主题添加相册功能/
作者
四维树
发布于
2022年6月21日
更新于
2022年7月6日
许可协议