Hugo LoveIt 主題內建了 Pagination(分頁)功能,可以透過首頁的頁碼切換不同頁面,避免所有文章一次塞在首頁,也能讓網站載入速度更快。
原本的分頁導覽以頁碼為主,對一般的瀏覽需求來說已經很夠用了。不過,隨著網站內容慢慢增加、分頁數變多,導覽列就不可能把所有頁碼都顯示出來。這時如果只靠頁碼來切換,使用上多少會有點不方便。
因此,我決定稍微修改一下 LoveIt 主題的分頁導覽,在保留原本頁碼顯示方式的同時,再加入上一頁、下一頁以及指定頁數跳轉功能,讓分頁操作更加方便、完整。
為什麼要修改
LoveIt 原本的 Pagination 主要提供頁碼導覽,例如:
當頁數較多時,雖然會透過 ... 簡化頁碼顯示,但如果想要連續瀏覽上一頁或下一頁,還是得自己找到對應的頁碼,操作上稍微沒那麼方便。
所以這次我在保留原有頁碼導覽的基礎上,加入上一頁、下一頁,以及指定頁數跳轉功能,讓分頁操作更直覺一些。
電腦版會將這些功能放在同一列,讓上一頁、頁碼、下一頁與跳頁功能可以直接操作:
1
| ◀︎ 1 ... 4 5 6 ... 10 ▶︎ [頁數] [前往]
|
手機版則將頁碼與其他操作分成上下兩行,避免畫面太擁擠:
1
2
| 1 ... 4 5 6 ... 10
◀︎ [頁數] [前往] ▶︎
|
修改的檔案位置
本次修改的 LoveIt 主題 Pagination 模板位於 layouts\partials\paginator.html 。另外, CSS 與多國語系文字也會一併調整,分別放在以下檔案:
1
2
3
4
5
6
| CSS:
assets\css\_custom.scss
i18n:
i18n\zh-TW.toml
i18n\en.toml
|
修改前
原本的 layouts\partials\paginator.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
| {{- if gt .Paginator.TotalPages 1 -}}
<ul class="pagination">
{{- .Scratch.Set "paginator.ellipsed" false -}}
{{- range $.Paginator.Pagers -}}
{{- $right := sub .TotalPages .PageNumber -}}
{{- $showNumber := or (le .PageNumber 1) (eq $right 0) -}}
{{- $showNumber := or $showNumber (and (gt .PageNumber (sub $.Paginator.PageNumber 3)) (lt .PageNumber (add $.Paginator.PageNumber 3))) -}}
{{- if $showNumber -}}
{{- $.Scratch.Set "paginator.ellipsed" false -}}
{{- $.Scratch.Set "paginator.shouldEllipse" false -}}
{{- else -}}
{{- $.Scratch.Set "paginator.shouldEllipse" (not ($.Scratch.Get "paginator.ellipsed") ) -}}
{{- $.Scratch.Set "paginator.ellipsed" true -}}
{{- end -}}
{{- if $showNumber -}}
<li class="page-item {{ if eq . $.Paginator }}active{{ end }}">
<span class="page-link">
<a href="{{ .URL }}">
{{- .PageNumber -}}
</a>
</span>
</li>
{{- else if ($.Scratch.Get "paginator.shouldEllipse") -}}
<li class="page-item ">
<span class="page-link" aria-hidden="true">…</span>
</li>
{{- end -}}
{{- end -}}
</ul>
{{- end -}}
|
修改後
修改後的 layouts\partials\paginator.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
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
| {{- if gt .Paginator.TotalPages 1 -}}
<ul class="pagination">
{{/* Previous */}}
{{- if .Paginator.HasPrev -}}
<li class="page-item pagination-prev">
<a class="page-link" href="{{ .Paginator.Prev.URL }}" aria-label="{{ i18n "pagination.previous" }}" title="{{ i18n "pagination.previous" }}">
{{ i18n "pagination.previousSymbol" }}
</a>
</li>
{{- else -}}
<li class="page-item pagination-prev disabled">
<a class="page-link" href="javascript:void(0);" aria-label="{{ i18n "pagination.previous" }}" title="{{ i18n "pagination.previous" }}" onclick="alert('{{ i18n "pagination.firstPage" }}');">
{{ i18n "pagination.previousSymbol" }}
</a>
</li>
{{- end -}}
{{/* Pages */}}
<li class="pagination-pages">
<ul class="pagination-pages-list">
{{- .Scratch.Set "paginator.ellipsed" false -}}
{{- range $.Paginator.Pagers -}}
{{- $right := sub .TotalPages .PageNumber -}}
{{- $showNumber := or (le .PageNumber 1) (eq $right 0) -}}
{{- $showNumber := or $showNumber (and (gt .PageNumber (sub $.Paginator.PageNumber 3)) (lt .PageNumber (add $.Paginator.PageNumber 3))) -}}
{{- if $showNumber -}}
{{- $.Scratch.Set "paginator.ellipsed" false -}}
{{- $.Scratch.Set "paginator.shouldEllipse" false -}}
{{- else -}}
{{- $.Scratch.Set "paginator.shouldEllipse" (not ($.Scratch.Get "paginator.ellipsed") ) -}}
{{- $.Scratch.Set "paginator.ellipsed" true -}}
{{- end -}}
{{- if $showNumber -}}
<li class="page-item pagination-page {{ if eq . $.Paginator }}active{{ end }}">
<span class="page-link">
<a href="{{ .URL }}" title="{{ i18n "pagination.page" . }}" aria-label="{{ i18n "pagination.page" . }}">
{{- .PageNumber -}}
</a>
</span>
</li>
{{- else if ($.Scratch.Get "paginator.shouldEllipse") -}}
<li class="page-item pagination-page">
<span class="page-link" aria-hidden="true">…</span>
</li>
{{- end -}}
{{- end -}}
</ul>
</li>
{{/* Next */}}
{{- if .Paginator.HasNext -}}
<li class="page-item pagination-next">
<a class="page-link" href="{{ .Paginator.Next.URL }}" aria-label="{{ i18n "pagination.next" }}" title="{{ i18n "pagination.next" }}">
{{ i18n "pagination.nextSymbol" }}
</a>
</li>
{{- else -}}
<li class="page-item pagination-next disabled">
<a class="page-link" href="javascript:void(0);" aria-label="{{ i18n "pagination.next" }}" title="{{ i18n "pagination.next" }}" onclick="alert('{{ i18n "pagination.lastPage" }}');">
{{ i18n "pagination.nextSymbol" }}
</a>
</li>
{{- end -}}
{{/* Page Jump */}}
<li class="page-item page-jump">
<form
class="page-jump-form"
onsubmit="return jumpToPage(this);"
>
<input
type="number"
name="page"
min="1"
max="{{ .Paginator.TotalPages }}"
placeholder="{{ i18n "pagination.pagePlaceholder" }}"
aria-label="{{ i18n "pagination.pagePlaceholder" }}"
title="{{ i18n "pagination.pagePlaceholder" }}"
inputmode="numeric"
required
>
<button type="submit" title="{{ i18n "pagination.jump" }}">
{{ i18n "pagination.jump" }}
</button>
</form>
</li>
</ul>
{{/* 頁數跳轉 JavaScript */}}
<script>
function jumpToPage(form) {
const input = form.querySelector('input[name="page"]');
const page = parseInt(input.value, 10);
const totalPages = {{ .Paginator.TotalPages }};
if (
isNaN(page) ||
page < 1 ||
page > totalPages
) {
input.reportValidity();
return false;
}
const baseURL = "{{ .Paginator.First.URL }}";
if (page === 1) {
window.location.href = baseURL;
} else {
const url = new URL(
baseURL,
window.location.origin
);
if (url.pathname === '/') {
url.pathname = '/page/' + page + '/';
} else {
url.pathname = url.pathname.replace(/\/+$/, '') + '/page/' + page + '/';
}
window.location.href = url.toString();
}
return false;
}
</script>
{{- end -}}
|
新增的 CSS
將以下 CSS 加入網站的樣式檔案:
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
| /* 移除分頁左右空白 */
.pagination {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
/* 頁碼群組不參與外層動畫 */
.pagination .pagination-pages {
margin: 0;
padding: 0;
&:before,
&:after {
display: none;
}
&:hover {
&:before,
&:after {
width: 0;
}
}
}
/* 頁碼群組停用父層 hover 文字效果 */
.pagination .pagination-pages:hover a {
color: #bfbfbf;
}
/* 頁碼個別元素保留 hover 效果 */
.pagination-page {
&:hover a {
color: $pagination-link-hover-color !important;
}
&.active a {
color: $pagination-link-hover-color !important;
}
}
/* 深色模式頁碼 hover 與啟用效果 */
[theme=dark] .pagination-page:hover a,
[theme=dark] .pagination-page.active a {
color: $pagination-link-hover-color-dark !important;
}
/* 顯示無法使用的上一頁與下一頁 */
.pagination .pagination-prev.disabled,
.pagination .pagination-next.disabled {
display: inline;
color: #bfbfbf;
}
/* 頁碼列表使用彈性排列 */
.pagination-pages-list {
display: flex;
align-items: center;
margin: 0;
padding: 0;
list-style: none;
}
/* 分頁左右按鈕與頁碼群組保持緊密排列 */
.pagination-prev,
.pagination-pages,
.pagination-next {
flex: 0 0 auto;
}
.pagination-prev {
margin-right: 0.25em !important;
font-size: 1rem;
a {
font-size: 1rem;
}
}
.pagination-next {
margin-left: 0.1em !important;
font-size: 1rem;
a {
font-size: 1rem;
}
}
/* 手機版分頁改為上下兩行 */
@media (max-width: 479px) {
/* 分頁容器允許換行並置中 */
.pagination {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
/* 第一行顯示頁碼 */
.pagination-pages {
order: 1;
flex: 0 0 100%;
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 0.75em !important;
}
/* 手機版頁碼保持置中並縮小間距 */
.pagination-pages-list {
justify-content: center;
.pagination-page {
margin-left: 0.9em !important;
margin-right: 0.9em !important;
}
}
/* 第二行依序排列上一頁、跳頁、下一頁 */
.pagination-prev {
order: 2;
}
.page-jump {
order: 3;
}
.pagination-next {
order: 4;
}
}
/* 輸入頁數跳轉元件 */
.page-jump {
margin-left: 0.4em !important;
margin-right: 0.4em !important;
}
.page-jump-form {
display: inline-flex;
align-items: center;
gap: 0.35em;
margin: 0;
padding: 0;
}
/* 頁數輸入框基本樣式 */
.page-jump-form input {
width: 5em;
height: 2.2em;
box-sizing: border-box;
padding: 0 0.5em;
border: 1px solid rgba(128, 128, 128, 0.35);
border-radius: 0.5em;
background: transparent;
color: inherit;
font: inherit;
font-size: 0.8rem;
text-align: center;
outline: none;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
}
/* 頁數輸入框滑入與聚焦效果 */
.page-jump-form input:hover {
border-color: rgba(128, 128, 128, 0.6);
}
.page-jump-form input:focus {
border-color: $pagination-link-hover-color;
box-shadow: 0 0 0 0.15em rgba(128, 128, 128, 0.12);
}
/* 頁數輸入框提示文字 */
.page-jump-form input::placeholder {
color: inherit;
opacity: 0.5;
}
/* 跳轉按鈕基本樣式 */
.page-jump-form button {
height: 2.2em;
padding: 0 0.7em;
border: 1px solid $pagination-link-hover-color;
border-radius: 0.5em;
background: $pagination-link-hover-color;
color: #fff;
font: inherit;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition:
opacity 0.2s ease,
transform 0.15s ease,
box-shadow 0.2s ease;
}
/* 跳轉按鈕滑入與按下效果 */
.page-jump-form button:hover {
opacity: 0.9;
box-shadow: 0 0.2em 0.5em rgba(0, 0, 0, 0.15);
}
.page-jump-form button:active {
transform: translateY(0.05em);
box-shadow: none;
}
/* 深色模式輸入框樣式 */
[theme=dark] .page-jump-form input {
background: rgba(14, 13, 13, 0.9);
color: #fff;
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 0.25em 0.6em rgba(0, 0, 0, 0.3);
}
/* 深色模式輸入框滑入與聚焦效果 */
[theme=dark] .page-jump-form input:hover {
border-color: rgba(255, 255, 255, 0.4);
}
[theme=dark] .page-jump-form input:focus {
border-color: $pagination-link-hover-color-dark;
box-shadow: 0 0 0 0.15em rgba(255, 255, 255, 0.1);
}
/* 深色模式跳轉按鈕樣式 */
[theme=dark] .page-jump-form button {
border-color: $pagination-link-hover-color-dark;
color: #fff;
}
/* 手機版縮小跳頁元件 */
@media (max-width: 479px) {
.page-jump {
margin-left: 0.3em !important;
margin-right: 0.3em !important;
}
.page-jump-form {
gap: 0.25em;
}
.page-jump-form input {
width: 4em;
height: 2.1em;
}
.page-jump-form button {
height: 2.1em;
padding: 0 0.65em;
}
}
|
新增的 i18n
i18n\zh-TW.toml
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
| [pagination.previous]
other = "上一頁"
[pagination.previousSymbol]
other = "◀︎"
[pagination.firstPage]
other = "已經到最前面了哦!"
[pagination.next]
other = "下一頁"
[pagination.nextSymbol]
other = "▶︎"
[pagination.lastPage]
other = "這是最後一頁了!"
[pagination.page]
other = "第 {{ .PageNumber }} 頁"
[pagination.pagePlaceholder]
other = "頁數"
[pagination.jump]
other = "前往"
|
i18n\en.toml
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
| [pagination.previous]
other = "Previous"
[pagination.previousSymbol]
other = "◀︎"
[pagination.firstPage]
other = "You're already at the very first page!"
[pagination.next]
other = "Next"
[pagination.nextSymbol]
other = "▶︎"
[pagination.lastPage]
other = "You've reached the very last page!"
[pagination.page]
other = "Page {{ .PageNumber }}"
[pagination.pagePlaceholder]
other = "Page"
[pagination.jump]
other = "Go"
|
結語
這次修改主要是在保留 LoveIt 原有分頁功能的基礎上,加入上一頁、下一頁與指定頁數跳轉,讓文章較多時的分頁操作更加方便。另外也透過 i18n 支援多國語言文字切換,並針對桌面版與手機版調整版面配置,讓新增的功能在不同裝置上都能維持良好的使用體驗。整體修改不需要更動 Hugo 的核心功能,只需要調整 Pagination 模板、 CSS 與 i18n ,就能讓原本的分頁導覽更加完整。
運行環境
- Hugo 0.145.0
- LoveIt 主題(2025 年 2 月 21 日 GitHub 上的版本)