Hexo Next 博客添加相册瀑布流

2023-05-16

原文:https://rebootcat.com/2020/09/19/nextphotowall/

前言

一直没有时间来整理下博客搭建的一些事情,现在补上一篇,给 Hexo Next 博客添加一个相册功能,使用瀑布流的方式。

原理说明

  • 使用 github 作为仓库存储图片文件(图床)
  • 使用 jsdelivr 进行图片 CDN 加速

优点

此种方式的优点是免费,不需要购买其他的对象存储产品;并且使用的是 github 作为图床,图片不会丢失。

早期的博文使用的是七牛云的免费存储,结果后来被他们删掉了。。。结果造成文中的一些图片链接都是 404,有兴趣的可以翻一翻我早期的博客。

缺点

由于采用的是 github 仓库存储图片,但是 github 对单仓库有 50MB 的大小限制,所以单仓库可能不能够存储太多的文件;

解决方法就是建立很多的图片仓库(稍微有点费劲,不过是行得通的);另外上传的单张图片大小最好不要太大。

还有个缺点就是得折腾啊,且看我后文。

各位可以参考下我的相册瀑布流: 摄影

开始搭建相册瀑布流

开始之前,需要简单介绍一下,我参考的是 Hexo NexT 博客增加瀑布流相册页面 这篇文章,文中涉及到的脚本主要都是 js 实现;与他不同的是,由于我对 js 的掌握远远不及我对 Python 的掌握,故部分脚本我采用了 Python 实现。

所以在开始操作之前,你可以根据自己的技能,选择不同的方式。如果你擅长 python,那么跟着我来吧。

新建 photo 页面

去到博客根目录:

mkdir -p source/photos

然后进入 photos 目录:

cd source/photos
vim index.md

把下面的粘贴保存:

---
title: 摄影
type: photos
---

<!-- CSS Code -->
<style>
.MyGrid{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}
@media(prefers-color-scheme:dark){.card{background-color:#333;}}
</style>
<!-- CSS Code End -->


<div class="MyGrid"></div>

修改 Next 主题配置文件

添加了 photos 页面后,需要在 next 配置文件中修改:

vim themes/next/_config.yml

找到 menu 项,填入如下:

photos: /photos || fas fa-camera-retro

比如我的是这样的:

menu:
  home: / || home
  about: /about/ || user
  tags: /tags/ || tags
  categories: /categories/ || th
  archives: /archives/ || archive
  #schedule: /schedule/ || calendar
  #sitemap: /sitemap.xml || sitemap
  #commonweal: /404/ || heartbeat
  guestbook: /guestbook || fas fa-comments
  photos: /photos || fas fa-camera-retro
  wiki: /wiki/ || wikipedia-w

完成之后还需要修改一下这个文件:

vim themes/next/languages/zh-CN.yml

找到 menu 项,加入如下一行:

 photos: 摄影

比如我的是这样的:

menu:
  home: 首页
  archives: 归档
  categories: 分类
  tags: 标签
  about: 关于
  search: 搜索
  schedule: 日程表
  sitemap: 站点地图
  commonweal: 公益 404
  guestbook: 留言
  photos: 摄影
  wiki: 维基

OK,到这里应该能看到这个 摄影 页面了,你可以现在本地测试一下看:

hexo s -g

添加 js 脚本

首先需要在 source 目录下新建一个 js 目录,用来保存自定义的一些 js 脚本;

mkdir -p source/js

然后新建 mygrid.js 文件,粘贴下面的一段代码:

// 获取网页不含域名的路径
var windowPath = window.location.pathname;
// 图片信息文件路径
var imgDataPath = '/photos/photoslist.json';
// 图片显示数量
var imgMaxNum = 50;
// 获取窗口宽度(以确定图片显示宽度)
var windowWidth = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
if (windowWidth < 768) {
    var imageWidth = 145; // 图片显示宽度(手机)
} else {
    var imageWidth = 215; // 图片显示宽度
}
// 腾讯云图片处理样式(根据图片显示宽度)
var imgStyle = '!' + imageWidth + 'x';


// 图片卡片(照片页面)
if (windowPath.indexOf('photos') > 0 ) {
    var LinkDataPath = imgDataPath;
    photo = {
        page: 1,
        offset: imgMaxNum,
        init: function () {
            var that = this;
            $.getJSON(LinkDataPath, function (data) {
                that.render(that.page, data);
            });
        },
        render: function (page, data) {
            var begin = (page - 1) * this.offset;
            var end = page * this.offset;
            if (begin >= data.length) return;
            var html, imgNameWithPattern, imgName, imageSize, imageX, imageY, li = "";
            for (var i = begin; i < end && i < data.length; i++) {
                imgNameWithPattern = data[i].split(';')[1];  // a.png
                imgName = imgNameWithPattern.split('.')[0]  // a
                imageSize = data[i].split(';')[0]; // length.height
                imageX = imageSize.split('.')[0]; //  length
                imageY = imageSize.split('.')[1]; // height

							  cdn_url       = data[i].split(';')[2]; // 原图 cdn url
							  small_cdn_url = data[i].split(';')[3]; // 缩略图 cdn url

                li += '<div class="card" style="width:' + imageWidth + 'px" >' +
                        '<div class="ImageInCard" style="height:'+ imageWidth * imageY / imageX + 'px">' +
                            '<a data-fancybox="gallery" href="' + cdn_url + '" data-caption="' + imgName + '" title="' +  imgName + '">' +
                                '<img data-src="' + small_cdn_url + '" src="' + small_cdn_url + '" data-loaded="true">' +
                            '</a>' +
                        '</div>' +
                      '</div>'
            }
            $(".MyGrid").append(li);
            this.minigrid();
        },
        minigrid: function() {
            var grid = new Minigrid({
                container: '.MyGrid',
                item: '.card',
                gutter: 12
            });
            grid.mount();
            $(window).resize(function() {
                grid.mount();
            });
        }
    }
    photo.init();
}

或者你可以直接在我的博客上找到: rebootcat.com/mygrid.js

wget https://rebootcat.com/js/mygrid.js -O source/js/mygrid.js

新建图片信息文件

我们再次回到 photos 目录,创建文件 photoslist.json:

vim source/photos/photoslist.json

然后输入如下的内容:

[
  "1080.1920;WechatIMG114.jpeg;https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting/rebootcat/photowall/cat/WechatIMG114.jpeg;https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting/rebootcat/photowall/cat/WechatIMG114_small.jpeg",
  "3024.4032;WechatIMG25834.jpeg;https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting/rebootcat/photowall/cat/WechatIMG25834.jpeg;https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting/rebootcat/photowall/cat/WechatIMG25834_small.jpeg"
]

OK, 到现在应该你能从博客上看到这两张图片了:

hexo s -g

本地测试一下,如果你能看到在博客的 摄影 页面看到这两张图片,那么说明你的配置没问题,你可以进行接下来的操作了;如果你不能正确显示,说明前面的步骤出了问题,自己研究调试一下;如果你还不能解决,欢迎联系我。

使用 python 脚本生成 photoslist.json

上面可以看到,photoslist.json 存放的是图片的信息,mygrid.js 解析 photoslist.json 这个文件,然后在 photos 页面添加 dom.

所以核心的部分在于 photoslist.json 文件,我们可以分析下这个文件:

1080.1920;WechatIMG114.jpeg;https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting/rebootcat/photowall/cat/WechatIMG114.jpeg;https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting/rebootcat/photowall/cat/WechatIMG114_small.jpeg

photoslist.json 保存的是一个 list,list 中每一行是一张图片的信息,包括原始图片大小、文件名、原始图片cdn链接、缩略图cdn链接

前面已经提到,我们的图片是使用了 github 作为图床(仓库),然后使用 jsdelivr 进行 cdn 加速。所以我们应该准备好图片文件,然后上传到仓库。

新建 github 仓库,用来存放图片文件

在 https://github.com 上创建图片仓库。

当仓库容量超过 50MB 之后需要重新再新建一个仓库

本地克隆仓库,然后把图片放入仓库,上传(这里以我的仓库为例)

git clone git@github.com:smaugx/MyblogImgHosting_2.git blogimg_2
cd blogimg_2

# put some image in this dir

...
git push

生成 photoslist.json 文件

编写 python 脚本或者直接从我的网站下载:

wget https://rebootcat.com/js/phototool.py  -O phototool.py

脚本如下:

#!/usr/bin/env python
# -*- coding:utf8  -*-

import os
import glob
from PIL import Image, ExifTags
import json

config = {
        # github 存储图片的仓库(本地仓库基准目录)
        'github_img_host_base': '/Users/smaug/blogimg_2',
        # 会对这个目录下的所有文件夹进行遍历,相同目录生成_samll 的 缩略图
        'img_path':             '/Users/smaug/blogimg_2/rebootcat/photowall',
        # cdn 前缀
        'cdn_url_prefix':       'https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting_2',
        # hexo 博客存放 photos 信息的 json 文件
        'photo_info_json':      '/Users/smaug/blog_rebootcat/source/photos/photoslist.json',
        }

# 压缩图片到 90%(目的是为了移除一些gps 等信息,并非真的为了压缩)
def compress_img(img_path, rate = 0.99, override = False):
    support_ftype_list = ['png', 'PNG', 'jpeg', 'JPEG', 'gif', 'GIF', 'bmp']
    sp_img = img_path.split('.')
    if not sp_img or sp_img[-1] not in support_ftype_list:
        print("not support image type:{0}", img_path)
        return False
    sp_img = img_path.split('/')
    if not sp_img:
        print("please give the right image path:{0}", img_path)
        return False
    img_full_name = sp_img[-1]
    img_name = img_full_name.split('.')[0]
    img_type = img_full_name.split('.')[1]
    img_path_prefix = img_path[:-len(img_full_name)]

    # 覆盖原图或者另存为
    compress_img_path = ''
    if override:
        compress_img_path = img_path
    else:
        compress_img_path = '{0}{1}_com.{2}'.format(img_path_prefix, img_name, img_type)

    img = Image.open(img_path)
    try:
        for orientation in ExifTags.TAGS.keys() :
            if ExifTags.TAGS[orientation]=='Orientation' : break
        exif=dict(img._getexif().items())
        if   exif[orientation] == 3 :
            img=img.rotate(180, expand = True)
        elif exif[orientation] == 6 :
            img=img.rotate(270, expand = True)
        elif exif[orientation] == 8 :
            img=img.rotate(90, expand = True)
    except Exception as e:
        print("catch exception:{0}",e)

    try:
        original_size = img.size
        length = original_size[0]
        height = original_size[1]
        new_length = int(length * rate)
        new_height = int(height * rate)
        print("originla length:{0} height:{1}", length, height)
        print("after compress length:{0} height:{1}", new_length, new_height)
        img = img.resize((new_length, new_height), Image.ANTIALIAS)
        img.save(compress_img_path, img_type)
        print("save compress img {0}".format(compress_img_path))
        return True
    except Exception as e:
        print("catch exception:{0}",e)

    return False


# 对 img_path 目录下的文件夹递归生成缩略图保存到同目录下
def thumbnail_pic(github_img_host_base, img_path, cdn_url_prefix):
    # 删除最后一个 '/'
    if img_path[-1] == '/':
        img_path = img_path[:-1]
    if github_img_host_base[-1] == '/':
        github_img_host_base = github_img_host_base[:-1]
    if cdn_url_prefix[-1] == '/':
        cdn_url_prefix = cdn_url_prefix[:-1]

    photo_info_list = []

    for item in os.listdir(img_path):
        print(item)
        abs_item = os.path.join(img_path, item)
        if os.path.isdir(abs_item): # sub-dir
            sub_img_path = abs_item
            print("cd dir:{0}".format(sub_img_path))
            sub_photo_info_list = thumbnail_pic(github_img_host_base, sub_img_path, cdn_url_prefix)
            photo_info_list.extend(sub_photo_info_list)
        else: # file
            ftype = item.split('.')
            if not ftype or len(ftype) != 2:
                print("error: invalid file:{0}".format(item))
                continue
            fname = ftype[0]  # a.png -> a
            ftype = ftype[1]  # a.png -> png
            support_ftype_list = ['png', 'PNG', 'jpeg', 'JPEG', 'gif', 'GIF', 'bmp']
            if ftype not in support_ftype_list:
                print("error: file type {0} not support, only support {1}".format(ftype, json.dumps(support_ftype_list)))
                continue

            abs_file = abs_item
            if item.find('_small') != -1: # 这是缩略图
                continue
            small_file = '{0}_small.{1}'.format(fname, ftype)
            abs_small_file = os.path.join(img_path, small_file)  # 缩略图绝对路径
            if os.path.exists(abs_small_file):
                # 对应的 _small 缩略图已经存在
                continue

            compress_status = compress_img(abs_file, 0.9, True)
            if not compress_status:
                print("compress_img fail:{0}", abs_file)
                continue

            im = Image.open(abs_file)
            original_size = im.size
            length = original_size[0]
            height = original_size[1]
            m = int(float(length) / 200.0)  # 计算缩小比例 (缩略图限制 200 长度)
            new_length = int(float(length) / m)
            new_height = int(float(height) / m)
            im.thumbnail((new_length, new_height))  # 生成缩略图
            im.save(abs_small_file, ftype)  # 保存缩略图
            print("save thumbnail img {0}".format(abs_small_file))

            relative_file       = abs_file[len(github_img_host_base) + 1:] # 计算相对路径,用来拼接 cdn
            relative_small_file = abs_small_file[len(github_img_host_base) + 1:]

            cdn_url_file        = '{0}/{1}'.format(cdn_url_prefix, relative_file)
            cdn_url_small_file  = '{0}/{1}'.format(cdn_url_prefix, relative_small_file)

            # 格式: 690.690;8.png;http://cdn_file_url;http://cdn_small_file_url;
            line = '{0}.{1};{2};{3};{4}'.format(length, height, item, cdn_url_file, cdn_url_small_file)
            photo_info_list.append(line)

    # end for loop
    print('dir:{0} Done!'.format(img_path))
    return photo_info_list


if __name__=='__main__':
    github_img_host_base = config.get('github_img_host_base')
    img_path             = config.get('img_path')
    cdn_url_prefix       = config.get('cdn_url_prefix')
    photo_info_json      = config.get('photo_info_json')

    photo_info_list     = []
    photo_info_list_has = []
    photo_info_list = thumbnail_pic(github_img_host_base, img_path, cdn_url_prefix)

    if os.path.exists(photo_info_json):
        with open(photo_info_json, 'r') as fin:
            photo_info_list_has = json.loads(fin.read())
            fin.close()

    photo_info_list_has.extend(photo_info_list)  # 追加此次新增的 photo info

    with open(photo_info_json, 'w') as fout:
        fout.write(json.dumps(photo_info_list_has, indent = 2))
        print("save photo_info_list to {0}".format(photo_info_json))
        fout.close()

    print("\nAll Done")

这里重点需要关注的是:

config = {
        # github 存储图片的仓库(本地仓库基准目录)
        'github_img_host_base': '/Users/smaug/blogimg_2',
        # 会对这个目录下的所有文件夹进行遍历,相同目录生成_samll 的 缩略图
        'img_path':             '/Users/smaug/blogimg_2/rebootcat/photowall',
        # cdn 前缀
        'cdn_url_prefix':       'https://cdn.jsdelivr.net/gh/smaugx/MyblogImgHosting_2',
        # hexo 博客存放 photos 信息的 json 文件
        'photo_info_json':      '/Users/smaug/blog_rebootcat/source/photos/photoslist.json',
        }

简单解释一下这个脚本:

  • github_img_host_base: 这个目录也就是本地的仓库目录,绝对路径(上面克隆的仓库对应的本地文件夹路径)
  • img_path: 我单独新建了 rebootcat/photowall 目录存放瀑布流图片,对应本地的路径
  • cdn_url_prefix:jsdelivr cdn url 前缀,只需要更改成你自己的github 用户名以及仓库名
  • photo_info_json: photoslist.json 路径

上面几个参数一定要配置对了。

那么简单解释一下脚本的功能:

脚本会递归的查找 img_path 目录下的图片,然后进行一定的压缩(99%),这里的压缩目的并非真的是压缩,而是为了去除一些敏感信息,比如 GPS 信息。注意这里会覆盖掉原始图片。然后会生成图片的缩略图,同时根据上面的几个配置参数,生成两个 cdn url,一个对应的是原始图片的 cdn url,一个是缩略图的 cdn url.

然后执行:

python phototool.py

脚本执行完,就会增量生成 photoslist.json,可以先打开检查下对不对,或者把里面的 cdn url 复制出来从浏览器看能不能访问。

注意需要把本地图片仓库推送到远程

这个 phototool.py 脚本你可以随便放在哪里,当你更新图片之后重新执行一遍就可以了。当然你也可以像我一样,跟网站源码直接放一起,所以你可以看到,我直接放到了 js 目录。

更新图片

把新图片放到本地仓库,然后执行:

python phototool.py

检查一下 photoslist.json 文件对不对,然后发布博客:

hexo d -g

发布之后,记得把本地图片仓库推送到远端,不然 jsdelivr 无法访问到。

至此,一个相册瀑布流就制作完成了!

The End

由于我是采用回忆的方式来写的博文,所以文中可能会有一些小的修改或者配置我忽略了,不过问题不大,大家如果碰到问题了可以自行研究一下,能解决的。

采用 github 作为图床来存放大量的瀑布流图片墙,方案是没问题的,只不过可能由于仓库容量的限制,需要在 github 上构建多个图片仓库。

对于我来说,github 图片仓库主要用来存放博文中涉及到的图片。至于图片墙,我再另想办法吧。

Blog:

  • rebootcat.com

  • email: linuxcode2niki@gmail.com

2020-09-19 于杭州
By 史矛革

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Hexo Next 博客添加相册瀑布流 的相关文章

  • apache poi 和EasyExcel 的使用

    文章目录 apache poi隐藏sheet做下拉列表 EasyExcel超链接跳转sheet页自定义类型转换隐藏sheet做下拉列表 apache poi 官方文档 xff1a https poi apache org component
  • Spring Data JPA 中 findById、getOne、findOne 的区别

    转载文献 xff1a Spring Data JPA 中 findById getOne findOne 的区别 文章目录 1 findById 方法2 getOne 方法3 findOne 方法4 总结 1 findById 方法 spa
  • git 代码统计

    查询指定分支指定人指定时间段的代码 git log dev span class token operator span billows span class token operator span author span class to
  • ftpClient.retrieveFileStream导致FTPClient的后面操作失败

    问题代码 xff1a FTPFile fs 61 ftpClient span class hljs preprocessor listFiles span span class hljs comment span for FTPFile
  • jenkins自动化搭建测试环境

    简述 概念 Jenkins是一个功能强大的应用程序 xff0c 允许持续集成和持续交付项目 xff0c 无论用的是什么平台 这是一个免费的源代码 xff0c 可以处理任何类型的构建或持续集成 集成Jenkins可以用于一些测试和部署技术 J
  • java 使用CA认证

    本文转自 xff1a 数字证书简介及Java编码实现 CA认证原理以及实现 java CA证书制作和代码中使用 名词大意 秘钥库 xff1a 存储了公钥和私钥信息 xff0c 一个秘钥库可以存储多对的密钥对 xff08 密钥对可以理解为就是
  • 关于Java编程实现n个小球涂色的问题

    声明 xff0c 本文内容为本人原创 xff0c 可以转载 xff0c 但请注明出处 xff0c 否则将追究法律责任 xff0c 谢谢合作 xff01 把这n个小球排成一线 xff0c 然后给这n个小球涂色 xff0c 假设共有三种颜料 x
  • 获取SqlServer数据库数据,转成JSON对象

    研究了很多天 xff0c 终于能够把数据库获取的数据成功转换成jsonObject对象 重要代码 xff1a NoticeDaoImpl java try conn3 61 DBUtil getConnection st3 61 conn3
  • 图床实打实大师

    实打实大师大师
  • STM32程序下载成功但是不运行

    调试stm32程序 xff0c 发现如下问题 xff1a STM32编写程序 xff0c 当程序中没有 sd 卡部分时 xff0c 程序正常下载 xff0c 正常运行 当程序中运行 sd 卡加文件系统时程序能正常下 载但是不能正常的运行 发
  • 基于Kurento的WebRTC移动视频群聊解决方案

    说在前面的话 xff1a 视频实时群聊天有三种架构 xff1a Mesh架构 xff1a 终端之间互相连接 xff0c 没有中心服务器 xff0c 产生的问题 xff0c 每个终端都要连接n 1个终端 xff0c 每个终端的编码和网络压力都
  • 阿里云动态域名解析

    1 如果不想用花生壳这类的工具 xff0c 可以利用阿里云的动态域名解析api来动态解析域名 2 原理就是定期检查ip是否改变 xff0c 如果改变就利用api重新解析域名 一 pom xml span class hljs tag lt
  • glibc fread函数源码剖析

    最近看APUE xff0c 看到了fread函数 xff0c 就把之前想分析的一个函数借这个机会研究一下 先写个程序 xff0c 调用一下fread函数 include lt stdio h gt int main char strbuf
  • 约数和公式 及其 证明 。。小学奥数啊 摔~

    为了便于理解 xff0c 举个具体例子来说明 72共有多少个不同的约数 xff1f 所有约数的和是多少 xff1f 分析 xff1a 我们已经学过了怎样求一个数的约数 xff0c 可以写出72的全部约数有 xff1a 1 xff0c 2 x
  • codeforces上的名字颜色和codeforces打比赛转载

    小编在网络上发现很多网友对codeforces名字颜色的关注度比较高 xff0c 小伙伴们现在肯定也是对与codeforces名水平的内容非常的感兴趣了 xff0c 都想要了解具体的codeforce id的颜色到底是讲些什么内容 xff0
  • 程序 格式

    阅读性格式有 出名的json和xml xff0c 各有优势 xff0c 这类格式 xff0c 有一个优势 xff0c 就是支持无限嵌套 xff0c 很多大型程序都使用的xml做配置解析 xff0c 如android xff1b json的话
  • Cocos2d-x开发教程-马宗扬-专题视频课程

    立即学习 https edu csdn net course play 11109 247724 粘包 发多次 只收一次 缺包 大数据包 拆成多个包发送 由于网络延迟 每次收包数据不完整
  • linux下trash替换掉rm

    linux中的删除与回收站 windows下的文件删除后就放到了回收站里 xff0c linux下的文件删除则分两种情况 xff1a 界面删除 xff1a 在文件夹界面右击 删除 xff0c 则该文件将移动到回收站 xff0c 与windo
  • PostgreSQL将查询结果输出到新表

    与子查询不同 xff0c 这里我们要将查询结果输出到新表 方式1 新建表后插入查询结果 通过CREATE TABLE新建表films通过INSERT将查询结果插入到新表 INSERT INTO films SELECT FROM tmp f
  • mysql数据库备份及恢复

    还原一个数据库 mysql h localhost u root p123456 www 备份一个数据库 mysqldump h localhost u root p123456 www gt d www2008 2 26 sql 其中WW

随机推荐