本篇教程基于 Hexo 6.3.0 & Solitude 1.4.7 的动态相册页魔改教程 📝

如果没有服务器可以搭建memos,可以使用iCat自用的memos服务

效果预览

创建数据

  • 创建并修改 [blogRoot]/source/photos/index.md 页面
1
2
3
4
5
6
7
8
9
10
11
12
13
---
title: 生活相册
date: 2024-02-10 12:17:08
type: photo
comments: false
cover: 'https://img.meuicat.com/banner'
desc: 封の生活色彩
leftend: '活在当下 热烈且自由'
rightbtn: '部署项目'
rightbtnlink: /posts/648b1ceb.html
---

<!-- 页面内容 -->
  • 新建 [blogRoot]/themes/Solitude/layout/includes/page/photo.pug 页面,并新增以下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
include ../widgets/page/banner

if theme.photo.bar.enable
.icat-status-bar
.status-bar-tips= theme.photo.bar.tips
.status-bar
#bar-box
each item in theme.photo.bar.list
- const content = item.split(' || ')
.status-bar-item
a(onclick="sco.photos('" + content[0] + "')") #{content[1]}
#status-bar-button(onclick="sco.statusbar('bar-box')")
i.scoicon.sco-show-right-line
if theme.photo.more.enable
- const contents = theme.photo.more.link.split(' || ')
if contents[0].startsWith('/')
a.status-bar-more(href="javascript:void(0)" onclick="pjax.loadUrl('" + contents[0] + "')") #{contents[1]}
else
a.status-bar-more(href=contents[0]) #{contents[1]}

div.gallery-photos.page
img(src=theme.photo.loading style="margin:auto")
  • 修改 [blogRoot]/themes/Solitude/layout/page.pug 来使页面匹配
    + 号直接删除 即是正常缩进)
1
2
3
4
5
6
                when 'album'
include includes/page/album
+ when 'photo'
+ include includes/page/photo
default
include includes/page/default
  • 新建 [blogRoot]/themes/Solitude/source/css/_page/photo.styl 样式文件,并新增以下内容
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
151
152
153
154
155
.icat-status-bar
margin: 16px 0
display: flex
white-space: nowrap
align-items: center
background: var(--sco-card-bg)
border-radius: 8px
padding: 0 12px
border: var(--style-border-always)
transition: .6s
box-shadow: var(--sco-shadow-border)

&:hover
border-color: var(--sco-blue)
box-shadow: var(--sco-shadow-blue)

.status-bar
padding: 0.4rem 0 0.4rem 0.4rem
white-space: nowrap
overflow: hidden
transition: .3s
width: 100%
justify-content: space-between
user-select: none
display: flex
align-items: center
font-size: 15px

#bar-box
white-space: nowrap
overflow-x: scroll
overflow-y: hidden
display: flex
border-radius: 8px
align-items: center
height: 30px

.status-bar-item
a
padding: 0.1rem 0.5rem
margin-right: 10px
font-weight: 700
border-radius: 8px
display: flex
align-items: center
height: 30px
color: var(--sco-fontcolor)
opacity: .8
transition: .6s

&:hover
background: var(--sco-blue)
opacity: 1
color: var(--sco-white)

&.selected
a
background: var(--sco-blue) !important
opacity: 1 !important
color: var(--sco-white) !important
pointer-events: none

#status-bar-button
margin-left: 12px
cursor: pointer
height: 22px
display: flex
align-items: center
transition: .6s

&:hover
color: var(--sco-blue)

.status-bar-more
margin-left: 12px
font-weight: 400
color: var(--sco-fontcolor)
opacity: .8
transition: .6s

&:hover
color: var(--sco-blue)

#bar-box::-webkit-scrollbar
display: none

.gallery-photos
width: 100%
text-align: center
animation: slide-in .6s .4s backwards

.gallery-photo
min-height: 5rem
width: 24.99%
padding: 4px
position: relative
animation: slide-in 0.6s 0.4s backwards

+maxWidth1024()
width: 33.3%

+maxWidth768()
width: 49.9%
padding: 3px

+minWidth2000()
width: 20%

&:hover
img
transform: scale(1.1)

a
border-radius: 8px
border: var(--style-border-always)
box-shadow: var(--sco-shadow-border)
display: block
overflow: hidden
transition: .6s

&:hover
border-color: var(--sco-blue)
box-shadow: var(--sco-shadow-blue)

img
display: block
width: 100%
animation: fadeIn 1s
cursor: pointer
transition: all .4s ease-in-out

.photo-title,
.photo-time
max-width: calc(100% - 7px)
line-height: 1.8
position: absolute
left: 4px
font-size: 14px
background: rgba(0,0,0,0.3)
padding: 0px 8px
color: #fff
animation: fadeIn 1s

.photo-title
bottom:4px
border-radius: 0 8px 0 8px

+maxWidth768()
font-size: 12px
left: 3px
bottom: 3px

.photo-time
top:4px
border-radius: 8px 0 8px 0
  • [blogRoot]/themes/Solitude/source/_page/index.styl 样式文件下面新增内容
    + 号直接删除 即是正常缩进)
1
2
3
4
5
6
7
if hexo-config('music.enable')
@import "music.styl"

@import "share.styl"

+if hexo-config('photo.enable')
+ @import "photo.styl"
  • [blogRoot]/themes/Solitude/source/js/main.js 文件中,第六百九十六行后,新增以下内容
    ( 注:第2、3、39行,需要根据自己的需求进行替换)
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
  photos(tag) {
let url = '你的memos地址' // 修改api
let apiUrl = tag ? `${url}/api/v1/memo?creatorId=用户ID&tag=${tag}` : `${url}/api/v1/memo?creatorId=用户ID&tag=首次进入需要显示的图片分类`;

fetch(apiUrl).then(res => res.json()).then(data => {
let html = '',
imgs = []
data.forEach(item => {
let ls = item.content.match(/\!\[.*?\]\(.*?\)/g)
if (ls) imgs = imgs.concat(ls)
if (item.resourceList.length) {
item.resourceList.forEach(t => {
if (t.externalLink) imgs.push(`![](${t.externalLink})`)
else imgs.push(`![](${url}/o/r/${t.id}/${t.publicId}/${t.filename})`)
})
}
})

if (imgs) imgs.forEach(item => {
let img = item.replace(/!\[.*?\]\((.*?)\)/g, '$1'),
time, title, tat = item.replace(/!\[(.*?)\]\(.*?\)/g, '$1')
if (tat.indexOf(' ') != -1) {
time = tat.split(' ')[0]
title = tat.split(' ')[1]
} else title = tat

html += `<div class="gallery-photo"><a href="${img}" data-fancybox="gallery" class="fancybox" data-thumb="${img}"><img class="no-lazyload photo-img" loading='lazy' decoding="async" src="${img}"></a>`
title ? html += `<span class="photo-title">${title}</span>` : ''
time ? html += `<span class="photo-time">${time}</span>` : ''
html += `</div>`
})

document.querySelector('.gallery-photos.page').innerHTML = html
imgStatus.watch('.photo-img', () => { waterfall('.gallery-photos') })
window.Lately && Lately.init({ target: '.photo-time' })
}).catch()

var statusBarItemItems = document.querySelectorAll('.status-bar-item');
let firstElement = statusBarItemItems[1]; // Bar栏首次进入的按钮状态
firstElement.classList.add('selected');

Array.from(statusBarItemItems).forEach(function (element) {
element.onclick = function (event) {
var selectedElements = document.querySelectorAll('.status-bar-item.selected');
Array.from(selectedElements).forEach(function (selectedElement) {
selectedElement.classList.remove('selected');
});
element.classList.add('selected');

event.stopPropagation();
event.preventDefault();
return false;
};
});
},
statusbar(elementId) {
const container = document.getElementById(elementId);

if (container) {
const buttonId = (elementId === "category-bar-items") ? "category-bar-button" : "status-bar-button";
const button = document.getElementById(buttonId);
const maxScroll = container.scrollWidth - container.clientWidth;

if (container.scrollLeft + container.clientWidth >= maxScroll - 8) {
container.scrollTo({
left: 0,
behavior: "smooth"
});
} else {
container.scrollBy({
left: container.clientWidth,
behavior: "smooth"
});
}

container.addEventListener("scroll", function() {
button.style.transform = (container.scrollLeft + container.clientWidth >= maxScroll - 8) ? "rotate(180deg)" : "";
}, { once: true });
}
}
  • [blogRoot]/themes/Solitude/source/js/main.js 文件末尾处,进行新增以下内容
    + 号直接删除 即是正常缩进)
1
2
3
4
5
6
7
8
9
10
11
12
13
···

window.refreshFn = () => {
···

if (location.pathname == '/photos/') sco.photos()
}

window.onresize = () => {
if (location.pathname == '/photos/') waterfall('.gallery-photos');
};

···
  • 创建 [blogRoot]/source/js/imgStatus.min.js 文件,并新增以下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
! function() {
this.loaded = 0, this.failed = 0, this.total = 0, this.watch = function(a, b) {
var c = document.querySelectorAll(a);
if (!c.length) return console.log("[imgStatus]: There aren't any images associated with this selector (" + a + ")!");
this.total = c.length;
for (var d = 0; d < this.total; d++) isCached(c[d].src) ? this._setLoaded(b) : c[d].addEventListener ? (c[d].addEventListener("load", this._setLoaded.bind(this, b)), c[d].addEventListener("error", this._setFailed.bind(this, b))) : (c[d].attachEvent("onload", this._setLoaded.bind(this, b)), c[d].attachEvent("onerror", this._setFailed.bind(this, b)))
}, this.isCached = function(a) {
var b = new Image;
return b.src = a, b.complete
}, this._setFailed = function(a, b) {
++this.failed, "function" == typeof a && a(this)
}, this._setLoaded = function(a, b) {
++this.loaded, "function" == typeof a && a(this)
}, this.isDone = function() {
return this.loaded + this.failed === this.total ? !0 : !1
}, "object" == typeof window && (window.imgStatus = this)
}();

// imgStatus.min.js
  • 创建 [blogRoot]/source/js/lately.min.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
! function() {
window.Lately = new function() {
var t = this;
this.lang = {
second: "秒",
minute: "分钟",
hour: "小时",
day: "天",
month: "个月",
year: "年",
ago: "前",
error: "NaN"
};
var e = function(e) {
e = new Date(n(e));
var r = new function() {
this.second = (Date.now() - e.getTime()) / 1e3, this.minute = this.second / 60, this.hour = this.minute / 60, this.day = this.hour / 24, this.month = this.day / 30, this.year = this.month / 12
}, i = Object.keys(r).reverse().find(function(t) {
return r[t] >= 1
});
return (i ? function(t, e) {
return Math.floor(t) + e
}(r[i], t.lang[i]) : t.lang.error) + t.lang.ago
}, n = function(t) {
return t = new Date(t && ("number" == typeof t ? t : t.replace(/-/g, "/").replace("T", " "))), !isNaN(t.getTime()) && t.getTime()
};
return {
init: function() {
var r = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, i = r.target,
a = void 0 === i ? "time" : i,
o = r.lang;
o && (t.lang = o);
var u = !0,
h = !1,
l = void 0;
try {
for (var s, c = document.querySelectorAll(a)[Symbol.iterator](); !(u = (s = c.next()).done); u = !0) {
var f = s.value,
g = n(f.dateTime) || n(f.title) || n(f.innerHTML) || 0;
if (!g) return;
f.title = new Date(g).toLocaleString(), f.innerHTML = e(g)
}
} catch (t) {
h = !0, l = t
} finally {
try {
!u && c.
return &&c.
return ()
} finally {
if (h) throw l
}
}
},
format: e
}
}
}();

/*
* Lately.min.js 2.5.2
* MIT License - http://www.opensource.org/licenses/mit-license.php
* https://tokinx.github.io/lately/
*/
  • _config.Solitude.yml 主题配置文件中 extends 下的 headbody 引入 lately.min.js imgStatus.min.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  ···

# 插入代码到头部 </head> 之前 和 底部 </body> 之前
# Insert code before </head> and before </body>
# 插入额外代码 如:统计,广告等
# Insert additional code such as: statistics, advertising, etc.
extends:
head: # 在head中插入 / Insert in head
- <script async src="/js/statistics.js"></script> # 监控统计
body: # 在body中插入 / Insert in body
- <script type="text/javascript" src="/js/imgStatus.min.js"></script> # memos动态相册 - imgStatus
- <script type="text/javascript" src="/js/lately.min.js"></script> # memos动态相册 - lately

···
  • _config.butterfly.yml 主题配置文件中,新增以下配置项
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
# photo 动态相册页
# MeuiCat设计
# https://meuicat.com/blog/84/
# --------------------------------------

photo:
enable: true
bar:
# 是否使用Bar分类栏
enable: false
tips: 年份类别
list:
# Bar分类栏的tag和显示文字,第一个参数为tag;第二个参数为需要显示的文字
- 相册 || 全部
- 2023 || 2023
- 2022 || 2022
- 2021 || 2021
- 2020 || 2020
- 2019 || 2019
- 2018 || 2018
- 2017 || 2017
more:
# 是否显示Bar分类栏最右侧的更多按钮
enable: false
link: /album/ || 影集
# Bar分类栏更多按钮的跳转链接和显示文字,第一个参数为跳转链接地址(支持/album/、https://meuicat.com这种格式,不支持不带头标的meuicat.com格式;第二个参数为需要显示的文字
loading: https://yife68.gitee.io/icat-pic/blog/loading.svg
# 相册加载前的加载loading图标

使用参数

memos api地址格式如下所示:
https://memos地址/api/v1/memo?creatorId=用户ID&tag=标签名

memos地址就是首页地址,如:memos.meuicat.com

用户ID获取方式:

  • 点击个人头像,然后点击 RSS

  • 根据浏览器链接获取ID

如url是:https://memos.meuicat.com/u/1/rss.xml
则creatorId就是1
最后完整链接如下:
https://memos.meuicat.com/api/v1/memo?creatorId=1&tag=相册
能看到数据则为正确链接

1
2
3
4
5
6
7
8
9
#相册
<!-- 写法就是markdown的写法,中括号里先写时间再写标题,中间使用空格隔开 -->
![2023-01-29 我是标题](图片链接)
<!-- 若不想要时间只写标题即可 -->
![我是标题](图片链接)
<!-- 若不想要标题只写时间即可,只不过后面需要添加空格 -->
![2023-01-29 ](图片链接)
<!-- 也可以只填写图片链接 -->
![](图片链接)
  • memos 内的写法
1
2
3
4
5
#相册
![2023-02-09 ](https://s11.ax1x.com/2023/03/16/pp3jQ3V.jpg)
![ 犹豫没要微信](https://s11.ax1x.com/2023/03/17/ppGlZid.jpg)
![2022-10-12 可可爱爱没有脑袋](https://s11.ax1x.com/2023/03/16/pp34dpj.jpg)
![](https://s11.ax1x.com/2023/03/15/pp39iRI.jpg)

魔改适配

已适配Butterfly主题,具体魔改教程可前往下方文章查看