作为一个静态博客框架,Hugo 的优势在于可以直接白嫖各厂商的静态页面托管服务(如 Cloudflare Pages、GitHub Pages)。而 PaperMod 主题的简约设计则提供了极高的可定制性。
生命在于折腾,选择这两者作为技术栈后,折腾博客的下一站自然就是设置一个好看的背景图片。
给首页加图片
如果只需要给首页加图片,那么只需要加一个 .list 的 CSS 选择器。在 PaperMod 的 DOM 结构中,首页容器带着 .list 类名。我们可以在 assets/css/extended/custom.css 中注入基础样式:
.list {
background-image: url("/images/background-light.webp") !important;
background-size: cover;
background-position: center;
background-attachment: fixed;
background-repeat: no-repeat;
}
直接加上背景后会遇到一个很刺眼的问题:首页的文章列表文字是直接覆盖在背景上的。我们需要把第一个帖子加上框框,不然文字会融入背景,导致可读性极差。
PaperMod 默认的首帖(.first-entry)没有预设内边距和边框。我们可以补全它的盒模型,并给框框加上透明效果与毛玻璃质感(Glassmorphism):
/* 补全首帖盒模型 */
.first-entry {
border-radius: var(--radius);
padding: var(--gap);
}
/* 给卡片框框加上半透明与毛玻璃效果 */
.first-entry,
.post-entry {
/* 使用 color-mix 复用主题原生变量并混入透明度 */
background: color-mix(in srgb, var(--entry) 80%, transparent) !important;
border: 1px solid color-mix(in srgb, var(--border) 60%, transparent) !important;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
适应暗色模式
现代 PaperMod 主题切换暗色模式时,会在顶层的 <html> 标签上注入 data-theme="dark" 属性,而不是修改类名。因此我们需要使用 HTML 属性选择器来进行样式覆盖:
html[data-theme="dark"] .list {
background-image: url("/images/background-dark.jpg") !important;
}
手机与电脑适配
手机屏幕是竖屏,电脑屏幕是横屏。强制让手机加载横屏大图会导致严重裁切并浪费流量。我们可以利用 CSS 媒体查询在不同视口宽度下切换不同比例的资源:
/* 默认加载移动端竖屏图 */
.list {
background-image: url("/images/background-mobile-light.webp") !important;
}
/* 视口宽度 >= 768px 时切换为桌面端横屏图 */
@media screen and (min-width: 768px) {
.list {
background-image: url("/images/background-desktop-light.webp") !important;
}
}
全局背景图片+随机选择背景图片
以上方式加的背景图片,在文章内容页又看不到了。因为点击进入文章后,<body> 节点的 .list 类名会切换为 .single。
我们需要将选择器改为 body,但如果直接将 background-attachment: fixed 挂载到 body 上,在许多移动端浏览器上会出现严重的视口缩放 Bug——渲染引擎会把整个长文章的“滚动总高度”当作计算 cover 比例的基准,导致图片被极度放大并产生异常裁切。
我们需要特殊处理避免图像裁剪出错。标准的工程解法是抽离一个固定伪元素渲染层:
body {
background: transparent !important;
position: relative;
}
/* 构建独立的背景渲染层,锁定物理视口 */
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
background-image: var(--dynamic-bg) !important;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
pointer-events: none;
}
此外,我们可能想把手机/电脑 + 明亮/黑暗四种切换的逻辑写到一处去,以遵守 DRY 原则。那么可以使用引入前端内联脚本,顺便加一个随机选择图片的功能。
内联脚本必须注入到 HTML 的 <head> 中(在 Hugo 中可以通过重写 layouts/partials/extend_head.html 实现),利用浏览器的渲染阻塞特性在 DOM 挂载前完成变量计算,避免产生无样式内容闪烁(FOUC)。
配置方式与整体代码如下:
- 将图片存放在
static/images/bgs/目录下,按desktop与mobile分文件夹,命名遵循bg-x-light.webp/bg-x-dark.webp规范。 - 在
custom.css中将样式收敛,只保留 CSS 变量接管:
/* 结构属性补全 */
.first-entry,
.post-single {
border-radius: var(--radius);
padding: var(--gap);
}
/* 全局半透明卡片与毛玻璃 */
.first-entry,
.post-entry,
.post-single,
.footer {
background: color-mix(in srgb, var(--entry) 80%, transparent) !important;
border: 1px solid color-mix(in srgb, var(--border) 60%, transparent) !important;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 精简 Footer 框体大小 */
.footer {
border-radius: var(--radius);
width: fit-content;
margin: 0 auto var(--gap) auto !important;
padding: 12px 24px !important;
line-height: 1.6 !important;
}
- 在
layouts/partials/extend_head.html中注入动态计算脚本:
<script>
(() => {
// 状态映射字典:配置不同环境下拥有的图片数量
const bgConfig = {
"desktop-light": 5,
"desktop-dark": 3,
"mobile-light": 4,
"mobile-dark": 2,
};
const isDark =
localStorage.getItem("pref-theme") === "dark" ||
(!localStorage.getItem("pref-theme") &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
const isMobile = window.innerWidth < 768;
const folder = isMobile ? "mobile" : "desktop";
const suffix = isDark ? "dark" : "light";
const currentStateKey = `${folder}-${suffix}`;
// 从字典中动态获取图池最大值
const maxBgCount = bgConfig[currentStateKey] || 1;
const randomIdx = Math.floor(Math.random() * maxBgCount) + 1;
const bgUrl = `/images/bgs/${folder}/bg-${randomIdx}-${suffix}.webp`;
document.documentElement.style.setProperty(
"--dynamic-bg",
`url('${bgUrl}')`,
);
})();
</script>
使用Front Matter字段实现帖子封面和专属背景
除了全局随机背景,有时我们也需要针对单篇文章配置独立的元信息。Hugo 提供了强大的 Front Matter(前置元数据)解析能力。
PaperMod 原生支持的字段可以直接写在文章头部的元数据区域(以 TOML 格式为例):
+++
title = 'Java泛型详解'
date = 2026-07-22T16:41:37+08:00
description = '深入理解 Java 泛型与类型擦除机制'
ShowToc = true # 开启单页目录
weight = 1 # 置顶权重
# PaperMod 原生封面图字段
[cover]
image = "images/java-cover.webp"
alt = "Java 封面图"
caption = "图片说明"
hiddenInSingle = false
+++
如何用 Front Matter 字段传递变量实现自定义背景?
我们可以自定义一个 custom_bg 字段。当该字段存在时,覆盖全局的随机背景脚本。
首先在特定文章的 Front Matter 中声明:
+++
title = '忙碌的一天'
date = 2026-07-16T22:00:00+08:00
custom_bg = "/images/special-bg.webp"
+++
然后修改 layouts/partials/extend_head.html,利用 Hugo Template 引擎的条件分支控制逻辑输出:
{{ if .Params.custom_bg }}
<script>
// 存在单页专属背景时,直接注入该图片路径
document.documentElement.style.setProperty(
"--dynamic-bg",
`url('{{ .Params.custom_bg }}')`,
);
</script>
{{ else }}
<script>
// 不存在专属背景时,回退到原有的随机背景计算逻辑
(() => {
const bgConfig = {
"desktop-light": 5,
"desktop-dark": 3,
"mobile-light": 4,
"mobile-dark": 2,
};
const isDark =
localStorage.getItem("pref-theme") === "dark" ||
(!localStorage.getItem("pref-theme") &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
const isMobile = window.innerWidth < 768;
const folder = isMobile ? "mobile" : "desktop";
const suffix = isDark ? "dark" : "light";
const currentStateKey = `${folder}-${suffix}`;
const maxBgCount = bgConfig[currentStateKey] || 1;
const randomIdx = Math.floor(Math.random() * maxBgCount) + 1;
const bgUrl = `/images/bgs/${folder}/bg-${randomIdx}-${suffix}.webp`;
document.documentElement.style.setProperty(
"--dynamic-bg",
`url('${bgUrl}')`,
);
})();
</script>
{{ end }}
番外:怎么选择好的背景图片
搞这么多技术内容,本质上的目的还是让我们的博客好看。对此,图片的选择往往是最容易被忽略的部分。 我个人为博客筛选了约数百张图片,对于背景图片的选取略有心得。
- 明确你需要什么主题的图片
- 注意博客页面对你的背景的遮挡作用
- 不要喧宾夺主
关于背景图片选取的更多内容,请参考我的另一篇文章:怎样挑选背景图片。
至此,一个兼顾全局随机分发、响应式设备切图、暗色模式适配以及单页独立定制的博客背景系统就全部搭建完成了。整个方案完全脱离了第三方图床 API 的依赖,在保持纯静态架构高可用性的同时,兼顾了优雅的 UI 呈现。