前言
在使用Hugo搭建个人博客时,除了写文章,我还希望能有一个专门展示照片的地方。本文记录了为基于Stack主题的Hugo博客添加相册功能的完整实现过程,希望对有类似需求的朋友提供参考。
功能需求
我对相册功能的需求很简单:
- 首页展示不同的相册集合,每个相册有封面图和描述
- 点击相册进入详情页,展示该相册下的所有照片
- 照片支持懒加载,提高页面加载速度
- 支持照片点击查看大图和轻松浏览
实现思路
相册功能主要由以下几个部分组成:
- 相册数据配置:使用YAML数据文件存储相册信息
- 相册列表页面:展示所有相册,使用CSS Grid布局
- 单个相册页面:展示特定相册中的照片,使用瀑布流布局
- 静态资源:照片文件按相册分类存储
具体实现
1. 创建相册入口页面
首先,在content/page/gallery/index.md
中创建相册入口页面:
1
2
3
4
5
6
7
8
9
10
11
12
13
| ---
title: "相册"
description: "我的照片集合"
slug: "gallery"
layout: "gallery"
menu:
main:
weight: 4
params:
icon: image
---
这里收集了我的一些照片回忆,记录生活的美好瞬间。
|
2. 创建相册数据文件
在data/gallery.yaml
中定义所有相册信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| - title: "今天的云"
slug: "clouds"
description: "记录下每天不同的云朵形状"
cover: "/photos/clouds/cover.jpg"
- title: "行东京"
slug: "tokyo"
description: "漫步在东京的街头巷尾"
cover: "/photos/tokyo/cover.jpg"
- title: "今天吃咩"
slug: "food"
description: "记录美食与生活"
cover: "/photos/food/cover.jpg"
|
这里只需要配置相册的基本信息和封面图,实际照片将从对应文件夹中自动读取。
3. 创建相册列表布局
在layouts/_default/gallery.html
中实现相册列表页面:
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
| {{ define "main" }}
<header class="page-header">
<h1>{{ .Title }}</h1>
{{ with .Description }}
<div class="page-description">
{{ . }}
</div>
{{ end }}
</header>
<section class="gallery-collections">
{{ range $idx, $album := .Site.Data.gallery }}
{{ $albumPath := printf "./static/photos/%s" $album.slug }}
{{ $photoCount := 0 }}
{{ range readDir $albumPath }}
{{ if findRE "(?i)\\.(gif|jpg|jpeg|tiff|png|bmp|webp|avif|jxl)$" .Name }}
{{ if ne .Name "cover.jpg" }}
{{ $photoCount = add $photoCount 1 }}
{{ end }}
{{ end }}
{{ end }}
<div class="album-card">
<a href="/gallery/{{ $album.slug }}">
<div class="album-cover">
<img src="{{ $album.cover }}" alt="{{ $album.title }}的封面" loading="lazy"/>
</div>
<div class="album-info">
<h2>{{ $album.title }}</h2>
<p>{{ $album.description }}</p>
<span class="photo-count">{{ $photoCount }} 张照片</span>
</div>
</a>
</div>
{{ end }}
</section>
{{ end }}
|
这个模板使用CSS Grid创建一个响应式的相册卡片网格,并自动计算每个相册中的照片数量。
4. 创建单个相册页面
为每个相册创建对应的内容页面。例如,在content/gallery/clouds/index.md
中:
1
2
3
4
5
6
7
| ---
title: "今天的云"
description: "记录下每天不同的云朵形状"
date: 2024-04-02T10:00:00+08:00
albumSlug: "clouds"
layout: "photos"
---
|
注意layout
使用了photos
,这个布局负责展示相册中的所有照片。
5. 创建照片展示布局
在layouts/_default/photos.html
中实现照片展示页面:
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
| {{ define "main" }}
<div class="gallery-header">
<h1>{{ .Title }}</h1>
{{ with .Description }}
<div class="gallery-description">
{{ . }}
</div>
{{ end }}
<div class="album-nav">
<a href="/gallery/" class="back-to-albums">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 18l-6-6 6-6"></path>
</svg>
返回相册列表
</a>
</div>
</div>
<div class="gallery-photos page">
{{ $albumSlug := .Params.albumSlug }}
{{ $albumPath := printf "./static/photos/%s" $albumSlug }}
{{ range (sort (readDir $albumPath) "Name" "asc")}}
{{ if ( .Name | findRE "(?i)\\.(gif|jpg|jpeg|tiff|png|bmp|webp|avif|jxl)$") }}
<div class="gallery-photo">
<img class="photo-img" loading='lazy' decoding="async" src="/photos/{{ $albumSlug }}/{{ .Name }}" alt="{{ .Name }}" />
</div>
{{ end }}
{{ end }}
</div>
{{ end }}
|
这个布局使用瀑布流展示照片,并支持照片懒加载和点击查看大图。
6. 创建照片目录结构
照片文件按照以下结构组织:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| static/
photos/
clouds/
cover.jpg # 相册封面
cloud1.jpg # 照片1
cloud2.jpg # 照片2
...
tokyo/
cover.jpg
tokyo1.jpg
...
food/
cover.jpg
food1.jpg
...
|
每个相册都有自己的文件夹,封面图命名为cover.jpg
,也可以根据你的需求自定义为其他照片,在gallery.yaml
中修改即可。
7. 更新菜单配置
在config/_default/menu.toml
中添加相册菜单项:
1
2
3
4
5
6
| [[main]]
identifier = "gallery"
name = "相册"
url = "/gallery/"
[main.params]
icon = "image"
|
优化与改进
在实现过程中,我遇到了一些问题并进行了优化:
相对路径问题
最初使用{{ $.Site.BaseURL }}
生成链接,但在局域网访问时会出现问题。改为使用相对路径/gallery/
和/photos/
解决了这个问题。
照片数量计算
最初在YAML数据文件中定义每张照片,后改为直接读取文件夹中的照片:
1
2
3
4
5
6
7
8
9
| {{ $albumPath := printf "./static/photos/%s" $album.slug }}
{{ $photoCount := 0 }}
{{ range readDir $albumPath }}
{{ if findRE "(?i)\\.(gif|jpg|jpeg|tiff|png|bmp|webp|avif|jxl)$" .Name }}
{{ if ne .Name "cover.jpg" }}
{{ $photoCount = add $photoCount 1 }}
{{ end }}
{{ end }}
{{ end }}
|
大小写敏感的图片格式
添加(?i)
标志使正则表达式大小写不敏感,支持.JPG
和.jpg
等不同大小写的文件扩展名:
1
| {{ if ( .Name | findRE "(?i)\\.(gif|jpg|jpeg|tiff|png|bmp|webp|avif|jxl)$") }}
|
最终效果

实现了一个简洁美观的相册功能,主要特点:
- 响应式设计,适应不同设备
- 图片懒加载,提高加载速度
- 照片瀑布流布局,充分利用屏幕空间
- 支持点击查看大图和轻松浏览
- 使用文件系统组织照片,便于管理
总结
通过自定义布局和数据文件,在Hugo博客中实现了功能完善的相册系统。整个实现过程展示了Hugo强大的模板功能和灵活性,不需要额外的插件或数据库支持,就能打造一个美观实用的相册功能。
希望这篇文章对想要在Hugo博客中添加相册功能的朋友有所帮助!如有问题或改进建议,欢迎在评论区交流。
功能优化与调整记录
在实现基本功能后,我根据实际使用情况对相册功能进行了一些优化和调整。以下是优化过程中的一些关键修改:
1. 照片排序优化
最初实现中,相册中的照片是按照文件名降序(“desc”)排列的。但在实际使用过程中,我发现按照文件名升序(“asc”)排列更符合浏览习惯,特别是当照片按照时间顺序命名时。
修改前:
1
| {{ range (sort (readDir $albumPath) "Name" "desc")}}
|
修改后:
1
| {{ range (sort (readDir $albumPath) "Name" "asc")}}
|
这样,照片就会按照文件名从小到大的顺序排列,通常对应时间的先后顺序,提供更好的浏览体验。
2. 移除照片标题和时间显示
在早期版本中,我曾实现了从文件名中提取并显示照片标题和拍摄时间的功能:
1
2
3
4
5
| <div class="gallery-photo">
<img class="photo-img" loading='lazy' decoding="async" src="/photos/{{ $albumSlug }}/{{ .Name }}" alt="{{ .Name }}" />
<span class="photo-title">{{ .Name | replaceRE "^[0-9 -]+(.*)[.].*" "$1"}}</span>
<span class="photo-time">{{ .Name | replaceRE "^([0-9-]+).*[.].*" "$1" }}</span>
</div>
|
这要求我按照特定格式命名照片文件,如"2024-04-02-樱花.jpg",其中"2024-04-02"会被提取为时间,“樱花"会被提取为标题。
但在实际使用中,我发现这种命名方式有些繁琐,而且页面上显示这些信息会让相册页面显得过于拥挤。因此,我移除了这些显示元素,回归到更简洁的界面设计:
1
2
3
| <div class="gallery-photo">
<img class="photo-img" loading='lazy' decoding="async" src="/photos/{{ $albumSlug }}/{{ .Name }}" alt="{{ .Name }}" />
</div>
|
3. 相册配置的灵活性
在data/gallery.yaml
中,我为相册配置增加了更多灵活性。可以自定义封面图路径,不一定非要使用相册文件夹中的"cover.jpg”:
1
2
3
4
| - title: "今天的云"
slug: "clouds"
description: "记录下每天不同的云朵形状"
cover: "/photos/clouds/best-cloud.jpg" # 可以指定任意图片作为封面
|
4. 文件命名建议
经过实践,我发现以下文件命名约定效果较好:
- 相册文件夹:使用简短的英文单词,如"clouds"、“tokyo”
- 照片文件:使用日期前缀命名,如"2024-04-02-1.jpg"、“2024-04-02-2.jpg”
这种命名方式既保持了文件的良好组织,又便于按时间顺序浏览照片。
5. 照片加载优化
为了进一步优化照片加载性能,我在img标签中增加了更多的属性:
1
2
3
4
5
| <img class="photo-img"
loading="lazy"
decoding="async"
src="/photos/{{ $albumSlug }}/{{ .Name }}"
alt="{{ .Name }}" />
|
loading="lazy"
确保了图片只有在即将进入视口时才会加载,而decoding="async"
让浏览器可以异步解码图片,不阻塞主线程,提高页面响应速度。
最终配置参考
以下是最终使用的相册配置和模板代码,供参考:
相册数据配置(data/gallery.yaml):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| - title: "今天的云"
slug: "clouds"
description: "记录下每天不同的云朵形状"
cover: "/photos/clouds/cover.jpg"
- title: "行东京"
slug: "tokyo"
description: "漫步在东京的街头巷尾"
cover: "/photos/tokyo/cover.jpg"
- title: "今天吃咩"
slug: "food"
description: "记录美食与生活"
cover: "/photos/food/cover.jpg"
|
照片显示模板(layouts/_default/photos.html)核心部分:
1
2
3
4
5
6
7
8
9
10
11
| <div class="gallery-photos page">
{{ $albumSlug := .Params.albumSlug }}
{{ $albumPath := printf "./static/photos/%s" $albumSlug }}
{{ range (sort (readDir $albumPath) "Name" "asc")}}
{{ if ( .Name | findRE "(?i)\\.(gif|jpg|jpeg|tiff|png|bmp|webp|avif|jxl)$") }}
<div class="gallery-photo">
<img class="photo-img" loading='lazy' decoding="async" src="/photos/{{ $albumSlug }}/{{ .Name }}" alt="{{ .Name }}" />
</div>
{{ end }}
{{ end }}
</div>
|
通过这些优化和调整,相册功能变得更加简洁、高效,提供了更好的用户体验。