我的公众号文章是用脚本从博客 Markdown 转成内联样式的 HTML,再走 draft/add 接口发到草稿箱的。最近发现一个顽固的排版 bug:列表项里只要出现行内代码,它后面的说明文字就会被挤到下一行。
预期是一行:
- low:简短、对延迟敏感的任务
实际渲染出来变成两行:
- low:
简短、对延迟敏感的任务
发出去的 HTML 明明没问题,浏览器里渲染也正常,唯独微信编辑器和手机端阅读时断行。折腾了好几轮才定位到根因,记录一下。
排查过程:四个都不对的猜测
猜测一:<code> 标签的锅。 微信编辑器确实会把行内 <code> 块级化(甚至会把后面的文字吞进 code 元素里),于是把行内代码全部转成带样式的 <span>。HTML 干净了,但还是断行。
猜测二:等宽字体触发的。 怀疑编辑器靠 font-family: monospace 识别"这是代码"然后特殊处理。去掉等宽字体,还是断行。
猜测三:行内元素类型的问题。 把 span 降级成 <strong style="color: ..."> 纯加粗变色,还是断行——说明跟用什么行内元素根本没关系。
猜测四:把 li 里的裸文本包进 <span> 就好了。 这一步方向对了,但包了个无属性的空 span,依然断行。原因后面揭晓。
到这里有个关键的调试技巧:用 draft/get 接口把存进草稿箱的 HTML 拉回来对比。结果发现 API 存储的内容和我发的一模一样——问题不在传输,而在微信编辑器打开草稿时会对 HTML 做一轮"规范化"重写。拉取一篇被编辑器保存过的旧草稿,看到了真凶:裸文本被包成了块级的 <section>。
一次测试定位根因
与其继续猜一轮发一轮,不如把所有假设塞进一篇测试草稿里一次验证。发了 6 个变体的 li:
- A:li 以裸文本开头,后面跟行内元素
- B:li 以行内元素开头,后面的裸文本包无属性
<span> - C:li 以行内元素开头,后面的裸文本包带 style 的
<span> - D:整个 li 内容包一层带 style 的 span
- E:行内元素用
<strong>+ 带 style 的 span 包裸文本 - F:纯裸文本对照组
手机上一看,结果非常清晰:只有 B 断行,其余全部正常。
根因:三条实测验证的编辑器行为
- li 以行内元素开头时,编辑器会把后续的裸文本节点包成块级
<section>——这就是断行的直接原因。li 以裸文本开头则完全安全。 - 无属性的
<span>会被编辑器直接剥掉,文字重新变回裸文本,等于没包。这就是猜测四失败的原因。 - 带 style 属性的
<span>会被保留,里面的文字不会被 section 化。
所以修法很简单:转换脚本里把 li 下的裸文本节点预先包进带 style 的 span。用 BeautifulSoup 写出来就几行:
for li in soup.find_all('li'):
for child in list(li.children):
if isinstance(child, NavigableString) and child.strip():
wrapper = soup.new_tag('span')
# style 属性不能少,无属性的 span 会被编辑器剥掉
wrapper['style'] = 'display: inline'
child.wrap(wrapper)
配合行内 <code> → 带样式 <span> 的转换,列表里的行内代码终于稳定不断行了。
顺带的两个经验
调试方法比修复本身更值得记。 公众号排版问题的难点是反馈链路长:改脚本 → 发草稿 → 手机看效果。两个提效手段:
- 用
draft/get拉回存储的 HTML,区分"我发的就是坏的"还是"微信改写了它" - 把多个假设做成 A/B/C…变体塞进一篇测试草稿,一次往返验证全部猜想
注意 2 万字符上限是含 HTML 标签的。 每个标签都带内联样式时,3500 字左右的 Markdown 就能撞上 draft/add 的限制。省字符的思路:可继承属性(字体、行高、颜色)只写在最外层包裹 div 上,高频标签只留必要属性。我的文章用精简主题后 HTML 从 21769 字符降到 14256,足够塞下 6000 字正文。
关于
关注我获取更多资讯