实现背景图全屏效果
设置背景图为全屏效果,可以使用 fluid 提供的注入代码功能,可以将代码无侵入式加入到主题里。
在根目录下新建一个 scripts 目录,在目录内新建:injector.js
建好之后,博客的根目录大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ├─📁 .github/ ├─📁 scaffolds/ ├─📁 scripts/ ├─📁 source/ ├─📁 test/ ├─📁 themes/ ├─📄 _config.fluid.yml ├─📄 _config.landscape.yml ├─📄 _config.yml ├─📄 .gitignore ├─📄 package-lock.json ├─📄 package.json ├─📄 template.md └─📄 yarn.lock
|
⚠️注意:
该文件不需要在_config.fluid.yml 中进行引用,hexo 自动调用执行里面的 js 文件
1 2 3
| const { root: siteRoot = "/" } = hexo.config; hexo.extend.injector.register("body_begin", `<div id="web_bg"></div>`); hexo.extend.injector.register("body_end",`<script src="${siteRoot}js/backgroundize.js"></script>`);
|
文件名其实可以随意,但接下来的 js 文件名请与 src="${siteRoot}js/backgroundize.js" 中保持一致。
如果你不需要背景图随日夜主题切换而切换的话,那么你只需要新建 backgroundize.js 并写入以下代码即可:(source\js\backgroundize.js)
1 2 3 4 5 6 7 8 9 10 11
| document .querySelector('#web_bg') .setAttribute('style', `background-image: ${document.querySelector('.banner').style.background.split(' ')[0]};position: fixed;width: 100%;height: 100%;z-index: -1;background-size: cover;`);
document .querySelector("#banner") .setAttribute('style', 'background-image: url()')
document .querySelector("#banner .mask") .setAttribute('style', 'background-color:rgba(0,0,0,0)')
|
第一段代码的功能是:找到根节点,在页面的 body 开始部分注入一个 div 元素,其 id 为 web_bg,作为背景图像的容器,然后在页面的 body 结束部分注入 js 文件(backgroundize.js)进行引用,确保脚本在页面加载完成后执行。
第二段代码的功能是:提取现有.banner 元素的图像 URL,将提取到的图像 URL 应用到 web_bg 元素上,同时设置背景样式为 fixed 固定定位、覆盖整个页面、z-index: -1 并置于其他元素之下。
至此,背景图就全屏且固定了。
实现背景图随日夜主题切换效果
看到这也就说明你需要标题所述功能。那么请将_config.fluid.yml 中涉及到 banner_img 的部分全部配置为空,因为我们要手动设置背景图。
例如首页 banner 和文章页 banner(可以通过搜索 banner_img 提高效率):


在 source/js 目录中新建或修改 backgroundize.js 文件,我的代码文件如下:backgroundize.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 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
|
const colorToggleButton = document.querySelector("#color-toggle-btn .nav-link");
const backgroundContainer = document.querySelector("#web_bg");
const BACKGROUND_IMAGES = { dark: { desktop: "URl1", mobile: "mobileURL", }, light: { desktop: "URL2", mobile: "mobileURL", }, fallback: "fallbackURL", };
const PAGE_EXCLUSIONS = [ "/links/", "/about/", "/categories/", "/tags/", "/collections/", ];
const isExcludedPage = (path) => { if (path === "/") return true; return PAGE_EXCLUSIONS.some((excluded) => path.startsWith(excluded)); };
const isMobileViewport = () => window.innerWidth < 768;
const removeBannerMask = () => { const mask = document.querySelector("#banner .mask"); if (mask) mask.style.backgroundColor = "rgba(0,0,0,0)"; };
const preloadBackgroundImages = () => { const urls = new Set(); Object.values(BACKGROUND_IMAGES).forEach((entry) => { if (typeof entry === "object") { urls.add(entry.desktop); urls.add(entry.mobile); } else { urls.add(entry); } }); urls.forEach((url) => { const img = new Image(); img.src = url; }); };
const applyBackgroundImage = (mode, customUrl) => { const mobile = isMobileViewport(); let imageUrl;
if (customUrl) { imageUrl = customUrl; } else if (mode === null) { imageUrl = mobile ? BACKGROUND_IMAGES.dark.mobile : BACKGROUND_IMAGES.fallback; } else if (!isExcludedPage(location.pathname) && mode) { imageUrl = mobile ? BACKGROUND_IMAGES[mode].mobile : BACKGROUND_IMAGES[mode].desktop; } else { imageUrl = mobile ? BACKGROUND_IMAGES.dark.mobile : BACKGROUND_IMAGES.fallback; }
backgroundContainer.style.backgroundImage = `url(${imageUrl})`; };
const getUserColorScheme = () => { const mode = document.documentElement.getAttribute("data-user-color-scheme"); return mode === "dark" ? "dark" : "light"; };
const handleColorSchemeToggle = () => { const mode = getUserColorScheme(); if (!isExcludedPage(location.pathname)) { applyBackgroundImage(mode); } removeBannerMask(); };
let resizeTimer;
const handleViewportResize = () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { applyBackgroundImage(getUserColorScheme()); }, 200); };
const observeColorSchemeChange = () => { const targetNode = document.documentElement; const config = { attributes: true, attributeFilter: ["data-user-color-scheme"], };
const callback = (mutationsList) => { for (const mutation of mutationsList) { if ( mutation.type === "attributes" && mutation.attributeName === "data-user-color-scheme" ) { handleColorSchemeToggle(); } } };
const observer = new MutationObserver(callback); observer.observe(targetNode, config); };
const initializeBackgrounds = () => { preloadBackgroundImages();
const currentPath = location.pathname; const currentMode = getUserColorScheme();
if (PAGE_EXCLUSIONS.includes(currentPath)) { applyBackgroundImage(null); } else { applyBackgroundImage(currentMode); }
removeBannerMask(); };
if (!colorToggleButton) { console.warn("未找到颜色模式切换按钮"); }
observeColorSchemeChange();
window.addEventListener("resize", handleViewportResize, { passive: true });
window.addEventListener("DOMContentLoaded", () => { console.log("DOM 完成,开始初始化背景图。"); initializeBackgrounds(); });
window.addEventListener("load", () => { console.clear(); });
|
该代码文件还需结合一个自定义的 css 文件使用:source/css/backgroundize.css
1 2 3 4 5 6 7 8 9 10
| #web_bg { position: fixed; width: 100%; height: 100%; z-index: -1; background-size: cover; transition: all 0.5s; }
|
在 backgroundize.js 文件中,我们手动指定了背景图、切换逻辑,所以上一节的第二段代码已不再有作用,故在此删去。
在 backgroundize.js 文件中,若你有更多自定义页面图片需求,你将能够很方便的实现它。
在 backgroundize.css 中,我们依然将 web_bg 设置为 fixed 将其固定,并设置 z-index: -1 保持沉底。
因为在_config.fluid.yml 中我们将 banner_img 配置项全部配置为空,那么控制台将会出现 404 错误:

眼不见心不烦,所以在 backgroundize.js 中存在下面的代码:
1 2 3 4
| window.addEventListener("load", () => { console.clear(); });
|
⚠️注意:
以上文件,别忘记在_config.fluid.yml 文件中的 custom_css 中进行引用。
URl1、mobileURL 和 fallbackURL 等字符,仅作占位使用,实际应为真实图片链接。
因为 injector.js 的存在,所以 backgroundize.js不需要在 custom_js 中引入,否则控制台中将会出现以下报错:
1
| Uncaught SyntaxError: Identifier 'colorToggleButton' has already been declared (at backgroundize.js:1:1)
|
_config.fluid.yml 配置项:
1 2
| custom_css: - /css/backgroundize.css
|
预加载的可能问题
如果你遇到了第一次切换主题,图片与图片之间的切换有些生硬,有点闪屏的感觉,但后续的所有切换却十分丝滑,那么很可能是预加载的失败。
可能的问题来源
如果 OSS/Bucket 设置了响应头 Cache-Control: no-store 或 max-age=0 或没有启用 Expires,浏览器会认为每次访问都需要重新拉取。也就是尽管你预加载了,但切换时图片链接仍然会触发一次真正的请求。
我遇到的问题是,我使用的图片托管在阿里云的 oss 上,阿里云返回的响应头中存在:
1 2
| x-oss-force-download: true Content-Disposition: attachment
|
而 Content-Disposition: attachment 的含义是:这个资源是供下载用的,而不是网页内容的一部分。
浏览器遇到这种响应头时,通常会绕过图片的常规缓存路径,或者不会把它作为正常的预加载资源处理。
这就导致:
- 图片不会进入
memory 或 disk cache
- 即使预加载代码写得很好,浏览器仍然会每次点击切换时发起新请求
- 导致首次切换背景图时出现 “闪屏或延迟加载”
翻阅文档,发现阿里云在很久之前就已经做出了调整:
出于安全考虑,通过OSS默认 Bucket域名访问文件时,OSS会强制增加下载响应头,导致浏览器强制下载文件。
也就是强制增加了 x-oss-force-download: true 使得 Content-Disposition 为附件类型。
如果遇到其他问题,欢迎讨论。
解决办法
如果你的托管平台可以更改元信息,那么请更改所需文件的元信息:
阿里云平台是无法通过更改文件元信息来解决的。
阿里云的推荐方案是为 oss/bucket 绑定自定义域名,不再使用阿里云提供的默认域名。详见:绑定自定义域名至 Bucket 默认域名
或者,你也可以将图片托管在 GitHub 上,就直接使用仓库的 img 目录用于存放,GitHub 的响应头是符合要求的。
非预期的切换效果
如果你遇到了第一时间显示的不是预期的背景图,那么很可能是你在_config.fluid.yml 文件中的 banner_img 没有全部配置为空。
无法匹配页面 / 排除页面不生效
这一般是因为你的网站的永久链接生成方式与我不同。
该配置项通常在_config.yml 文件中可以找到。我的配置如下:
1 2 3 4 5 6 7 8 9
|
url: https://4rozeN.github.io permalink: archives/:category/:abbrlink.html abbrlink: alg permalink_defaults: defaultPermaLink pretty_urls: trailing_index: true trailing_html: true
|
详见 Hexo 官方文档:【Hexo】- 永久链接(Permalinks)
什么意思呢?
我的代码中,写死了部分排除在外不会跟随日夜模式切换背景图的页面,这些页面都是能够被
1 2 3 4 5 6 7 8 9 10 11 12 13
| const PAGE_EXCLUSIONS = [ "/links/", "/about/", "/categories/", "/tags/", "/collections/", ];
const isExcludedPage = (path) => { if (path === "/") return true; return PAGE_EXCLUSIONS.some((excluded) => path.startsWith(excluded)); };
|
校验语句判断为真返回 true 的。但如果你的网站使用如:
2013/07/14/hello-world/
2013-07-14-hello-world.html
foo/bar/hello-world/
- ……
等等方案,我很难每个都适配,只得你自己进行修改。
修改其实也很简单,比如日期类的永久链接:/2025/04/26/xxx/
你可以修改 excludePages 中的字符串为正则匹配符:/^\/\d{4}\/\d{2}\/\d{2}\//
校验函数也可以修改为:
1 2 3 4
| const isExcludedPage = (path) => { return excludePathPatterns.some(pattern => pattern.test(path)); };
|
总之,方法有很多。如果你没有这方面的基础,你也可以借助 AI 工具实现。当然,如果你的网站刚建立,更换永久链接是一件损失非常小的事情,如果你不想折腾的话,你也可以更换永久链接来适配代码。
⚠️ 注意:
更换永久链接意味着您的网站在各搜索引擎中的收录索引将会失效,重新索引可能需要一段时间,重新索引完成的时间将取决于您网站的流量高低。当然,您也可以手动前往提交索引请求,但这只是起到告知作用,重新索引完成的时间仍然未知。若您仍不明白这将有何影响,建议您不要更换永久链接。
如果您愿意更换永久链接生成方式,想暂时解决搜索引擎索引问题,可以考虑使用 hexo-generator-alias 插件,它可以帮助你实现旧链接重定向到新链接的效果。
毛玻璃底页效果
-
修改_config.fluid.yml 文件的主面板背景色
1 2 3 4 5 6
| # 主面板背景色 # Color of main board -board_color: "#ffffff" -board_color_dark: "#252d38" +board_color: "#ffffff80" +board_color_dark: "#00000080"
|
-
source/css 目录下新建 glassBg.css 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #board { -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); }
#toc { padding: 10px; top: 4rem; background-color: var(--board-bg-color); border-radius: 10px; -webkit-backdrop-filter: blur(var(--background-blur)); backdrop-filter: blur(var(--background-blur)); }
|
侧边的目录区域毛玻璃效果如果不需要可以删除。
一些好用的插件
Hexo 官方推荐插件就足够:https://fluid-dev.github.io/hexo-fluid-docs/advance/#hexo - 插件
致谢
感谢 @banyee19 在日夜背景图切换方面的探究,我一度以为我的切换效果是正常的,但使用其他设备进行查看后才发现效果无法正确实现,故此重构。
相关文章: