hexo的fluid主题添加相册功能及自定义页面

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

在阅读本博客之前,我默认你已经配置好你的hexo博客。 如还未配置请参考hexo配置文档:https://hexo.bootcss.com ,我的博客主题是fluid,fluid用户参考手册:https://hexo.fluid-dev.com/docs

创建相册页面

新建相册页 hexo new page photos,编辑 /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
33
34
35
---
title: 相册
subtitle: 用一张小小的照片,记录下身边的美好瞬间
date: 2022-4-30 19:04:03
layout: photo
comment: //评论插件,删除,如想要填入评论插件,开启你的评论插件
---

<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: #efefef;
padding: 1.4px;
}
.ImageInCard img {
padding: 0;
border-radius: 8px;
width:100%;
height:100%;
}
@media (prefers-color-scheme: dark) {
.card {background-color: #333;}
}
</style>

<div id="imageTab"></div>
<div class="ImageGrid"></div>

Github+picgo搭建图床

我的相册是采用GitHub+jsDelivr加速来加载图片,这样有利于更快加载图片,当然你可以选择跳过这一步,将你的图片放到你的博客中。或者搭建其他图床。

下载picgo

百度搜索picgo下载picgo

picgo下载网址:https://molunerfinn.com/PicGo/

picgo安装完成

创建github仓库

创建一个公共的仓库,用来存放你的图片。

在picgo中设置图床

在picgo中选择github图床,如果你的图床仓库为其他的,请依据的你选择。以下是github图床设置
仓库名选择你创建的仓库,分支名为main,存储路径按照你相册文件名存放。设置自定义域名,我的域名中加入了cdn.jsdelivr这个会加速我们的图片加载速度。token设置在你创建的github仓库中点击setting找到personal access token将创建好的token复制进token设置中
picgo图床设置

至此我们的图床设置完毕,点击上传图片之后,图片就可以保存到githu仓库中,在picgo中选择相册,即可查看图片链接来进行访问

处理图片json文件

这一步是为了将相册所有的图片信息目录及大小保存到json文件中,访问通过固定域名+json文件中的图片路径即可访问

在blog目录下创建相册文件

创建imagescreate文件夹,结构如下图所示:
相册文件结构

创建phototool.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
 const fs = require('fs-extra');
const path = require('path');
const imageSize = require('image-size');

const rootPath="imagescreate/" //相册相对路径

class PhotoExtension {
constructor() {
this.size = 64;
this.offset = [0, 0];
}
}

class Photo {
constructor() {
this.dirName = '';
this.fileName = '';
this.iconID = '';
this.extension = new PhotoExtension();
}
}

class PhotoGroup {
constructor() {
this.name = '';
this.children = [];
}
}

function createPlotIconsData() {
let allPlots = [];
let allPlotGroups = [];

const plotJsonFile = path.join(__dirname, './photosInfo.json');
const plotGroupJsonFile = path.join(__dirname, './photos.json');

if (fs.existsSync(plotJsonFile)) {
allPlots = JSON.parse(fs.readFileSync(plotJsonFile));
}

if (fs.existsSync(plotGroupJsonFile)) {
allPlotGroups = JSON.parse(fs.readFileSync(plotGroupJsonFile));
}

fs.readdirSync(__dirname).forEach(function(dirName) {
const stats = fs.statSync(path.join(__dirname, dirName));
const isDir = stats.isDirectory();
if (isDir) {
const subfiles = fs.readdirSync(path.join(__dirname, dirName));
subfiles.forEach(function(subfileName) {
// 如果已经存在 则不再处理
// if (allPlots.find(o => o.fileName === subfileName && o.dirName === dirName)) {
// return;
// }

// 新增标
const plot = new Photo();
plot.dirName = dirName;
plot.fileName = subfileName;
const imageInfo = imageSize(rootPath+dirName + "/" + subfileName);
plot.iconID = imageInfo.width + '.' + imageInfo.height + ' ' + subfileName;
allPlots.push(plot);
console.log(`RD: createPlotIconsData -> new plot`, plot);

// 为新增标添加分组 暂时以它所处的文件夹为分组
let group = allPlotGroups.find(o => o.name === dirName);
if (!group) {
group = new PhotoGroup();
group.name = dirName;
allPlotGroups.push(group);
console.log(`RD: createPlotIconsData -> new group`, group);
}
group.children.push(plot.iconID);
});
}
});

fs.writeJSONSync(plotJsonFile, allPlots);
fs.writeJSONSync(plotGroupJsonFile, allPlotGroups);
}

createPlotIconsData();

生成json文件

  • 运行npm i -S image-size 导入获取文件大小第三方包
  • 运行node imagescreate/phototool.js生成json文件
    找到photos.json,将其复制到source/photos目录下。内容示例:
    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
     [
    {
    "name": "美食",
    "children": [
    "1036.584 foot1.jpg",
    "1036.584 foot1.jpg",
    "3000.4000 IMG20190906183445.jpg",
    "4000.3000 IMG20210306173452.jpg",
    "4000.3000 IMG20210624124705.jpg"
    ]
    },
    {
    "name": "风景",
    "children": [
    "1036.584 foot1.jpg",
    "1036.584 foot1.jpg",
    "3000.4000 IMG20190906183445.jpg"
    ]
    }
    {
    "name": "生活",
    "children": [
    "2401.1600 1619402831098.jpeg",
    "1440.1080 1637328153132.jpeg",
    "1440.1080 1637581717468.jpeg"
    ]
    }
    ]

    编写相册js文件

    在 /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
    var imgDataPath = "/photos/photos.json"; //图片名称高宽信息json文件路径   //填入你的路径,注意修改
    var imgPath = "http://imghe.xyz/"; //图片访问路径 //填入你的路径,注意修改
    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}${name}/${imgNameWithPattern}"
    data-caption="${imgName}" title="${imgName}">
    <img class="lazyload" data-src="${imgPath}${name}/${imgNameWithPattern}"
    src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
    onload="lzld(this)"
    lazyload="auto">
    </a>
    </div>
    </div>`;
    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.split(" ")[1];
    imgName = imgNameWithPattern.split(".")[0];
    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(".")[0];
    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();
    然后使用注册器将需要的js,css注入,在scripts/injector.js(如没有,则创建)中输入以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const js = hexo.extend.helper.get('js').bind(hexo);
    const css = hexo.extend.helper.get('css').bind(hexo);


    hexo.extend.injector.register('head_end', () => {
    return css('https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css');
    }, 'photo');
    hexo.extend.injector.register('head_end', () => {
    return js('https://cdn.jsdelivr.net/npm/jquery@3/dist/');
    }, 'photo');
    hexo.extend.injector.register('head_end', () => {
    return js('https://cdn.bootcdn.net/ajax/libs/lazyloadjs/3.2.2/lazyload.js');
    }, 'photo');
    hexo.extend.injector.register('head_end', () => {
    return js('https://cdn.jsdelivr.net/npm/minigrid@3.1.1/dist/minigrid.min.js');
    }, 'photo');
    hexo.extend.injector.register('body_end', () => {
    return js('/js/photoWall.js');
    },"photo");
    至此博客相册功能已经完成

页面自定义

hexo框架基本具备了博客搭建的基本功能,通常情况我们修改配置就可以了,但是为了使页面更美观,我们通常情况下会自定义页面,你可以自己编写css、js等,通过主题配置引入,具体方法参见主题文档。也可以使用注入器来自定义页面,参考文档:https://hexo.io/zh-cn/api/injector.html