
AI帮我造博客(八):正文页排版没那么简单——从 AST 提取目录到自制 Tuner 对齐组件

上一篇搞完了首页的底部导航栏,外壳算是齐活了。接下来该回到博客最核心的东西:文章正文页。
TL;DR 文章速览
文章页看起来只是"把 Markdown 变成网页",实际远不止于此:目录要理解文档结构才不会被代码块里的 # 误抓,代码块的高亮和复制要拆分到服务器与浏览器两端,音乐文章的装饰组件更要让播放器和歌词像拼积木一样精确卡进外框图的透明区域。解决方案分别是 AST 提取、Shiki+客户端复制按钮、自造 Tuner 调试工具三轮迭代对齐、歌词框九宫格伸缩。一路踩下来最大的体会:AI 能快速写组件,但哪些内容该特殊对待、哪些代码该放哪里、调试工具必须验收真实效果——这些规矩只能自己踩坑自己立。
前言
我最开始感觉文章页的实现应该比较简单:就是一个能把 Markdown 转成网页的工具。
内容存在后台(Strapi的 markdown 格式),前端拿过来,扔给一个现成的渲染器(react-markdown),我让AI帮我写下前端代码,页面就出来了。
demo跑起来一看,标题有了,段落有了,列表有了,表格也有了。
文章页的风格也和整个网站的风格比较一致,AI 的审美还是在线的(这里用的是 Gemini 3)。当然我又人工调整了背景图等美化逻辑,此处略去不表。
我当时还挺乐观:这不就快结束了吗?
等我把真实文章塞进去,麻烦才刚开始。
比如代码块里的 ## 会被目录误抓,复制按钮没法放服务器端,表格在手机上会撑破页面。等写到音乐文章,播放器和歌词框又冒出一堆新问题。
这时候我才明白:文章页不是“把 Markdown 变成 HTML”那么简单。
目录:被代码块里的 ## 坑了
文章页右侧有个目录,要从正文里自动提取所有小标题(h2、h3)。
我让 AI 写了一个简单的规则(正则表达式),大致意思:找那些以两个或三个 # 开头的行。前三篇文章都没问题,第四篇的代码块里恰好有一行 ## install——目录里突然多出一个根本不存在的章节。
这种 bug 最阴:前三篇都没事,第四篇刚好有一段代码示例,页面才露馅。你点目录跳不过去,还会先怀疑是不是 slug 生成错了、滚动锚点错了、CSS 遮挡了,绕了一圈最后才发现是代码块里的 # 被误当成标题了。
怎么解决?不能用简单的“找行首的 #”,而需要真正理解 Markdown 的结构。这里用了一个叫 AST(抽象语法树) 的东西,它能像拆积木一样把文章拆成标题、段落、代码块等不同类型的节点,然后只从“标题”节点里提取内容,代码块里的 # 就会被忽略。
我用 AST 改造了目录:
const tree = unified().use(remarkParse).parse(markdown);
visit(tree, 'heading', (node) => {
if (node.depth !== 2 && node.depth !== 3) return;
// 从 heading 节点提取文本,生成 slug
});后来我给 AI 的指令也变了。不再说「帮我从 Markdown 里提取标题」,而是说:
用
remark解析 Markdown AST,只遍历 heading 节点,忽略代码块和引用块里的伪标题。
AI 很擅长找最方便的实现方案,但它不会自动知道边界在哪里,最短的路,不一定是最优的路。
代码块:服务器上高亮,浏览器里复制
技术博客的代码块要有语法高亮,还要能一键复制代码。
我选了一个叫 Shiki 的工具,它能在服务器端就把代码转成带颜色的 HTML,用户看到的时候已经高亮好了。
第一个坑是版本。AI 写的代码用的是旧版 Shiki 的写法,跑起来直接报错。报错信息很迷惑,让我先怀疑是不是项目配置错了,绕了一大圈才发现是 AI 用的文档过时了(如果你知道skills,可以用context7,如果不懂这个,就把最新文档贴给AI就行)。
高亮搞定,复制按钮又来了。复制功能需要调用浏览器的 navigator.clipboard,这个东西只能在浏览器里跑,不能放到服务器端。所以代码块拆成两层:服务器端负责高亮,浏览器端负责复制按钮和点击逻辑。AI 写复制逻辑很快,但它不会自动知道这个分界线在哪里,你得明确告诉它,否则它很容易把 navigator 写进服务端代码里,运行时直接报错。
音乐文章:规则不够用了
技术文章修得差不多,我写了第一篇音乐文章《浮生饮月》。正文里我希望能有两种技术文章不会有的东西:
- 音乐播放器:一个漂亮的装饰外框,中间嵌一个 QQ 音乐的 iframe(类似一个小窗口)。
- 歌词框:四周有花纹,高度会随歌词变长,但花纹不能变形。

/// 音乐文章页面 ///
我不想在 Markdown 里直接写代码标签,那样写文章时还得记组件名,太反人类。我希望 Markdown 还是干干净净的,所有特殊样式都藏在渲染器里。
我希望它们在 Markdown 里类似这样:
> **[音乐播放《浮生饮月》](https://i.y.qq.com/n2/m/outchain/player/index.html?songid=642034035&songtype=0)**
lyrics:
晚风轻拂过衣袖
暮色浸染了层楼
而前端在渲染的时候检查内容:
- 如果是代码块且标注了
lyrics,就套用歌词组件; - 如果是引用块且里面是 QQ 音乐链接,就渲染成播放器。
这一步 AI 很好用,能快速写出判断逻辑。
但”哪些内容应该被特殊对待”这个规则,不能交给 AI 猜。它不知道我的博客里技术文章和音乐文章会长期并存,这个取舍只能我自己定。
另外,从美化的角度来看,音乐文章的页面,我也想和技术文章完全不一样,不是换个配色就行,而是要换一种气质。整体风格的定调,我直接复用了本系列中的这篇文章:AI帮我造博客(六):零设计基础做首页——从蒙眼指挥到截图驱动的三轮进化 里摸出的那条路子:生图模型出概念图,多模态模型提取设计规范,截图驱动迭代。做首页时这套流程已经验证过了,拿到音乐文章上同样顺利,流程就不多提了。
但接下来碰到了一件之前没遇到过的事。
首页的设计,本质上是在调 CSS 参数和背景图,颜色、间距、发光效果,都是”平面”元素,那套截图驱动的工作流能搞定。音乐文章的装饰组件不一样:外框图中间有透明区域,播放器 iframe 和歌词内容要像拼积木一样卡进去。
截图反馈工作流能帮你生成一张漂亮的装饰外框图,但它没法告诉你:这张图的透明区域有多大、播放器应该偏移多少百分比才能严丝合缝。不是”怎么让页面好看”(第六篇已经回答了),而是”怎么让一个组件精确嵌进图片的孔里”。
播放器:差一像素都难受
需求很具体:一张装饰外框图,中间留了透明区域,QQ 音乐播放器要刚好卡进去。
差一两像素都会很难看。

/// 未对齐播放器示例 ///
第一版用了一张背景图,把播放器绝对定位在上面。一缩放页面就露馅:有的宽度下会露白,有的宽度下播放器会偏移。
换方案:外框用一张 <img> 图片,让图片自己撑开高度,保持原始比例。播放器用绝对定位压进去,上下左右全部用百分比,跟着外框一起缩放。
最后调出一组参数:
上边距 29.4%,下边距 22.9%,左边距 11.6%,右边距 11.6%这些数字怎么来的?卡了我很久。
Tuner:卡了三小时,突然灵光一闪
一开始我手算比例,改代码,刷新网页,歪一点,再改,再刷新……循环了几十次,心态快炸了。因为 AI 还无法精确分析图片中透明区域的大小和位置,没法直接计算出百分比参数,让背景图和 QQ 音乐播放器完美融合。
突然想到:能不能做一个调试页面?上面放外框图,再叠一个可以拖拽的红框,红框的位置实时显示百分比。这样我用鼠标拉一拉就能看到参数,不用每次都改代码刷新网页。
这个念头就是 Tuner 的由来。

/// 播放器 Tuner 调试页 ///
我让 AI 快速写了个调试页面:几个滑块,一个红框,实时输出参数。AI 几分钟就搞定了。
但第一版 Tuner 只对齐了红框,没放真实的 QQ 音乐播放器。红框看着严丝合缝,真播放器一塞进去,还是有白边。
“这个 tuner 有点不对啊……用你这个配置,更新后还是没对齐”
AI 又产生了幻觉。你以为调好了,实际上根本没对齐。
于是第二版 Tuner 直接塞真实 iframe。调参时看的不再是“理论透明区”,而是播放器有没有露白。
结果还是不对,QQ 音乐的那个 iframe 本身自带内边距,播放器内容和 iframe 边界不是一回事。
第三版 Tuner,我在 iframe 里再画一层参考线,直接标出播放器内容的实际边界。这次终于对齐了。
所以,AI 协作中你必须有自己的想法,创造性的工作是属于你的,AI 能帮你写工具,但工具对不对,也得拿真实场景来验收。
歌词框:四角不能歪
播放器解决了,歌词框又来了。它的问题是高度会随歌词变长,但四角花纹不能变形。
如果简单地把整张装饰图拉满(background-size: 100% 100%),四角花纹就会被扯歪。截图里可能看不出,但上线后会很刺眼。
解决方案叫“九宫格”(CSS 的 border-image):把装饰图切成九块,四个角固定不动,上下左右中间的部分可以拉伸。这样不管歌词框多高,四角花纹始终是正的。
播放器按比例定位,歌词框按九宫格伸缩。都叫“带装饰外框”,但不能用一个方案糊过去。
(另外顺手把两张装饰图从 PNG 转成 WebP,体积从 1.5MB 压到 100KB 以内——这种体力活就不展开了。)
这里安利一个在线免费的图片压缩网站:" https://squoosh.app↗ ",上传图片可以调整各种参数,还能实时查看压缩前后的对比。

/// 图片资源压缩 ///
踩出来的规矩
| 遇到的坑 | 后来怎么解决的 |
|---|---|
用简单的行首匹配找标题,代码块里的 # 被误抓 | 改用能理解文章结构的工具(AST),只从真正的标题节点提取 |
| AI 写的 Shiki 版本不对,报错很迷惑 | 给 AI 贴最新的文档,重新生成 |
| 复制按钮的代码跑在服务器端,浏览器不支持 | 把高亮放服务器,复制按钮放浏览器 |
| 调试工具只用红框预览,没放真实播放器,调了半天还是歪 | 调试工具必须直接预览最终效果 |
| 装饰图拉满四角变形 | 歌词框用九宫格方案,四角固定,边缘拉伸 |
现在回头看,文章页确实不是“把 Markdown 变成 HTML”那么简单。
哪些内容要特殊对待,哪些交互只能放浏览器,哪些效果必须拿真实页面验收——这些规矩都是踩坑踩出来的。
AI 可以把组件写很快,但规矩得我自己立。
尤其 Tuner 那次:卡了很久,突然灵光一闪,想出一个绝妙的方案,然后又解决新出的问题。这种过程,才是折腾的日常,也是折腾的乐趣所在。
