事情是从一张截图开始的。8 月 26 日晚上十一点半,我在做一份访谈的逐字稿整理,受访者是一位在杭州做无障碍阅读插件的老工程师,姓陆,六十二岁,做了十九年屏幕阅读器的适配。访谈录了 74 分钟,我用转写工具导出成 HTML,双击打开,准备从头核对一遍。屏幕上是这样的:正文段落里的大段中文,全部变成了一排排空心的方块,只有英文标点和阿拉伯数字正常显示。我以为是文件损坏,重新导出一次,还是一样。于是我又把这同一个文件拖进另外两个浏览器——其中一个浏览器显示正常,另一个依旧是方块。三个浏览器,两份方块,一份正常。那一刻我意识到,这不是文件的问题,而是「打开网站网页」这件事本身,在每一层都藏着可以被忽略的假设。
同一份文件,同一台机器,三次打开,三种结果。这大概是今年最让我坐不住的一次排查。
先说第一个弯。我把页面源码打开,逐行看头部,发现转写工具导出的文件里,<meta charset> 被放在了 <title> 之后,中间还夹着三段内联脚本。按规范,浏览器在解析到编码声明之前,会先用一套猜测策略试探前 1024 字节的字节序列;一旦它猜成别的编码,后面的声明就只是「更正」,而更正之前已经解析出来的文本,不会回头重来。说得直白些:你告诉它「我是中文」,但它已经按别的语言读完了开头几个字。这解释了为什么同一份文件在不同浏览器里表现不同——它们的猜测阈值和试探字节数不一样,Firefox 和 Safari 在这件事上的默认策略,本来就跟 Chrome 不是同一套。
第二个弯在字体。我当时的 CSS 里写的是 font-family: "PingFang SC", sans-serif,看起来没问题,但那份逐字稿在导出时被注入了一段内联样式,把所有文本强制指向一个只在 Windows 上存在的字体名,等到在 macOS 上打开时,这个名字找不到,回退链又写得过于简单,末尾只有一句 sans-serif。中文没有合适的无衬线回退,就被交给了系统的兜底字体,而兜底字体在某些渲染路径下不包含这些字形,于是画出方块。这些方块不是「字丢了」,字一直在,只是没有一张脸可以显示它。
第三个弯,也是最让我困惑的一个。为了确认问题出在导出端还是显示端,我把那段中文复制出来,复制粘贴到浏览器打开一个纯文本框页面,结果正常;再换成图片方式查看同一段文本,也没问题。这说明字符本身是好的,问题只在「以网页形式打开」时的解析与渲染链条上。我把这个对比记在笔记里,顺手标了一个链接:复制粘贴到浏览器打开,那篇笔记里我详细记了三种打开方式在字符集上的差异,这里就不重复了。
我目前的判断,以及我不确定的部分
说实话,我不认为这是「浏览器的错」。更接近事实的说法是:网页从字节到字形,中间要经过编码猜测、解码、DOM 构建、样式匹配、字体回退、栅格化六个环节,任何一个环节的默认值被打破,结果都可能是一次静默的失败——它不报错,不弹窗,只是安静地把字变成方块。这份逐字稿的问题,是三个环节同时踩了坑:编码声明位置不对、内联样式覆盖了字体链、回退链末端过于简陋。修好其中一个,方块少一半;三个都修,才恢复正常。我现在的困惑在于:转写工具为什么要注入那段内联样式?导出设置里其实有一个「保留原始字符集」的选项,默认是关的,我没找到它的说明文档。这一点我暂时没有答案。
我的个人观点:绝大多数「网页打不开 / 显示乱码」的抱怨,根源不在网络,而在没人愿意承认的默认值。
这话可能有点绝对,但我确实越来越这么看。八月二十七号下午,我把这个案例发给陆工看,他在电话里沉默了几秒,说了一句让我记到现在的话:「我们做无障碍的,最怕的不是出错,是出错之后没有任何提示。」他做过一个统计:在他经手的 213 个页面里,有 61 个存在中文回退字体缺失的问题,其中 58 个页面的开发者从未意识到。这个比例我没有独立复核,但和我这几天的观察是吻合的。顺便一提,排查过程中我还顺带核对了图片资源的加载路径,把几张关键截图归档到了本地图库,入口记在这里:打开图库照片,方便以后复查时对照时间戳。
一个顺手记录的价格对照
另一个让我困惑的点跟内容无关,跟成本有关。我为了复现问题,租了一台低配云主机,按月付费,配置是 2 核 4G,价格标签我留了个记录——当时比较了几家服务商的相似配置,其中一家的说明页面上写着某个版本的报价,措辞含糊,我把它连同截图一起存进了笔记:新奥门494949.cm版本价格。这跟字体渲染没有直接关系,但它提醒我一件事:很多技术问题的排查成本,最后会以账单的形式出现,而账单本身往往比问题更难读懂。
我不打算在这篇文章里给出一个「三步解决乱码」的结论。这个案例里还有两个变量我没有控制住:转写工具的内部编码逻辑,以及我本机字体缓存的状态。等我把这两项测完,会再写一篇。现在能确定的是:打开一个网站网页,远不是「双击」这么简单的事。 —— 本文写作时的自注,2026 年 8 月 30 日