首页轮播图在不同窗口宽度下变形,排查发现根因是 object-fit:fill 配合写死高度,改图尺寸无法解决。最终确定 Banner 标准:画布 1640×700,CSS 用 aspect-ratio 与 object-fit:cover,移动端 16:9,并留文字安全区、字号按两倍制作。
前几天把首页三张幻灯片换成了自己画的 banner,第二天用笔记本打开一看,标题胖了一圈,字是横向压扁的。一开始我以为是图没画好,改了两版字号还是不对劲。把浏览器窗口从 1920 一路拖到 400,发现变形率每个宽度都不一样——这就不是图的问题了,是渲染的问题。
这篇把整个排查过程记下来:怎么量、根因在哪、为什么改图尺寸救不了,最后给出我定下来的一套 Banner 标准(画布 1640×700,配一段 CSS),以及画图时踩到的三个文字遮挡坑。如果你也在用 AeroCore Pro 或者别的把轮播高度写死的主题,应该用得上。
顺带一提,站点里图片相关的问题之前也写过一篇:WordPress 插入大图看不清怎么办:多档尺寸怎么选 + 灯箱滚轮缩放改造,那篇讲的是正文插图,这篇讲的是首页轮播,两个是不同的坑。
一、先看现象:同一张图,不同窗口宽度胖瘦不一样
我当时的第一反应是截图对比。后来发现截一张不够,得把窗口宽度一个一个试过去,因为变形量跟容器宽度直接挂钩。
首页这个轮播在 AeroCore Pro 里是一个 swiper 容器,桌面端宽度恒定 820px、高度 350px(后台的滑块高度设置会写进内联变量 --slider-h)。问题出在窄一点的窗口:769px 到 1279px 这个区间里,容器宽度会跟着缩,高度却还是 350px。实测下来是这样:
| 视口宽度 | 容器实际渲染 | 显示比例 | 对 1720×640 的变形 |
|---|---|---|---|
| ≥1280 | 820 × 350 | 2.343 | 纵向拉伸 12.8% |
| 1024 | 664 × 350 | 1.897 | 纵向拉伸 29.4% |
| 820 | 521 × 350 | 1.489 | 纵向拉伸 44.6% |
| 768 及以下 | 721 × 200 | 3.605 | 纵向压扁 34.1% |
| 390(手机) | 358 × 200 | 1.790 | 纵向拉伸 33.4% |
820 宽那个断点最离谱,容器变成了 521×350,比例 1.489,一张 2.688 的横图塞进去等于被拉高了一倍多。文字全是歪的。
二、别猜,先量:用无头浏览器抓真实渲染尺寸
查 HTML 源码是看不出这些的,宽度是百分比、高度来自 CSS 变量,最后还有 JS 布局参与。我直接起了个无头 Chrome 连 CDP,在每个视口宽度下取 img.getBoundingClientRect():
// measure.js —— node measure.js
// 把轮播图在多个视口宽度下的真实渲染尺寸打出来
const widths = [1920, 1600, 1440, 1280, 1024, 820, 768, 480, 390];
for (const w of widths) {
await send("Emulation.setDeviceMetricsOverride",
{ width: w, height: 900, deviceScaleFactor: 1, mobile: w < 700 });
await sleep(900);
const v = await eval_js(`(() => {
const img = document.querySelector('.swiper-slide img');
const r = img.getBoundingClientRect(), cs = getComputedStyle(img);
return { w: r.width, h: r.height, fit: cs.objectFit,
natW: img.naturalWidth, natH: img.naturalHeight };
})()`);
const squash = (v.w / v.h) / (v.natW / v.natH);
console.log(w, v.w + "x" + v.h, "fit=" + v.fit,
(Math.abs(squash - 1) * 100).toFixed(1) + "%");
}
关键是 cs.objectFit 这一列,它直接告诉了你答案。
三、根因:object-fit:fill 加上写死的高度
主题的轮播 CSS 是这么写的:
.slider-component .swiper{width:100%;height:var(--slider-h, 400px)}
.slider-component .swiper-slide img{width:100%;height:100%;display:block}
图片元素上还挂着一段内联样式:
<img src="..." style="object-fit:fill;object-position:left top" />
这几个条件凑一起就成了必然:
- img 的盒子和容器一样大,宽度百分比、高度写死;
object-fit:fill的意思是”不管原图比例,把内容拉满整个盒子”,这是它的默认行为,也是最容易踩的一个值;- 容器宽度随断点变、高度不变,于是盒子的比例在每个断点都不同。
fill、cover、contain 三者的差别,画出来最直观:

cover 是等比缩放然后裁掉超出部分,contain 是等比缩放留白。这两种都不会让内容变形,代价是可能裁切或者留白。
四、为什么”把图做对尺寸”救不了
我一开始真的去改画布了。把 1720×640 改成跟桌面容器一样的 2.343:1,宽屏下确实好了。然后把窗口一缩,820 宽那个断点又变形了——容器是 521×350,比例 1.489。
把容器比例的区间列出来就明白了:1.489 到 3.605。一张静态图片只有一个比例,你不可能同时等于这五个数。所以只要渲染端还是 fill,怎么改图都会在某个断点翻车。
出路只有一条:让渲染端不许变形,再把图片比例对齐到最常用的那个断点,把裁切量压到零。
五、我定下来的 Banner 标准 v1
1. 画布 1640×700
桌面容器 820×350,比例 2.342857。源图做 1640×700,正好是这个比例的两倍。这样桌面端 1:1 显示,不缩放、不裁切、不变形。
为什么不是 820×350 原尺寸?高分屏和缩放会糊,两倍图是底线。格式统一 webp(quality 92),别再往站里塞 jpg/png。
2. 渲染端必须改 CSS
把下面这段放进主题的 custom_css(后台自定义 CSS 输入框就行):
/* 首页轮播:高度跟随图片比例,杜绝 object-fit:fill 拉伸 */
.slider-component .swiper{height:auto!important;aspect-ratio:1640/700}
.slider-component .swiper-slide img{object-fit:cover!important;object-position:center!important}
@media (max-width:768px){
.slider-component .swiper{aspect-ratio:16/9}
}
两个地方必须带 !important,因为 object-fit:fill 是内联样式,普通样式表盖不过它。
效果:桌面 820 宽下高度算出来正好 350px,跟原来一模一样,视觉上零变化;窄屏时高度自动收缩(664 宽 → 283px,521 宽 → 222px),不再拉伸。移动端我改成了 16:9,在 390 宽的手机上高度是 219px,比写死的 200px 还稍微舒展一点。
3. 安全区:文字往中间放
cover 加 aspect-ratio 之后,桌面和中间断点都不裁切了,但移动端 16:9 会裁掉左右各 12%。所以文字不能顶边放:

| 级别 | 区域(以 1640×700 计) | 放什么 |
|---|---|---|
| L1 必读区 | 水平 12%~88%,垂直 10%~90% | 标题、副标题、描述、标签、代码卡片 |
| L2 出血区 | 水平 5%~95% | 大号装饰符号、辉光、纯色块 |
4. 字号 = 期望显示字号 × 2,最小 26px
这个是我第二处翻车的地方。桌面显示是源图的 0.5 倍,我在源图上写 22px 的描述文字,页面上就只剩 11px。别问怎么看出来的,问就是眯着眼看了半天。
| 元素 | 桌面显示目标 | 源图字号 |
|---|---|---|
| 主标题 | 32px | 64px(放不下可降到 50px) |
| 副标题 | 18px | 36px |
| 描述正文 | 13~14px | 26~28px |
| 标签 / 代码 | 13px | 26px |
5. 中英混排要逐字换字体
Consolas 没有中文字形,把中文字符塞给它只会得到豆腐块。代码类的图里我用了逐字符判断:汉字用微软雅黑,其余用 Consolas。另外不要对文字做横向压缩,等比缩放是底线。
六、三个遮挡坑,一个比一个隐蔽
上面的标准解决了拉伸,接着我碰到了遮挡。三张图都出过问题,记下来省得再犯。
坑一:从底部倒推定位,描述压到标签上
第一版我是先定标签的位置,再往上倒推描述、副标题、标题的 y 坐标。结果描述有两行,最后一行的底部到了 523px,标签胶囊顶部在 504px,重叠了 19px。倒推的算法对行数变化太敏感,多一行就全乱。后来改成了自上而下的流式排版,每块之间强制留至少 8px。
坑二:用 Consolas 量宽度,却用雅黑去画
这个坑最阴。标题里有英文(”OpenWrt + MosDNS + AdGuard”),我折行时按 Consolas 的字符宽度算,画的时候整行都用了雅黑。雅黑的拉丁字母比 Consolas 宽,实测那行标题渲染出来 954px,上限是 640px——直接冲出左栏,压到了右边终端卡片上,重叠 24×20px。
修法很朴素:测量和绘制必须用同一套字体。纯中文的块就用中文字体测量,不要混。
坑三:装饰元素画在了文字上
两个子问题。一是左侧那根强调竖条画在 x=220,标题也从 x=220 起,竖条直接压在标题第一个字上,挪到 x=192 就好。二是扫描线、网格、胶囊的半透明底色,我原来是把它们画在一个覆盖层上,最后才合成到整张图上面,等于给所有文字蒙了一层纱。后来把渲染顺序改成严格的分层:渐变底 → 扫描线 → 网格和辉光 → 装饰符号 → 胶囊底色 → 最后才是文字。
七、让脚本自己找遮挡
看图找遮挡不靠谱,我眼睛骗了我一次。现在生成器里每个文字块都会记下自己的包围盒,出完图跑一遍两两相交检测:
def validate(rects):
"""rects: [(name, x0, y0, x1, y1)] —— 两两相交或越出 L1 即报错"""
errs = []
for i in range(len(rects)):
n0, x0, y0, x1, y1 = rects[i]
if x0 < L1[0] or x1 > L1[2] or y0 < L1[1] or y1 > L1[3]:
errs.append(f"[越界] {n0} 超出安全区 {L1}")
for j in range(i + 1, len(rects)):
n1, a0, b0, a1, b1 = rects[j]
if x0 < a1 and a0 < x1 and y0 < b1 and b0 < y1:
ov_w = min(x1, a1) - max(x0, a0)
ov_h = min(y1, b1) - max(y0, b0)
errs.append(f"[遮挡] {n0} x {n1} 重叠 {ov_w:.0f}x{ov_h:.0f}px")
return errs
跑起来的输出长这样,每个块的坐标都打出来,有问题一眼能定位:

规则很简单:python make_slides.py 末尾必须是 总计问题: 0,不是 0 就不许上传。这条比我肉眼可靠多了。
八、上线前我核对的四件事
- 出图校验问题数为 0;
- custom_css 那段生效,
getComputedStyle(img).objectFit是cover; - CDP 在 1920 / 1440 / 1024 / 820 / 390 五个宽度下量一遍,变形率都接近 0%;
- 手机上看一眼关键字没被裁掉。
装完之后
这套标准落地之后,首页轮播在五个断点上都是干净的,字号也能看清楚了。相关几篇:
- PVE 安装 img 格式虚拟机教程:qm importdisk 图文全流程 —— 三张新 banner 之一,里面那张终端卡片的命令就是从这篇里抄的
- OpenWrt + MosDNS + AdGuard Home 全网去广告搭建教程 —— 第二张 banner 对应的文章,nft 劫持 53 端口那套
- 国内外常用 DoH/DoT 服务汇总(含 IPv4/IPv6 与接入地址) —— 第三张 banner,加密 DNS 的接入地址表
- 把首页那个节日日历插件开源了:农历节气靠算,图片零外链 —— 同样是首页的定制,讲怎么用 PHP 直接画 SVG
有别的主题也把轮播高度写死的话,思路是通的:先量,再改渲染,最后才轮到改图。
- 文章名称:AeroCore Pro 首页幻灯片变形:object-fit:fill 的坑和一套 1640×700 Banner 标准
- 文章永久网址:https://www.997wan.com/aerocore-slider-banner-standard/
- 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长 997笔记 进行删除处理。
- 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
- 本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
- 本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
- 如无特别声明本文即为原创文章仅代表个人观点,版权归《997笔记》所有,欢迎转载,转载请保留原文链接。