用 alt 后缀驱动的 WordPress 密集墙排版:从 float 到 flex-wrap 与纵向叠列

用 alt 后缀驱动的 WordPress 密集墙排版:从 float 到 flex-wrap 与纵向叠列-知集
用 alt 后缀驱动的 WordPress 密集墙排版:从 float 到 flex-wrap 与纵向叠列
此内容为付费阅读,请付费后查看
¥1
立即购买
您当前未登录!建议登陆后购买,可保存购买订单
付费阅读
阅读约 82 分钟24,443 字

摘要:本文记录 zibll 子主题「图片宽度排版」模块从需求到落地的完整过程:如何在不动编辑器、不给图片加自定义字段的前提下,用 alt 后缀驱动宽度档位;为什么必须弃用 float 改用 flex-wrap 画廊;以及「25% 宽的列里上下叠两张图」这种嵌套拼图怎么实现。文中给出核心算法代码、线上实测验证数据与 7 条踩坑记录。
适合阅读:WordPress 主题 / 插件开发者,以及需要图片墙排版的站长。

一、需求:到底要什么样的密集墙

需求最初只有一句话:「文章里加一个段落,全放图片,不要出现小段落」。真正做下去才发现,这句话背后压着五条硬约束:

  1. 纯图片:段内不能混文字,否则图会跟着文字流走;
  2. 每行宽度和恰好 100%:既不能超(溢出换行),也不能少(右侧留白);
  3. 底边齐平:同一行的图片下沿必须在一条线上;
  4. 支持纵向叠放:一个 25% 宽的格子里上下叠 2 张甚至 4 张图,做出嵌套拼图;
  5. 写法极简:作者只在图库里改 alt,不需要记任何布局语法或短代码。

其中第 2 条是踩过坑才定下来的。最初我理解成「只要不超过 100% 就行」,于是把各种组合装箱上墙,结果页面上出现大量右侧空白的半行——被当场打回。必须恰好等于 100%,这条后来成了所有算法的验收标准。

二、方案设计

2.1 语法:把宽度藏进 alt

WordPress 后台没有给图片提供「宽度」字段,加自定义字段又太重。alt 是唯一随手可改、且语义上允许带额外信息的属性。于是约定在 alt 描述末尾加后缀:

描述-宽度 [left|right] [s] [0]

档位:20 / 25 / 33 / 50 / 75 / 100
  s      → 这张图参与纵向叠放(stack)
  0      → 这张图进无缝贴边墙(间距归零)
  left/right → 图文混排时浮动方向

示例:
  alt="雪山日落-50"      → 占正文 50% 宽
  alt="雪山日落-25 s"    → 与相邻同档位 s 图叠成一列
  alt="雪山日落-33 0"    → 1/3 宽,且所在段落升级为无缝墙

宽度信息只做一件事:转成 class(fig-50)挂到 img 上,然后把 alt 的宽票后缀剥掉——alt 最终只留纯描述,不影响无障碍语义和 SEO。

2.2 布局模型:为什么必须弃用 float

这套功能最早是从 Typecho 主题 Yodu 移植来的。Yodu 的原实现是纯 float 模型:

.figure.fig-25,.figure.fig-50,.figure.fig-75 { float:left; margin:2px; }
.figure.fig-25 { width:calc(25% - 4px); }
.figure.fig-50 { width:calc(50% - 4px); }

float 的好处是「作者随便写都能自动挤」,但代价恰恰是需求里的第 3 条:图片高度不一致时,下一行的图会卡进矮图下方的空隙,整墙错行、底边参差。v1 的源码注释里就把这个现象当成 bug 记着,改成 flex 画廊后才解决。两种模型对比:

维度float + marginflex-wrap 画廊
行内底边天然不齐(高度参差)由 align-items 决定,可强制等高
换行行为矮图下方会被下一张插入严格按宽度换行,绝不交错
宽度精度margin 参与盒模型,易算错flex-basis 扣掉 gap,精确
叠放支持不支持(无列容器)列容器是行内 flex item,天然支持

最终采用 display:flex; flex-wrap:wrap; gap,每个格子 flex: 0 0 calc(N% - var(--zl-gap)),间距用 CSS 变量注入、后台可配。

2.3 数学约束:只认「和恰好 = 100」的组合

既然每行必须铺满,那能用的组合就不再是「随便挑」,而是六档位里凑满 100 的有限解——正好 10 种有序排法:

100
75 + 25  |  25 + 75
50 + 50
50 + 25 + 25  |  25 + 50 + 25  |  25 + 25 + 50
33 + 33 + 33        (33 实际按 33.3333% 渲染,三张恰好铺满)
25 + 25 + 25 + 25
20 + 20 + 20 + 20 + 20

⚠️ 这里有个容易写错的校验:33 必须按 100/3 参与求和,不能拿字面值 33 去硬卡 array_sum() === 100——那样 33×3=99 会被判为不合法,而 CSS 侧三张 33.3333% 其实是恰好铺满的。

2.4 叠列:列宽与列内图数无关

叠列是整个功能里最关键的一个抽象:一个叠列在行宽账上只占「档位」这一格,不管里面叠了几张图。

alt="A-25 s" + alt="B-25 s" + alt="C-75"
        ↓
┌──────┬────────────────────┐
│ A    │                    │
├──────┤        C (75%)     │
│ B    │                    │
└──────┴────────────────────┘
   25%           75%         → 行宽 = 100%

所以「1 张 25% 的图」和「25% 宽、内叠 4 张的列」在行宽账上完全等价——这是后面能保证每行恒定 100% 的数学基础。

三、实现

3.1 单图打标:一条正则 + 边界判定

第一步是扫出所有 img,解析 alt 后缀,把宽度写进 class。正则要同时认档位、方向、叠放、贴边四个可选段:

#^(.*S)s*-s*(d{2,3})(?:s+(left|right))?(?:s+(s))?(?:s+(0))?$#i

解析完还要做几组互斥判定,宁可「不认」也不能产出半失效的组合:left/right 与 0 互斥(float 靠 margin 造间距,无缝墙靠 gap 归零,同时开会打架);s 与两者都互斥(叠列是纵向布局)。

3.2 按「行」装格:叠列分组算法(核心)

这是唯一一处真正有难度的逻辑。第一版我写成「连续 + 同档位就合并」,线上实测铺满率直接掉到 0.74,两个坑:① 上一行末尾的 25s 25s 和下一行开头的 25s 25s 连成一段,被并成一个 4 图列,后面整行宽度账错位;② 连续图里混进一个不同档位,整段被放弃,页面上残留一堆没有样式的孤立标记。

根因是只看了图、没看行。正确做法是先按 100% 装行,行内再决定列:

$row    = 0.0;   // 当前行已占宽度百分比
$colKey = null;   // 当前打开的叠列

foreach ($meta as $i => $info) {
    $u = span($info['w']);          // 33 → 100/3,其余取字面档位

    // ① 能并入当前打开的列?列宽不变 → 行宽也不变
    if ($info['st'] && null !== $colKey) {
        $col = $groups[$colKey];
        if ($col['w'] === $info['w'] && count($col['idxs']) < 4) {
            $groups[$colKey]['idxs'][] = $i;
            continue;
        }
        $colKey = null;              // 换档位 / 到上限 → 收列
    }

    // ② 放不下就先换行
    if ($row + $u > 100.5) { $row = 0.0; }

    $groups[] = array('idxs' => array($i), 'w' => $info['w'], 'stack' => $info['st']);
    $colKey   = $info['st'] ? count($groups) - 1 : null;
    $row     += $u;
}

因为「并入列」这一步完全不改变行宽,所以无论一列里叠 2 张还是 4 张,每行宽度之和都恒等于 100%——这就是验收标准能被程序保证的原因。

3.3 段落包裹:捕获组必须包住整个序列

最后一步是把「纯图片段落」包成画廊容器。正则有个经典陷阱:重复组 (?:...)+ 的括号要写在外面,写在里面的话 $m[1] 只保留最后一张图,其余全被吞掉。

#<p(?:s[^>]*)?>s*((?:(?:<imgb[^>]*bfigureb[^>]*>|<divb[^>]*bzl-stackb[^>]*>.*?</div>)s*)+)</p>#is

四、效果验证

功能不能靠「看起来对」交付。写了浏览器脚本按画廊的直接子元素测量每一行,断言铺满率、行内高度极差、叠列数量三项。线上实测(单段落 136 张图):

指标实测值判定
每行铺满率min 0.99 / max 1.00通过
行内高度极差0 px通过(底边齐平)
叠列14 个(2 图列 ×10、4 图列 ×4)通过
全站画廊 / 行 / 不齐行40 / 86 / 0通过

铺满率上限是 1.00 而非 1.0 的原因:格子的 flex-basis 扣掉了一个 gap,n 个格子的宽度和是 100% - gap + (n-1)×gap,剩下约 1 个 gap 的零头由小数误差吸收,肉眼无感。

五、踩坑记录

下面 7 条全部是线上实测踩出来的,每一条的共同点是:不报错,只是静默不生效。

1. PHP 的 !empty('0') 是 false

贴边标记要判断字符串 '0',但 PHP 把 '0' 当空值,!empty('0') 恒为 false → 无缝墙静默失效。必须写 isset($m) && '0' === trim($m)。

2. 跨行的同宽图会被误并成一列

即 3.2 的坑①。代码侧改成按行装格后已能自动切断;但仍有内容侧的残留歧义:一组排法以 25% 叠列收尾、下一组又从 25% 叠列开头,两段之间没有任何图能表达「这里要断」。约定是组与组之间插一张 100% 整幅图做隔断。

3. flex 子项写 1 1 0 会整体塌陷

叠列容器高度是 auto 时,flex-basis:0 会让列内所有图塌成 0 高,整列消失。必须写 flex: 1 1 auto,同时补 min-height:0(否则图片被自身内容高度顶住无法收缩)。

4. 多行 flex 里不要写 height:100%

百分比高度参照的是容器总高而不是行高,多行布局下会算错。等高应该交给 align-items:stretch,它给的正是本行行高。

5. wp_add_inline_style() 必须晚于句柄入队

间距是后台可配的,用内联样式注入 CSS 变量。但内联样式要求句柄已经入队,早于入队执行会静默无输出——页面上看不到任何错误,只是 --zl-gap 消失了。框架里资源统一在优先级 99 入队,内联必须挂 100。

6. 素材自带黑边,CSS 拉不掉

上线后发现不少格子四周有黑边。查下来不是样式问题——黑边是游戏原画图片像素的一部分,object-fit 只裁不缩,黑边在画面内部就永远跟着。最后写了个 GD 脚本批量处理:原图缩到 200px 宽做行列扫描,四周 ≥98% 纯黑的边判定为黑边,每边最多裁 18% 防误裁,裁完重新生成缩略图。230 张里裁掉 41 张。

7. cover 中心取景会把人物头顶切掉

小格被行高拉得比原图竖长,cover 垂直裁切时默认取画面正中间,人物立绘的头正好被切。把 object-position 从 50% 50% 改成 50% 25%,取景中心上移,头部回来了;对风景图只是取景略偏上,无感。

六、总结

回过头看,这个功能真正难的地方不在 CSS,而在把”作者意图”翻译成”行宽账”:作者只写 -25 s 这种极简记号,程序要还原出他想拼成什么样。一旦承认「列宽与列内图数无关」这个不变量,行宽就能被严格约束在 100%,底边齐平、不留白、不溢出这三条就都变成了可以自动断言的指标,而不是靠眼睛看。

下一步可以考虑的方向:把取景焦点(object-position)也做成 alt 可写的可选项,让竖构图的主体能单独指定;以及把「整幅隔断图」这条内容侧约定,换成显式的分段标记。

附:效果示例(本文用同一套语法渲染)

下面这组图没有任何额外标记,只在 alt 里写了档位,由本文讲的模块自动排成墙:

再叠一列试试(-25 s ×2 + -50):


📚 zibll 子主题开发实录(共 22 篇) · 系列总目录与阅读路径 →

① 前端性能与加载

② 排版与样式系统

③ 功能实现与边界

④ 系统与运维

⑤ 成就与活动系统

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容