一、写在前面
大多数博客的首页第一屏是一张固定的大图,换主题换模板都得重新配。Joe3 Next 主题把这块做成了一个纯配置项——改一行 JSON 就能换成任意图片地址,不需要碰模板、不需要重启 Halo。
这篇拆解 www.wycha.top 首页大图的完整实现:图到底从哪来、怎么渲染的、改配置要注意什么。
二、渲染机制:CSS background,不是 img 标签
首页模板 index.html 里只有一行判断和一次片段引入:
<th:block th:if="${theme.config.beauty.enable_big_banner}">
<th:block th:replace="~{modules/macro/big_banner :: big_banner(${title})}" />真正的实现落在 templates/modules/macro/big_banner.html:
<div id="EvanBigBanner" class="evan-big-banner"
th:style="'background-image:url('+${theme.config.beauty.big_banner_Photos}+');'
+${htmlType == 'index'? 'height:calc(100vh - 60px);margin-top: initial;':''}">三个设计要点:
纯 CSS background-image。整张大图不是
<img>标签,而是容器元素的背景图。好处是可以用background-size、background-position做各种裁切适配,不需要额外的图片标签参与布局。首屏满屏。首页场景下高度是
calc(100vh - 60px),减掉的 60px 是站点头部导航的高度。这段高度只在htmlType == 'index'时拼接,文章页、分类页用的是另一套高度。底部 SVG 波浪。容器里内嵌了一个
#EvanWavesSVG,用四条<use>引用同一个gentle-wave路径、分别偏移 0/3/5/7 像素,做出视差滚动的层叠效果。
此外还有两个可选图层:
视频层。
enable_big_banner_video为真时,容器里会叠一个<video>元素,样式是width:100%; height:100%; object-fit:cover,配合autoplay、loop、muted实现自动播放的背景视频。本站当前是关闭状态。文字层。包含站点标题和一个「一言」引用位,标题在模板里写死
opacity: 0先隐藏,等 JS 加载完成后淡入。
三、图片从哪里来
答案是:来自外部随机图 API,不是本站上传的附件。
配置存在主题的 ConfigMap 里,路径是 beauty 这一组。本站当前的实际配置值如下:
那个随机图 API 的实际行为
对 https://imgapi.xl0408.top/index.php 连续请求三次,实测结果:
可以归纳出这个 API 的三个特征:
每次都换图。不是缓存同一张,而是每次请求随机返回不同的图片。
图源是漫画图。最终 302 跳转到
raw.gitcode.com上的comic/xx.jpg路径,属于从 GitCode 镜像站拉取的图库。体积波动极大。从 86 KB 到接近 2 MB 都有,图片分辨率是 1920×1080 的 JPEG。这个波动对首屏加载体验影响不小。
四、文字层的数据流
标题和一言都由 assets/js/beauty.js 里的 EvanBigBanner 对象在运行时填充:
this.dom.bigBanner = $("#EvanBigBanner");
this.dom.bannerTitle = $("#EvanBigBanner_Title");
this.dom.bannerSubTitle = $("#EvanBigBanner_SubTitle");
this.dom.hitokotoText = $("#HitokotoText");
this.dom.hitokotoForm = $("#HitokotoForm");一言内容来自 hitokoto 的公开接口 https://v1.hitokoto.cn/,实测返回 200 和一段 JSON。
顺带一提,hitokoto 的 v2 接口(v2.hitokoto.cn)实测请求超时,但主题 JS 用的是 v1,所以不影响使用。如果哪天 v1 挂了、页面上的「一言」区域变空,先查这个接口而不是查主题。
五、怎么改:改数据,不改模板
大图的所有可调项都在主题的 ConfigMap 里,编辑端点是:
GET /api/v1alpha1/configmaps/theme-Joe-configMap注意路径是 /api/ 单数、没有 group 段,和 Halo 其它扩展资源(/apis/content.halo.run/v1alpha1/...)的路径规律相反,按组名推断必然 404。
改设置不用重启 Halo。ConfigMap 是数据不是模板,PUT 完刷新页面就生效。只有改模板文件才需要重启(Thymeleaf 不热重载)。
data.beauty 是 JSON 字符串,要解两次。第一层是 ConfigMap 的 data 映射,第二层才是 beauty 组本身的 JSON。
PUT 是整体替换。只改
big_banner_Photos时,beauty 组里其余的键必须原样回写,否则该组所有设置会一起丢失。
六、别和轮播 banner 搞混
首页其实有两套大图机制,很容易混淆:
首页那些带 lazyload 的 <img> 标签(指向 /upload/*.jpg)是文章列表卡片的封面,跟首屏大图没有关系。
七、两个值得注意的点
1. 首屏依赖第三方服务
大图地址指向站外 API,那个服务挂了或变慢,首屏就会空白。因为用的是 CSS background,浏览器不会因此报错——页面照样 200,只是图出不来。这种故障排查起来很费劲,因为服务端日志里什么都没有。
2. 图片没有落地到本地
每次访问都要打一遍那个 API,再被 302 跳到 GitCode 拉图。首次加载可能几百 KB,也可能接近 2 MB。
想优化的话,把图下载到本站的 /upload/ 目录,再把 big_banner_Photos 指向站内路径就行——background-image:url() 对站内相对路径同样支持,不需要改任何模板代码。
评论区