Hexo Next 博客添加相册瀑布流
原文: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: / || homeabout: /about/ || usertags: /tags/ || tagscategories: /categories/ || tharchives: /archives/ || archive#schedule: /schedule/ || calendar#sitemap: /sitemap.xml || sitemap#commonweal: /404/ || heartbeatguestbook: /guestbook || fas fa-commentsphotos: /photos || fas fa-camera-retrowiki: /wiki/ || wikipedia-w
完成之后還需要修改一下這個文件:
vim themes/next/languages/zh-CN.yml
找到 menu 項,加入如下一行:
photos: 攝影
比如我的是這樣的:
menu:home: 首頁archives: 歸檔categories: 分類tags: 標簽about: 關于search: 搜索schedule: 日程表sitemap: 站點地圖commonweal: 公益 404guestbook: 留言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.pngimgName = imgNameWithPattern.split('.')[0] // aimageSize = data[i].split(';')[0]; // length.heightimageX = imageSize.split('.')[0]; // lengthimageY = imageSize.split('.')[1]; // heightcdn_url = data[i].split(';')[2]; // 原圖 cdn urlsmall_cdn_url = data[i].split(';')[3]; // 縮略圖 cdn urlli += '<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 jsonconfig = {# 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 Falsesp_img = img_path.split('/')if not sp_img:print("please give the right image path:{0}", img_path)return Falseimg_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_pathelse: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' : breakexif=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.sizelength = 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 Trueexcept 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-dirsub_img_path = abs_itemprint("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: # fileftype = item.split('.')if not ftype or len(ftype) != 2:print("error: invalid file:{0}".format(item))continuefname = ftype[0] # a.png -> aftype = ftype[1] # a.png -> pngsupport_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)))continueabs_file = abs_itemif item.find('_small') != -1: # 這是縮略圖continuesmall_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 縮略圖已經存在continuecompress_status = compress_img(abs_file, 0.9, True)if not compress_status:print("compress_img fail:{0}", abs_file)continueim = Image.open(abs_file)original_size = im.sizelength = 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:] # 計算相對路徑,用來拼接 cdnrelative_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 loopprint('dir:{0} Done!'.format(img_path))return photo_info_listif __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 infowith 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 史矛革
總結
以上是生活随笔為你收集整理的Hexo Next 博客添加相册瀑布流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Scons构建C++项目
- 下一篇: 迁移博客到香港虚拟空间