夜雨聆风学习资料网

ARTICLE · 1352972

排版插件开发手记4:样式为什么总跑偏,以及我怎么算对,不依赖浏览器,我自己算层叠

排版插件开发手记4:样式为什么总跑偏,以及我怎么算对,不依赖浏览器,我自己算层叠
上篇说到那把最危险的「尺子」。它最危险的地方,是从不报错——却把你量到的「默认值」,当成「文件值」。这一篇,讲我怎么换掉这把会撒谎的尺子。

一 · 最直觉的做法,是个陷阱

第 3 篇结尾说,读进来的文档,不等于算对的样式。第 1 篇的「坑一」,说的也是它:导入后排版不对,但结构完好、不报错。

我第一版的做法,正是大多数人会想到的:把正文塞进一个隐藏的 iframe,调 getComputedStyle 把每个元素的实际样式读出来。

听起来很合理。浏览器算出来的,还能有错?

有。而且错得极其安静。

我拿同一份文件测了两条路径。算对的时候,标题行高输出 28.8px;走那条「省事」路径,整篇只剩 margin:14px 0。两条路径都对同一个文件,结果差了一整个排版世界。

根因在 CSP。那个 iframe 是 about:blank,它会继承宿主页面的内容安全策略。当宿主的 style-src 不放行内联样式时,iframe 里我塞进去的 <style> 被静默禁用——不抛错,getComputedStyle 老老实实返回一个全 UA 默认值的结果(17px / 27.2px / #333)。

产物「看着完整、其实全默认」。这是「导入后排版不对」最隐蔽的一类根因:你以为你在量文件,其实你在量浏览器。

二 · 正解:不依赖浏览器,自己算层叠

既然浏览器这把尺子会撒谎,那就别用它的尺子。我改成自己算。

核心是一个纯函数:planStyle(tag, cs, pcs, ctx)。给定标签、自身样式、父级样式、上下文,返回这份元素最终该写进 inline style 的属性集合。

它走五步:

  1. 解析样式表。
     把 <style> 块、元素自带 style、以及 Markdown 的语义样式,都解析成规则。
  2. 按特异性匹配。
     选择器的特异性(id > class > 标签)决定哪条规则赢。
  3. 处理强制优先级声明。
     带强制优先级标记的规则单独成一层,赢了普通规则。
  4. 沿树继承。
     可继承属性(字体、颜色、行高)从父级往下传。
  5. 展开简写。font
     拆成 font-size / font-family / font-weight 等,避免目标编辑器不认简写。

这套做法有三个好处,是浏览器方案给不了的:

  • 确定性。
     同一份输入,永远同一份输出。没有布局、没有 CSP、没有超时。
  • 可在 node 单测。
     不依赖 DOM,纯逻辑断言,276 项单测里相当一部分守着它。
  • 可对照。
     我可以拿真浏览器原生渲染当黄金基准,逐元素比对,而不是「重新导入再看一眼」。

三 · 可继承属性:最容易被「省掉」的坑

自己算层叠,最大的陷阱是可继承属性。

直觉上,子元素「和父级相同」的属性可以省掉不写——反正继承了嘛。但这前提是:父级的基线,也要进产物。

公众号的编辑器只认 inline style。我搬过去的子树,挂在陌生的父级下(比如 section.rich_media_content)。原文 body { line-height:1.85; color:#3f3f3f } 不会跟着走。只要当初按「与父级相同就省掉」,整篇就落到目标编辑器的默认(17px / 1.6 / #333)。

用户视角是「导入后排版和原文件不一样」,但不报错、结构完好——最难查的一类。

修法:顶层子元素无条件写基线(字体 / 字号 / 行高 / 字色 / 字重),子孙才相对父级去重。实测代价仅约 8 KB(对一份约 1.48 MB 的文档而言,几乎可以忽略)。

还有一个连带坑:行高要保留倍数 1.85,而不是算成 px。钉死成像素,用户在手机端或改字号时必错位。倍数跟着缩放,才是稳的。

四 · 把决策抽成纯函数,是为了能验证

为什么非要把「算哪条样式」抽成 planStyle 这样一个纯函数?

因为只有这样,我才能在没有浏览器的环境里验证它。同一份函数,只切换 isTopChild(是不是顶层子元素)一个入参,就是修复前和修复后的对照。

判据因此变得具体:同样是这个文档,修复前整篇落到默认,修复后顶层子元素都带基线。对照一跑,根因立刻收敛。

这也是第 3 篇说「黄金对照」的落地:不是凭感觉说「看起来对」,而是拿真浏览器渲染逐元素比。后者分不清「本来就这样」和「被我改坏了」,前者能。

五 · 衔接:算对了,送不进去

这一篇,我把第 1 篇的「坑一」拆完了。样式算对了,确定性、可对照、可单测。

但第 1 篇的「坑二」马上接上来:算对了,送不进去。

更气人的是,送不进去不是因为接口难调,而是因为一个叫「隔离世界」的东西——你在内容脚本里看到的页面,和页面自己看到的页面,不是同一个。下一篇讲这个。

最后

样式跑偏,根因往往不在你算错了,而在你量错了尺子。那把最危险的尺子,是浏览器——它会「安静地撒谎」。

换一把不会撒谎的尺子:自己算层叠,确定性、可对照、可验证。这一步做稳了,后面「送进去」「说清哪步错」才站得住。

下一篇,从一个反直觉的事实说起:为什么你在内容脚本里读不到页面的接口。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料