Hexo中添加类QQ空间旅游相册页面

身为一个喜欢拍照的猿,博空中怎么可能少了相册呢。
Hexo博客自身没有相册功能,网上找了一些资料都是瀑布流的样式较多,这不是我想要的,于是借鉴了 水寒写的添加瀑布流相册 自己写了一个。先上几张图看一下效果(ps:css现学现卖拼凑出为的,页面效果有待优化哈,完整效果可以去我博客看过愙相册)
Hexo中添加类QQ空间旅游相册页面
文章图片
相册目录页 Hexo中添加类QQ空间旅游相册页面
文章图片
单个相册 1. 创建页面和导航栏 进入博客根目录,用命令hexo new page photos新建相册页面 ,这样会在 /source/ 下创建 photos/index.md,在其中添加 type: picture。
在主题配置文件themes\next\_config.yml中对应位置 menu 里添加 photos: /photos || camera-retro ,这样生成后就有导航栏菜单了。
有了语言,还要到语言对应的目录中修改。
2. json 文件处理图片信息 2.1 文件分析
为了生成多级的相册,这里的目录分为二级。

  • 第一级目录是相册,目录名就是相册名。
  • 第二级目录是分类目录,可以把同一时间或同一主题的放在一起,类似于QQ空间旅游相册中的地点分类。
  • 如果没有第二级目录,则以相片的日期分类。
  • 二级目录文件名如果带时间,如:2020-02-02.分类1。 拆分时间和目录名;如果不带时间,则用目录创建时间为分类时间,目录名为分类名
目录结构如下:
相册根目录 ├─相册1 ││图片1.jpg ││图片2.jpg ││ │└─2020-02-02.分类1 │图片3.jpg │图片4.jpg │ └─相册2 图片5.jpg 图片6.jpg

我们需要生成的目录json数据为:
[{ "mini": "/min", "dir": "相册2", "date": "2020-03-14", "num": 2, "cover": { "url": "/min/相册2/图片6.jpg", "width": 1024, "height": 768, } "photos": [{ "date": "2020-02-12", "name": "", "photo": [{ "name": "图片5", "url": "/相册2/图片5.jpg" },{ "name": "图片6", "url": "/相册2/图片6.jpg" }] }] },{ "mini": "/min", "dir": "相册1", "date": "2020-03-14", "num": 4, "cover": { "url": "/min//相册1/图片2.jpg", "width": 1024, "height": 768, } "photos": [{ "date": "2020-02-02", "name": "分类1", "photo": [{ "name": "图片3", "url": "/相册1/2020-02-02.分类1/图片3.jpg" },{ "name": "图片4", "url": "/相册1/2020-02-02.分类1/图片4.jpg" }] },{ "date": "2020-02-27", "name": "", "photo": [{ "name": "图片1", "url": "/相册1/图片1.jpg" },{ "name": "图片2", "url": "/相册1/图片2.jpg" }] }] }]

2.2 编写处理脚本
在博客根目录的 /scripts/ 文件夹下新建一个 photo.js 文件,内容如下
const fs = require('fs') const path = require('path') const util = require('util') const images = require("images"); // 默认相册图片根目录 const defaultRootPath = '/source/photos'; // 默认是否开启缩略图 const defaultUseMini = true; // 默认缩略图存放目录 const defaultMiniPath = '/min'// 是否在hexo g状态 const isInHexo = "undefined" != typeof hexo; if (isInHexo && !hexo.theme.photo.enable ) return; const promisifyReaddir = util.promisify(fs.readdir) const promisifyStat = util.promisify(fs.stat)// 图片格式 const photoExtname = ['.jpg', '.bmp', '.png', '.gif']// 相册目录相对博客根目录,引用主题里的 photoPath 目录,如果没有则用 /source/photos const rootPath = ((isInHexo && hexo.config.photo.path) ? hexo.photo.config.path : defaultRootPath); // 相册绝对路径 const photoPath = path.resolve(__dirname, '..' + rootPath); // 是否使用缩略图,主题中存在 const useMini = (isInHexo && hexo.config.photo.mini.enable) ? hexo.photo.mini.enable : defaultUseMini; // 缩略图目录 const minPath = useMini ? ((isInHexo && hexo.config.photo.mini.path) ? hexo.config.photo.mini.path : defaultMiniPath) : ''; // 相册json数据 var photoArray = new Array(); async function init() { // 是否成生缩略图 if (useMini) { deleteFolder(photoPath + minPath); }// 获取相册数据 await getPhotoData()// 相册内按时间排序 for (let i = 0; i < photoArray.length; i++) { if (photoArray[i].photos == null) { continue; }photoArray[i].photos = photoArray[i].photos.sort(function (a, b) { return a.date > b.date ? 1 : -1 })console.log(photoArray[i].date, photoArray[i].photos[0].date); // 刷新创建时间,不晚于最早的分类时间 if (photoArray[i].date > photoArray[i].photos[0].date) { photoArray[i].date = photoArray[i].photos[0].date } } photoArray = photoArray.sort(function (a, b) { return a.date > b.date ? -1 : 1 }) console.log(JSON.stringify(photoArray))// 写入jsonp数据 fs.writeFile(photoPath + path.sep + 'photo.jsonp', 'callback('+JSON.stringify(photoArray)+')', function (err) { if (err) { return console.error(err); } console.log("数据写入成功!"); }); }// 获取相册数据 async function getPhotoData(proPath = '') { const dir = await promisifyReaddir(photoPath + proPath)// 当前相册数据 var photoData = https://www.it610.com/article/null; // 相册名使用相册目录名 var photoName = null; // 存在分类目录, //分类目录没带时间,则用目录创建时间为分类时间,目录名为分类名 //分类目录存在时间,如:2020-02-02.分类1。 拆分时间和目录名 // 分类名。 var categoryName =""; // 分类时间。 var categoryDate = null; // 相册只有一层,非根目录相册直接加入到对应一级目录中 if (proPath) { // 获取最后一级目录名 let index = proPath.split(path.sep).join('/').lastIndexOf("\/"); //兼容两个平台 并获取最后位置index let lastDir = proPath.substring(index + 1, proPath.length); //截取获得结果// 获取第一次目录名,即 相册名 index = proPath.substring(1).split(path.sep).join('/').indexOf("\/"); photoName = index > 0 ? proPath.substring(1, index + 1) : proPath.substring(1); // 最后一级目录与相册名相等,说明当前目录是相册目录,否当前目录为分类目录 if (lastDir != photoName) { // 拆分目录名 var tmp = lastDir.split('.'); if (tmp.length > 1) { categoryDate = new Date(tmp[0]).toISOString().substring(0, 10); categoryName = lastDir.substring(tmp[0].length + 1); }// 不存在时间格式或时间格式不对,都认为无效时间,以分类目录创建时间处理 if (categoryDate == null) { categoryName = lastDir; const stat = await promisifyStat(`${photoPath}${proPath}`) categoryDate = stat.mtime.toISOString().substring(0, 10); } }for (let i = 0; i < photoArray.length; i++) { if (photoArray[i].dir === photoName) { photoData = https://www.it610.com/article/photoArray[i]; if (!photoData.photos) { photoData.photos = new Array() } break; } } }// 遍历目录中文件和文件夹 for (let i = 0; i < dir.length; i++) { const stat = await promisifyStat(path.resolve(photoPath + proPath, dir[i]))if (stat.isFile()) { // 根目录文件不处理,只处理子级以以下的图片文件 if (proPath) {// 匹配图片后缀 var ext = path.extname(dir[i]).toLowerCase(); if (photoExtname.indexOf(ext) != -1) { var name = path.basename(dir[i], ext) var date = stat.mtime.toISOString().substring(0, 10)// 相册分类数据 var photoCategory; for (let j = 0; j < photoData.photos.length; j++) { // 根据时间和分类获取现在分类数据 if (photoData.photos[j].date == categoryDate && photoData.photos[j].name == categoryName) { photoCategory = photoData.photos[j]; if (!photoCategory.photo) { photoCategory.photo = new Array(); } break; } }// 不存分类则创建 if (!photoCategory) { photoCategory = { date: categoryDate ? categoryDate : date, name: categoryName ? categoryName :"", photo: new Array() } photoData.photos.push(photoCategory); }// 插入相片 let img = images(photoPath + proPath + path.sep + dir[i]); photoCategory.photo.push({ name: name, width: img.width(), height: img.height(), url: proPath + path.sep + dir[i] })// 更新相册信息 photoData.num++; photoData.cover={ width: img.width(), height: img.height(), url: minPath + proPath + path.sep + dir[i] }if (useMini) { var outPath = path.resolve(photoPath + minPath + path.sep + proPath + path.sep) + path.sep; checkDirExist(outPath); // 宽度小于1024的不缩放,大于1024的以宽度等比缩到1024 let width = img.width(); let height = img.height(); if(width>1024){ height = 1024 * height / width; width = 1024; } img.resize(width,height) .save(outPath + path.sep + dir[i],{ quality: 50//保存图片到文件,图片质量为50 }); } } } } else if (stat.isDirectory()) { // 缩略图目录忽略 if (path.sep + dir[i] === minPath) { continue; }if (!proPath) { // 根目录时创建相册 var photoData = https://www.it610.com/article/{ mini: minPath, dir: dir[i], date: stat.mtime.toISOString().substring(0, 10), num: 0, photos: new Array() } photoArray.push(photoData) } // 递归子目录 await getPhotoData(proPath + path.sep + dir[i]) } } }// 删除目录 function deleteFolder(filePath) { const files = [] if (fs.existsSync(filePath)) { const files = fs.readdirSync(filePath) files.forEach((file) => { const nextFilePath = filePath + path.sep + file const states = fs.statSync(nextFilePath) if (states.isDirectory()) { //recurse deleteFolder(nextFilePath) } else { //delete file fs.unlinkSync(nextFilePath) } }) fs.rmdirSync(filePath) } }// 判断目录是否存在,否存创建 function checkDirExist(folderpath) { const pathArr = folderpath.split(path.sep); let _path = ''; for (let i = 0; i < pathArr.length; i++) { if (pathArr[i]) { _path += `${pathArr[i]}${path.sep}`; if (!fs.existsSync(_path)) { fs.mkdirSync(_path); } } } }init()

在博客配置_config.yml中添加
photo: enable: false path: /source/photos/ mini: enable: true path: /mini

下载图片nodejs的插件
npm install images

配置说明
  • photo.enable: 是否在hexo g时编译, 不是相册不是很次都有更新,也不需要每次遍相册的时候都来编译这个,所以加了一个标志控制使用 hexo g编译时编译相册,false-不编译true-编译
  • photo.path: 相册图片根目录, 如果不通过hexo编译,可以配置js脚本中的defaultRootPath
  • photo.mini.enable: 是否开启图片压缩生成缩略图。如果不通过hexo编译,可以配置js脚本中的defaultUseMini 配置
  • photo.mini.path: 缩略图生成的相对路径。如果开启了缩略图功能,这项不能添空,不然图片会被覆盖。 如果不通过hexo编译,可以配置js脚本中的defaultMiniPath
开启photo.enable: true直接hexo g,或者在根目录执行node script/photo.js就能生成json数据了,json数据在相册跟目录
3. 编写页面处理js脚本 在 /themes/next/source/js/src/ 目录下创建一个 photo.js,内容如下:
const imgRoot = '/photos' photo = { // 当前显示类型、dir-目录,photo-照片 type: 'dir',// 相册索引 photoIndex: 0,// 分类索引 categoryIndex: 0,// 相册数据数组 photoArray: null, //offset 每次加载照片数量,以分类为一个单位 offset: 10,init: function () { var that = this; if (this.photoArray == null) { //这里设置的是刚才生成的 jsonp 文件路径 // json文件如果用代码仓库做图床会出现跨域问题 $.ajax({ type:"GET", url:imgRoot + "/photo.jsonp", dataType:"jsonp", jsonpCallback:"callback", success:function(data){ photoArray = data; that.randerDir(); } }); } else { this.randerDir(); }}, randerDir: function () { // 显示相册目录 let data = https://www.it610.com/article/photoArray; this.type ='dir'; $(".photo-div").empty(); let li = ''; for (let i = 0; i < data.length; i++) { let cover = data[i].cover; // 封面 let width = 275; // 相册目录定宽 let height = width * cover.height / cover.width; li += '' + '' + 'Hexo中添加类QQ空间旅游相册页面
文章图片
' + '' + '' + data[i].dir + '' + '' } li += ''; $(".photo-div").append(li); this.minigrid(); }, render: function (page) { if (!photoArray || photoArray.length <= this.photoIndex) { return; }// 相册数据 var photoData = https://www.it610.com/article/photoArray[this.photoIndex]; this.type ='photo'; // 插入相册标题 if (page == 1) { let li = '' + '' + photoData.dir + '返回相册'+'' + ' 创建于' + photoData.date + '/ 共' + photoData.num +' 张' + '' $(".photo-div").append(li); }let showCount = 0; for (let i = 0; i < photoData.photos.length; i++) { // 分类 photoCategory = photoData.photos[i]; if (i < this.categoryIndex) { // 跳过 continue; } else if (page <= i && showCount>=this.offset) { // 结束 break; } else { // 加入分类信息 this.categoryIndex++; let li = '' +photoCategory.date + (photoCategory.name?'| ':'')+photoCategory.name+ '' + ''li += '' $(".photo-div").append(li); for (let j = 0; j < photoCategory.photo.length; j++) { showCount++; // 相片数据 let data = https://www.it610.com/article/photoCategory.photo[j]; let imgNameWithPattern = data.url; let imgName = data.name; let li ='' + 'Hexo中添加类QQ空间旅游相册页面
文章图片
'$('.photo-category-box-' + this.categoryIndex).append(li); $('.photo-category-box-' + this.categoryIndex).justifiedGallery({ rowHeight: 300, margins: 4, randomize: true }); } } }$(".photo-div").lazyload(); this.minigrid(); }, minigrid: function () {// 使用 minigrid动态布局相册 var grid = null; if (this.type == "dir") { grid = new Minigrid({ container: '.photo-box', item: '.photo-box-item', gutter: 12 }); grid.mount(); }var that = this; // 监听窗口大小事件 $(window).resize(function () { if (that.type == "dir") { grid.mount(); } // 计算宽度是否加载新相片 that.loading(); }); // 监听滚动事件 $(window).on('scroll', function () { // 计算宽度是否加载新相片 that.loading(); }); // 相册点击事件 $(".photo-box-item-click").bind("click", function () { if (!photoArray || photoArray.length <= this.id) { return; }$(".photo-div").empty(); that.photoIndex = this.id; that.categoryIndex = 0; that.render(1) }); // 返回相册事件 $(".photo-back").bind("click", function () { if (!photoArray || photoArray.length <= this.id) { return; }$(".photo-div").empty(); that.randerDir(); return false; }); },// 判断滚动长度大于时加载新的 loading: function () { if (this.type != 'photo') { return; }var scrollTop = $(window).scrollTop(); if(scrollTop+$(window).height()>$(".photo-div").height()){ this.render(this.categoryIndex+1); } } } photo.init();

这里imgRoot 配置相册相对网站的相对路径,也可以用图床填写图床的目录。
我用的是coding代码仓库为图床,这里可以配置
const imgRoot = '//guoke3915.coding.net/p/guoke3915/d/img/git/raw/master/photos'

4. 配置主题 这里用到了几个第三方的布局js插件,所以在主题中配置一下。这里都引用了cdn的代码,也可以自己下过来放到自己的网站上。
  • minigrid:等宽瀑布布局,用于相册目录布局
  • justifiedGallery: 画廊式图片布局,用于相册照片显示
  • fancybox:照片展示插件
4.1 引用js文件 修改/themes/next/layout/_scripts/commons.swig文件,在最后加入代码
{% if page.type ==='picture' %}{% endif %}

4.2 引用css文件 修改themes\next\layout\_partials\head.swig文件,在最后添加代码
{% if page.type ==='picture' %} {% endif %}

4.3 开启fancybox 打开主题配置文件themes\next\_config.yml
找到fancybox:设置为true
找到vendors:添加cdn地址
vendors:fancybox: //cdn.jsdelivr.net/gh/zngw/cdn/fancybox/jquery.fancybox.min.js fancybox_css: //cdn.jsdelivr.net/gh/zngw/cdn/fancybox/jquery.fancybox.min.css

5 修改CSS样式 找到themes\next\source\css\_custom\custom.styl文件,在后面添加
.photo-box { width: 100%; max-width: 1040px; margin: 0 auto; text-align: center; }.photo-box-item { overflow: hidden; transition: .3s ease-in-out; border-radius: 8px; background-color: #ddd; }.photo-box-item-img { } .photo-box-item-img img { transition: opacity 500ms ease-in }.photo-box-item .caption { visibility: hidden; position: absolute; bottom: 0; padding: 5px; background-color: #000000; left: 0; right: 0; margin: 0; color: white; font-size: 12px; font-weight: 300; font-family: sans-serif; opacity: 0.7; } /* 鼠标移动上去后显示提示框 */ .photo-box-item:hover .caption { visibility: visible; }.photo-box-item-check { } .photo-box-item-check img { transition: opacity 500ms ease-in }.photo-category-card { overflow: hidden; transition: .3s ease-in-out; border-radius: 8px; background-color: #ddd; }.photo-category-card-img { } .photo-category-card-img img { transition: opacity 500ms ease-in }.photo-category-card .caption { visibility: hidden; position: absolute; bottom: 0; padding: 5px; background-color: #000000; left: 0; right: 0; margin: 0; color: white; font-size: 12px; font-weight: 300; font-family: sans-serif; opacity: 0.7; } /* 鼠标移动上去后显示提示框 */ .photo-category-card:hover .caption { visibility: visible; }.photo-title-text { line-height: 54px; background-color: #479ac7; color: white; font-size: 24px; }.photo-title-desc { line-height: 20px; background-color: #479ac7; color: white; font-size: 12px; }.photo-back{ line-height: 34px; margin-top: 10px; position: absolute; display: inline-block; right:10px; padding: 10px 10px; border-radius: 4px; background-color: #63b7ff; color: #fff; cursor: pointer; } .photo-back:hover{ background-color: #99c6ff; } .photo-category-text { line-height: 30px; background-color: #f1fafa; color: #336699; ; font-size: 18px; }

6. 部分图片禁用fancybox 相册目录中的图片需要点击事件,所以,这里的图片需要禁用fancybox。
加一个nofancybox类标签,
修改一下theme\next\source\js\src\utils.js文件
找到wrapImageWithFancyBox: function函数,在循环里加一句if ($(this).hasClass('nofancybox')) return;
wrapImageWithFancyBox: function () { $('.content img') .not('[hidden]') .not('.group-picture img, .post-gallery img') .each(function () { if ($(this).hasClass('nofancybox')) return; // 就加这一句就可以了 var $image = $(this); var imageTitle = $image.attr('title'); var $imageWrapLink = $image.parent('a');

然后在用的时候,img标签中加入 'Hexo中添加类QQ空间旅游相册页面
文章图片
'
7. 放入相册 在第一步创建好的 /source/photos/index.md 文档中编辑好自己需要的相册页面内容,在需要放置相册的位置加入以下内容即可
--- title: 相册 date: 2020-03-14 12:26:45 type: picture ---

8. 编译发布 【Hexo中添加类QQ空间旅游相册页面】最后 hexo clean && hexo deploy -g 就可以把相册页设置好了
如果不想编译博客,可以用node script/photo.js直接编译。然后的动将'/source/photos'目录下的所有图片文件夹、缩略图文件夹以及 photo.json数据文件直接上传就可以更新相册了。

    推荐阅读