typography.md 18 KB

Typography:排印推理系统

这不是字体清单,是配对与排版的推理规则。 design-styles.md 已经给了 40 种风格各自的字体名;本文回答的是「为什么这样配」「拿到任意内容怎么推导出字号/行长/字重」。目标:同一个风格标签,落到不同内容上,能推导出不同的排印结果,而不是每次都抄同一套字号。

前置纪律不变:有 design context 先 lift 用户自己的字体(见 design-context.md),本文的一切只在「用户没有字体规范」时启用。

0. 排印决策顺序

拿到内容后按这个顺序推,每一步都由上一步决定,不许跳到「直接选个好看的字体」:

  1. 内容类型 → 长文阅读 / 数据密集 / 营销大字 / UI 界面,决定音阶比例和正文字号
  2. 语言构成 → 纯中文 / 中西混排 / 纯西文,决定 fallback 链写法和行高基准
  3. 风格温度(对齐 design-styles.md 的安静/中性/大胆三档)→ 决定字体配对的对比度来源
  4. 最后才是字体名 → 从下面第 3 章配对表选,或从风格库对应条目取

为什么:先选字体名的做法,会让「内容是什么」对排印零影响,这正是千人一面的病根。

1. 字号音阶(modular scale)

字号不是拍脑袋,是从正文字号乘一个固定比例逐级推出来的。比例决定页面的「戏剧性」:

比例 名字 性格 适用
1.2 小三度 平缓、层级多而不吵 dashboard、文档站、信息密集 UI
1.25 大三度 通用、安全 大多数网页、产品落地页
1.333 纯四度 标题明显跳出 editorial 长文、营销页、报告
1.5 纯五度 戏剧性、层级极少 大字报、slides、hero 一屏一句

推导规则:正文定 16-18px(中文正文建议 17-18px,汉字笔画密、同字号比西文显挤),然后按比例上推标题、下推 caption。层级超过 5 档就是失控,砍掉。

档位 1.25 比例下的参考值 用途
caption 12-13px 图注、meta 信息、EXIF 式小字
small 14px 辅助说明、表格
body 16-18px 正文,一切的基准
h3 ≈1.25x 小节标题
h2 ≈1.56x 章节标题
h1 ≈1.95x 页面标题
display 3x-8x,脱离音阶自由发挥 hero 巨字,由版面而非音阶决定

流式字号写法(display 档必用,避免大屏死板小屏溢出):

/* clamp(最小值, 首选值, 最大值):首选值 = 基础rem + 视口系数 */
h1 { font-size: clamp(2rem, 1.2rem + 3.5vw, 4.5rem); }
.display { font-size: clamp(3rem, 1rem + 9vw, 9rem); }
/* 正文不要 clamp 出大幅波动,16→18 的窄区间即可 */
body { font-size: clamp(1rem, 0.95rem + 0.3vw, 1.125rem); }

为什么 display 脱离音阶:hero 巨字是版面元素不是文本层级,它的尺寸由「占视口几成」决定,用 vw 推导比用音阶推导更合理。

2. 行长与行高

行长(比字体选择更影响可读性)

语言 舒适区 CSS 实现
西文正文 45-75 字符,最佳 66 max-width: 65ch
中文正文 一行 22-38 字,最佳 28-32 字 max-width: 36em(em 随字号缩放)
图注/侧栏 更短,中文 15-20 字 窄容器天然限制

为什么中文更短:汉字是无空格的致密方块字,同宽度下承载的信息量明显高于西文,同样的眼跳次数中文读进更多内容,行太长回行时找不到下一行开头。

行高随行长联动

行高不是常数,是行长的函数。行越长,眼睛回行距离越远,需要更大的行间距当「轨道」:

场景 西文 中文
display 大字(1-2 行) 0.95-1.1 1.1-1.25
标题(h1-h3) 1.1-1.3 1.3-1.4
短行正文(<30 字/行) 1.4-1.5 1.6-1.7
长行正文(接近上限) 1.6 1.8-2.0

中文全线比西文高 0.2 左右:汉字是满格方块,没有西文小写字母之间的天然空隙,行距不足会糊成一片。

text-wrap(2024+ 浏览器都支持了,白拿的排印质量)

h1, h2, h3 { text-wrap: balance; }  /* 标题多行时各行长度均衡,消灭孤字行 */
p { text-wrap: pretty; }            /* 正文消灭行尾孤词(西文效果明显,中文轻微) */

balance 只用于 ≤4 行的标题(算法限制 6 行且有性能成本);pretty 全局给正文无副作用。

3. 十组开源字体配对(西文)

配对的三种对比度来源,配之前先想清楚用哪种:

  • 形式对比:衬线 display x 无衬线 body(最经典,但要 x-height 咬合,否则视觉字号跳)
  • 同族咬合:superfamily 同一设计骨架(零风险,代价是平淡)
  • 时代对比:古典字形 x 现代字形(谱系差 200 年以上才有张力,差 50 年只显得乱)
# 配对(display + body) 配对逻辑 温度 获取
1 Newsreader + Geist 形式对比:屏显优化的过渡衬线,x-height 高、与 Geist 咬合好;Fraunces 的正牌平替 安静 Google Fonts / Vercel 官方仓库
2 Source Serif 4 + Source Sans 3 同族咬合:Adobe 同设计系统,字高字重节奏完全对齐,报告和文档零翻车 安静 Google Fonts
3 EB Garamond + IBM Plex Sans 时代对比:16 世纪法国老衬线 x 2017 理性 grotesque,差 400 年的张力;注意 Garamond x-height 低,同行混用需字号补偿(+8% 是经验起点,系统解法用 font-size-adjust,见第 4 章) 安静·文气 Google Fonts
4 Lora + Hanken Grotesk 形式对比:Lora 笔刷感衬线中等反差,屏显耐看;Hanken 是 Söhne 气质的开源近亲 中性 Google Fonts
5 Instrument Serif + Geist 形式对比:只有 400 一档字重,天生 display-only,正文必须交给 sans。⚠️ 正在被 AI 工具用烂的路上,2026 年慎用于「想显得独特」的场合 中性 Google Fonts
6 Schibsted Grotesk + Source Serif 4 反转结构:grotesque 当 display、衬线当正文,媒体感;Space Grotesk 泛滥后的平替(挪威 Schibsted 报业定制开源,带新闻血统) 中性 Google Fonts
7 Bricolage Grotesque + Newsreader 形式对比:Bricolage 的 ink trap 和不规则细节在大字号才显现,天生 display;配安静衬线正文形成粗野 x 文雅 大胆 Google Fonts
8 Archivo(Expanded/Black)+ Inter 大字报结构:Archivo 宽体黑重压场,Inter 只当 14-16px 正文工蜂(这是 Inter 的正确用法,见反模式) 大胆 Google Fonts
9 Cormorant Garamond + Work Sans 高反差奢侈感:Cormorant 笔画极细,必须 ≥40px 才成立,小字号笔画会断;适合时尚/太空图录风 大胆 Google Fonts
10 Geist Mono / JetBrains Mono + Geist 等宽当主角:命令行感、工程感;等宽只用于标签/编号/代码,整段正文用等宽是灾难(行长膨胀 30%) 中性·技术 Vercel / JetBrains 官方,均 OFL

已被用烂名单(AI 生成页面的指纹,用了等于自曝):

烂大街 为什么烂 平替
Fraunces 当 display 2023-2025 所有 AI 设计工具的默认「有品位」选项 Newsreader、Libre Caslon Text
Inter 当 display Inter 是为 UI 小字设计的,大字号下匀质无表情 Archivo、Anton、Schibsted Grotesk
Space Grotesk 「科技感」的偷懒答案,泛滥于加密/AI 落地页 Schibsted Grotesk、Familjen Grotesk
Playfair Display 「优雅」的偷懒答案,婚礼请柬既视感 Cormorant(更极端)、DM Serif Display(更憨)

4. 中文排印(本文最重的一章)

西文排印有百年成熟工具链,中文没有。AI 设计工具在中文上集体摆烂(默认交给系统字体、直接套西文规则),这里是差异化所在。

4.1 开源/免费商用中文字体地图

字体 类别 气质 温度 获取
思源宋体(Noto Serif SC) 宋体 出版正统、7 字重齐全,Heavy 可当 display 安静-中性 Google Fonts,OFL
思源黑体(Noto Sans SC) 黑体 中文界的 Inter:可靠、无表情,当默认正文没错但没个性 全温度兜底 Google Fonts,OFL
霞鹜文楷 楷体 手写温度、亲切,适合文艺/教育/个人博客正文与引文 安静·暖 GitHub lxgw/LxgwWenKai,OFL
霞鹜新晰黑 黑体 比思源黑更瘦更透气的屏显黑,正文久读不累 安静 GitHub lxgw/LxgwNeoXiHei
得意黑 Smiley Sans 斜黑体 中文世界罕见的原生斜体,运动感、标题专用;正文用它会晕 大胆 GitHub atelier-anchor/smiley-sans,OFL
汇文明朝体 旧字形明朝 老印刷铅字气、复古出版,适合书封/文化类 display 中性-大胆·复古 猫啃网/GitHub,免费商用
京华老宋体 老宋 笔画方硬的标题宋,报头感 大胆·复古 猫啃网,免费商用
源流明体/源样明体 明朝体(繁向) 思源宋改刻,保留传统字形细节,繁体内容首选 安静·古典 GitHub ButTaiwan,OFL
未来荧黑 Glow Sans 几何黑 思源黑衍生的现代几何黑,多宽度(Compressed 可做窄长 display) 中性-大胆·现代 GitHub welai/glow-sans,OFL
MiSans / HarmonyOS Sans / OPPO Sans 厂商 UI 黑 比思源黑略有性格的 UI 黑,App 原型合适 中性 各厂官网,免费商用

选型推理:正文只在宋/黑/楷里选(其余都是 display 字体,整段用会累);display 想要个性时才去动得意黑/老宋/明朝体。中文字体一个顶西文十个(单文件 5-15MB),一页最多两个中文字体家族,为加载和统一性两个原因。

4.2 中西混排规则

fallback 链是第一杠杆:中文字体自带的西文字符普遍难看(思源黑的拉丁字母呆板),把西文字体放在前面,拉丁字符和数字被它接住,汉字自动落到后面的中文字体:

/* 西文在前,中文在后,系统中文兜底,泛型收尾 */
font-family: "Geist", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
/* 衬线同理 */
font-family: "Newsreader", "Noto Serif SC", "Songti SC", serif;

为什么这个顺序:font-family 是逐字符匹配的,西文字体不含 CJK 码位,汉字自然穿透到中文字体。反过来写(中文在前)西文字符全被中文字体吃掉,等于白配。

字号补偿:同字号下西文小写视觉偏小(x-height 只占字身一半,汉字占满)。两种解法:

/* 解法一:font-size-adjust 让 fallback 字体按 x-height 归一(Chrome 127+/FF/Safari 17+) */
:root { font-size-adjust: from-font; }
/* 解法二:选 x-height 高的西文体(Geist/Inter/Source Sans 都高),混排天然齐 */

baseline 对齐:中西 baseline 不一致时症状是英文单词在中文行里「下沉」。优先换 x-height 更高的西文体;个别 display 场景用 vertical-align: -0.02em~-0.06em 微调西文 span,正文别这么修(维护成本大于收益)。

数字规则:数字一律走西文字体(fallback 链已保证),数据表格必须加 font-variant-numeric: tabular-nums,否则 1 和 8 宽度不同,列会抖。

中英之间不加空格:这是本仓库规范(花叔明确不用盘古之白),靠 fallback 链的字体本身留白,不靠手动敲空格。

4.3 中文没有斜体

中文字形没有 italic 传统,浏览器遇到 font-style: italic 会机械倾斜汉字(faux italic),笔画变形、极丑。强调手段替换表:

西文习惯 中文替代 CSS
italic 强调 换字重 font-weight: 600(前提:字体真有这档字重)
italic 书名/引用 底色高亮 background: linear-gradient(transparent 60%, #FFE9A8 60%) 荧光笔式
italic 引文块 换字体 引文整段换霞鹜文楷,楷体本身就是中文的「引用语气」
italic 专名 颜色/着重号 text-emphasis: dot(着重号,中文原生强调,支持度已可用)

保险丝:font-synthesis: none; 全局禁掉合成斜体和合成加粗,宁可不强调也不接受变形字。

4.4 标点规范

规则 做法 为什么
引号 直角引号「」『』,不用弯引号 "" 弯引号在中文字体里是全角占位但形状是西文的,视觉漂浮;「」是本仓库硬规范
避头尾 line-break: strict; 禁止句号逗号出现在行首、开引号出现在行尾,这是中文排版的底线
标点悬挂 hanging-punctuation: first allow-end;(仅 Safari);跨浏览器用 text-indent: -0.5em 处理段首开引号 段首的开引号不悬挂会让首行看起来缩进了半格,视觉左边缘不齐
连续标点挤压 font-feature-settings: "halt";(行尾挤压)或 "palt"(全比例宽度,需配合 letter-spacing) 全角标点连排(如「)。」)会出现一个半字宽的空洞,halt 收窄它

4.5 中文 letter-spacing 区间

场景 区间 为什么
正文 0 至 0.05em 微加字距提升透气度;超过 0.05em 词的完形被打散,读速下降
标题(24-48px) 0 汉字方块字距天然均匀,不需要西文式 tracking 调整
display 巨字(>60px) -0.02em 至 0 大字号下字面之间的空隙被放大,微收更紧凑;再负就笔画相撞
全大写西文小标签 0.08-0.15em 唯一需要大正字距的场景,且只对西文大写生效

中文永远不要用西文那套「display 收 -0.05em」:汉字是满格设计,负字距直接笔画打架。

4.6 中文 display 大字

中文没有西文那种 Ultra Thin 到 Black 的 display 字体生态,大字的戏剧性要靠推理制造:

  • 字重对比是主武器:思源宋 Heavy 900 压 Light 300,同一字体两个极端字重同屏,比换字体更有张力且零加载成本
  • 笔画密度决定可用字号下限:笔画细/反差大的字体(宋体细横、Cormorant 式)只在大字号成立;小于 24px 细笔画开始断笔,正文必须回到黑体/中等笔画
  • 反向也成立:笔画重的字(黑体 Black、老宋)在超大字号下墨量过大,「一」和「灥」墨量差被放大,密度不均的标题考虑换低一档字重
  • 竖排是中文独有的 display 武器writing-mode: vertical-rl 做书脊式标题、诗词、目录,西文做不到;注意竖排里的西文和数字用 text-orientation: uprighttext-combine-upright: all(两位数字合体直立)

5. 反模式清单

❌ 反模式 为什么错
全场 Inter(display+body 一把梭) Inter 是 UI 小字工具,当 display 匀质无表情;这是「AI 生成页面」的头号指纹
中文交给 sans-serif 系统默认 Windows 落到中易宋体/雅黑、macOS 落到苹方,同一页面跨设备完全两张脸,等于没做设计
faux italic / faux bold 浏览器合成变形:斜体扭曲汉字,合成加粗把笔画糊成墨团;用 font-synthesis: none 断根
大标题字距过松 西文 display 需要收紧(大字号空隙被放大),AI 常反着来加 +0.05em,标题松垮像临时占位
行长失控(无 max-width) 大屏上一行 60 个汉字,读者回行必迷路;可读性问题里行长失控排第一,比字体选错伤害大
字号档位 >6 档 层级贬值,读者分不清什么重要;音阶的意义就是强制克制
只有 400/700 两档字重 层级全靠字号撑,页面平;variable font 时代 300-900 都是免费的表达维度
表格/数据不用 tabular-nums 数字宽度不等,列左右抖动,数据可信感直接打折
中文正文用 display 字体(得意黑/老宋整段排) display 字体的个性在正文里变成阅读阻力,200 字后就累
中西混排中文字体放 fallback 链最前 拉丁字符全被中文字体自带的难看西文吃掉,配好的西文体永远轮不到出场

6. CSS 实现要点

:root {
  /* 1. fallback 链:西文 → 中文 → 系统中文 → 泛型(顺序即规则,见 4.2) */
  --font-body: "Geist", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
  --font-display: "Newsreader", "Noto Serif SC", "Songti SC", serif;

  /* 2. 禁合成:不接受浏览器伪造的斜体/加粗(中文场景必开) */
  font-synthesis: none;

  /* 3. 中文断行底线 */
  line-break: strict;        /* 避头尾 */
  overflow-wrap: break-word; /* 长 URL/英文串不撑破容器 */
}

body {
  font-family: var(--font-body);
  font-size: 17px;           /* 中文正文基准,见第 1 章 */
  line-height: 1.8;          /* 中文行高基准,见第 2 章 */
  /* 正文开启标准连字,关闭花哨特性 */
  font-feature-settings: "liga" 1, "calt" 1;
}

/* 数据场景:等宽数字 + 斜杠零(0 和 O 不混淆) */
.data, table { font-variant-numeric: tabular-nums slashed-zero; }

/* 西文小标签:全大写 + 大字距的唯一合法场景 */
.label { text-transform: uppercase; letter-spacing: 0.1em; font-size: 12px; }

/* 标点挤压:中文 display 大字里全角标点的空洞收窄 */
.display-cjk { font-feature-settings: "halt" 1; }

中文字体加载(单文件 5-15MB,直接引全量会毁掉首屏):

  • 首选 Google Fonts 的 Noto SC 系(已按 unicode-range 自动切成上百个分片,浏览器只下用到的字)
  • self-host 个性字体(霞鹜/得意黑等)必须先子集化:cn-font-split 或 fonttools 的 pyftsubset,正文字体按常用 3500 字切,display 字体按实际出现的字符切(一张海报往往只有 20 个字,子集能压到 50KB 以内)
  • font-display: swap 保底,中文字体下载慢,白屏等字体是最差体验