11 月 11 号傍晚六点多,上海市金山区。我坐在桌前准备给首页加两篇新文章,双手还没摸到键盘,浏览器一刷新,整个人愣住了。

「技术分享」那一块塌了。左边两列卡片到某一行就断了,底下是一大片黑,右边那一列还在往下堆。看着像渲染到一半卡住了,又像浏览器把某段内容吃掉了。

更邪门的是,这个页面我前一天刚动过——上午刚把首页里内嵌的 55 篇全文删掉,页面从 25 万字符瘦到 7.8 万,当时测了三遍都正常。怎么隔了一天,自己塌了。

这篇就把从「服务端没问题」到「源头是一行摘要」的整个过程写清楚。中间我走了两次弯路,都值得记下来。

一、先确认服务端,别一上来就怀疑代码

线上的东西出了怪样子,第一个要排除的永远是「是不是根本没传上去 / 传坏了」。我做了三件事:

三条全绿。这说明文件本身是好的,问题在「浏览器怎么理解这个文件」这一层。

二、把浏览器眼里的真实坐标读出来

肉眼看到的「卡片高度参差」不算证据,得读数字。我在浏览器里把每个卡片的 getBoundingClientRect() 全打了一遍,同时读网格自己的属性:

gridTemplateRows:
374.969px 374.969px 374.969px 374.969px 374.969px 374.969px
398.969px 374.969px  6797.44px

最后那个数太离谱了。整块网格的行高应该是 375 左右,怎么会冒出一行 6797 像素?

顺着这一行找,抓到了两张卡:

卡片位置实测高度
/post/17-ai-xiaoshuo.htmlx=576797px
/post/22-dafeiyu-soft.htmlx=4476797px
/post/21-ai-xiaoshuo-xia.htmlx=838375px

同一行三张卡,两张被拉到 6797,一张老老实实 375。这就是截图里左边两列底下一大片空白的来源——卡片被拉长了,但里面的内容只有三百多像素,剩下六千多全是空壳。

再往里看这两张卡的内部:只有两个子元素,封面 160px、正文区 213px,加起来 373。outerHTML 才 557 个字符。内容根本撑不出 6797,说明高度不是它自己的,是被外面拽上去的。

三、第一次弯路:以为是 CSS 拉伸

网格没设 align-items,默认就是 stretch——同行卡片会被拉到一样高。我当时的判断是:行高被某个东西撑高了,于是把这两张卡拉长了。

顺着这个思路,我临时把 align-items 改成 start,让卡片按自己的内容定高。结果:

这一步很关键。如果是单纯的 CSS 拉伸问题,改完对齐方式,卡片应该恢复正常三列排布才对。它没有。后面那 17 张卡宁可每行只站一张、全挤到第三列,也不肯往左挪——这说明它们根本不在这个网格里正常排队。

我从这时候开始怀疑:「卡片不是被拉长的,是这堆卡片的关系出了问题」。

四、数一数网格的直接子元素

这是整件事的转折点。我不再看卡片长什么样,改成数结构——网格容器下面,到底挂了几个直接子元素。

// 44 张卡应该都是网格的直接子元素
document.querySelectorAll('.card-grid')[1].children.length
→ 27

44 张卡片,网格却只认 27 个孩子。这 27 个里,26 个是 a.card,剩下 1 个是——STRONG。

我当场把那几张卡的父节点打出来:

cards[26]  parentElement.tagName → STRONG
cards[27]  parentElement.tagName → STRONG
cards[43]  parentElement.tagName → STRONG

从第 27 张卡开始,往后 17 张,全部挂在同一个 <strong> 底下。而这个 <strong> 自己,又正好坐在网格第 9 行的第三格里。

到这里全通了。网格第 9 行三格:第一格 17 号卡、第二格 22 号卡、第三格是一个 <strong>。这个 <strong> 里面塞了 17 张卡,竖着叠,总高 6797。行高由这一行最高的东西决定,于是一整行变成 6797,同行的 17 号和 22 号被 stretch 拽成同样高。

五、源头:上一篇文章的摘要里少了一个闭合标签

顺着这个 <strong> 往回找,源头是 16 号那篇《大肥鱼「聊死」之后,我把无限上下文 + 省 token 做成了桌面软件》的卡片摘要。原文是这么写的:

<p class="card-excerpt">……下决心研究一件事:
<strong>怎么让 AI 的上下文变成"无限",还能不烧钱。</p>

那个 <strong> 开了,后面没有对应的闭合。浏览器不会因为这个标签少一半就停下,它会一直往下找最近的闭合标签——一路找到页面底部微信号那个 <strong>lige0552</strong> 才收手。

中间隔了多少东西?17 张卡片、三个板块的分隔、友情链接、页脚。全被塞进这一个「加粗」里。

回头验一下也简单,全文搜一遍就知道:

全文 <strong> 出现 2 次
全文 </strong> 出现 1 次

2 开 1 闭。少的那个,就是把首页压塌的那个。

现在再回看那张截图,为什么是「左中两列空、右列继续」?因为被吞的 17 张卡都挤在第 9 行第三格的那根 <strong> 里,竖着叠成了右侧那一条。左中两列第 9 行之后就没有东西了,剩下的就是空。

六、修:补一个字,页面回来了

修复动作只有一处,把摘要末尾补上闭合:

旧:……还能不烧钱。</p>
新:……还能不烧钱。</strong></p>

改完再验,这次不是看截图,是看数:

指标修复前修复后
技术分享网格直接子元素27(26 卡 + 1 个 STRONG)44
网格行数9 行,最后一行 6797px15 行(14 行满 3 + 末行 2)
页面总高13154px9150px
<strong> 开 / 闭2 / 12 / 2
文件字符数7830078309

顺手把另一个错也改了——首页顶部统计写的是「53 文章」,实际早就 55 篇了。这个数字是写死在 HTML 里的,articles 数组只有 53 条,而页面上的卡片有 55 张(后两篇是直接插进去的卡,没进数组),两边一直对不上。

七、这次学到的三条

1. 卡片摘要这类地方,别用行内标签

摘要是我自己写的,写的时候顺手加个加粗想强调一下,就埋了个雷。静态站的卡片摘要是纯展示文本,不需要任何行内标签。真要强调,用颜色或者排版,别用 <strong>、<em> 这类必须成对的东西。

2. 布局出现「某一行异常巨大」,先数子元素,别先动 CSS

我第一反应是改 align-items,改完卡片视觉上「好了」,但行高没变、卡片还挤在第三列——这个假象最容易骗人。真正的判据是:网格的直接子元素数应该等于卡片数。不等于,就一定是结构被什么东西吃掉了。

3. 上线后加一步「标签平衡」冒烟

现在的流程里我加了两条很土但管用的检查:

# 标签开闭数量对齐
grep -o "<strong>" index.html | wc -l
grep -o "</strong>" index.html | wc -l

# 网格子元素数 = 卡片数
document.querySelectorAll('.card-grid')[1].children.length

这两条加起来不用十秒钟,但能拦住这类「文件语法没毛病、浏览器解析跑偏」的问题。它不是语法错误,node --check 查不出来,编辑器也不报警——只有浏览器知道。

八、最后

修完之后我又把整页翻了一遍:技术分享 44 张卡,15 行,每行 3 张,卡片高度统一 375,封面全部显示。最底下「常用软件」两张软件卡也正常。

整个过程最贵的不是修复,是定位。一个字符的问题,让我从服务器查到 CSS、从 CSS 查到 DOM,绕了两圈才回到「标签没闭合」这个最朴素的答案上。

如果你也在手写静态站的卡片列表,遇到类似「某一块突然不对、但文件看着没坏」的情况,可以先数一眼子元素。

有类似经历想聊的,站内交流,微信:lige0552。