排版系统工程:从字号比例到阅读节奏的层级构建
排版系统工程:从字号比例到阅读节奏的层级构建
本文是设计系统系列的第八篇。前七篇分别讨论了 Token 架构、风格光谱、动效系统、设计语言迁移、组件级微交互、页面叙事节奏和数据可视化组件——涵盖了色彩、动效、布局、交互等维度。本文则聚焦一个贯穿所有维度但始终未被独立展开的基础支柱:排版系统。排版不是"选个字体调个字号",而是一套从数学比例到阅读节奏的工程体系——它决定了用户能否在你的界面上舒适地阅读和理解信息。
一、排版为什么是"系统"
大多数前端开发者对排版的处理方式是这样的:标题用大字号,正文用小字号,再加一个 line-height: 1.5。如果设计师给了设计稿,就按设计稿的数值来;如果没有设计稿,就用浏览器的默认值。
这种方式的问题在于:排版值之间没有关联。标题的 32px、正文的 16px、标签的 12px——这三个数字是独立选择的,它们之间没有数学关系。当你需要把标题从 32px 改为 28px 时,你不知道正文的 16px 是否也需要调整,因为它们之间没有比例约束。
排版系统的核心思想是:所有字号、行高、字距都不是独立值,而是一个比例系统的衍生值。当你改变系统中的基础值时,所有衍生值按比例自动调整——这就是 Token 架构在排版维度的具体实现。
1.1 排版系统的三个层次
层次 1: 字号比例(Scale) — 字号之间的数学关系
层次 2: 行高节奏(Leading) — 行间距与字号的配对关系
层次 3: 字重语义(Weight) — 字重与信息层级的映射关系
每个层次解决不同的排版问题:
- 字号比例解决"这个元素应该多大"——不是凭感觉,而是按比例
- 行高节奏解决"文字之间应该留多少空间"——不是固定值,而是与字号联动
- 字重语义解决"这个文字有多重要"——不是随意加粗,而是有语义层级
下面逐层拆解。
二、字号比例:从"凭感觉"到"按数学"
2.1 等差与等比的差异
最常见的字号定义方式是等差数列:
--text-xs: 12px;
--text-sm: 14px;
--text-base: 16px;
--text-lg: 18px;
--text-xl: 20px;
--text-2xl: 24px;
--text-3xl: 30px;
每级增加 2-6px——增量是固定的。这种定义方式的问题在于:在小字号区间,2px 的差异很明显(12px → 14px 是 17% 的增幅);在大字号区间,2px 的差异几乎看不出来(24px → 26px 是 8% 的增幅)。等差数列让小字号区间的层级过于密集,大字号区间的层级过于稀疏。
等比数列解决了这个问题——每级之间的比例是固定的:
--text-xs: 0.75rem; /* 12px — 比例 1.0 */
--text-sm: 0.875rem; /* 14px — 比例 1.17 */
--text-base: 1rem; /* 16px — 比例 1.14 */
--text-lg: 1.125rem; /* 18px — 比例 1.125 */
--text-xl: 1.25rem; /* 20px — 比例 1.11 */
--text-2xl: 1.5rem; /* 24px — 比例 1.2 */
--text-3xl: 1.875rem; /* 30px — 比例 1.25 */
--text-4xl: 2.5rem; /* 40px — 比例 1.33 */
比例的变化规律:小字号区间的比例较小(1.14-1.17),大字号区间的比例较大(1.25-1.33)。这是因为人眼对字号差异的感知是对数的——10px 和 12px 的差异感知与 30px 和 36px 的差异感知大致相同(都是约 20% 的增幅)。
2.2 比例系统的选择
常见的排版比例系统有几种:
| 比例系统 | 比率 | 12px 起步的序列 | 适用场景 |
|---|---|---|---|
| Major Second | 1.125 | 12, 13.5, 15, 17, 19, 22, 24 | 紧凑型界面(工具类应用) |
| Minor Third | 1.2 | 12, 14, 17, 21, 25, 30 | 平衡型界面(通用效率工具) |
| Major Third | 1.25 | 12, 15, 19, 23, 29, 36 | 宽松型界面(内容阅读类) |
| Perfect Fourth | 1.333 | 12, 16, 21, 28, 38, 50 | 大幅层级差异(营销页面) |
本次项目使用的是 Minor Third(1.2)——它在紧凑和宽松之间取了中间值,适合效率工具的界面密度。
为什么选 1.2 而不是 1.25 或 1.333? 因为效率工具的排版需要满足两个约束:
- 层级差异可感知——标题和正文必须有明显的字号差异,用户一眼就能区分
- 层级数量有限——效率工具不需要营销页面那种 6-7 级标题,通常只需要 4-5 级
1.2 的比例在 4 级跨度内产生的字号差异是 1.2³ = 1.728——标题比正文大 73%,足以形成视觉层级。1.25 的比例在 4 级跨度内是 1.25³ = 1.953——标题比正文大 95%,层级差异过大,在紧凑的效率工具界面中会显得标题"过于突出"。1.333 的比例更是达到 1.333³ = 2.37——标题比正文大 137%,适合营销页面的"大标题冲击",但不适合需要长时间阅读的效率工具。
2.3 基准字号的选择
基准字号(--text-base)是整个比例系统的锚点。本次项目选择 16px(1rem)作为基准:
:root {
--text-base: 1rem; /* 16px */
}
为什么是 16px 而不是 14px 或 18px?
- 14px:在桌面端偏小——长时间阅读 14px 的正文字会导致视觉疲劳。14px 更适合移动端或辅助文字。
- 16px:浏览器的默认值,也是 WCAG 推荐的最小正文字号。在桌面端提供舒适的阅读体验,在移动端也不会过大。
- 18px:在桌面端偏大——效率工具的界面信息密度较高,18px 的正文字会占据过多空间,减少每屏可见的信息量。
核心原则:基准字号的选择不是审美问题,而是阅读舒适度与信息密度的权衡。效率工具偏向信息密度,所以选 16px;阅读类应用偏向阅读舒适度,所以选 18px。
2.4 rem vs px 的策略
本次项目的字号 Token 使用 rem 而非 px:
:root {
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.5rem;
}
rem 的优势:当用户在浏览器设置中调整默认字号时(如将默认从 16px 改为 20px),所有使用 rem 的字号会按比例缩放——14px 的辅助文字变为 17.5px,32px 的标题变为 40px。这对视力不佳的用户至关重要。
px 的问题:px 是绝对单位——无论用户如何调整浏览器设置,16px 永远是 16px。这剥夺了用户的字号自定义权。
但 rem 有一个陷阱:如果 html { font-size: 16px } 被硬编码,rem 就退化成了 px——用户的浏览器设置不再生效。正确做法是不设置 html 的 font-size,让浏览器默认值(通常是 16px)作为 rem 的基准,用户可以自由调整。
三、行高节奏:从"固定值"到"字号联动"
3.1 行高不是独立值
最常见的行高定义方式是给所有元素一个固定的 line-height:
body { line-height: 1.5; }
h1 { line-height: 1.2; }
这种方式的问题在于:行高与字号是脱钩的。当字号从 16px 变为 32px 时,line-height: 1.5 产生的行间距从 24px 变为 48px——但 48px 的行间距对于 32px 的标题来说太松了,标题的行与行之间会显得"断裂"。
行高应该与字号联动——不同字号需要不同的行高比例:
| 字号 | 行高比例 | 绝对行高 | 感知 |
|---|---|---|---|
| 12px | 1.6 | 19.2px | 紧凑但可读 |
| 14px | 1.55 | 21.7px | 稍紧凑 |
| 16px | 1.5 | 24px | 舒适 |
| 18px | 1.45 | 26.1px | 稍宽松 |
| 20px | 1.4 | 28px | 宽松 |
| 24px | 1.35 | 32.4px | 标题节奏 |
| 30px | 1.25 | 37.5px | 大标题紧凑 |
| 40px | 1.15 | 46px | Hero 紧凑 |
核心规律:字号越大,行高比例越小。这是因为大字号的字符本身已经占据了更多垂直空间——如果行高比例保持 1.5,大字号的行间距会过于松散,文字之间的视觉关联会被破坏。
3.2 行高比例的数学解释
为什么字号越大,行高比例应该越小?这涉及到人眼对"行间距"的感知机制。
人眼感知的行间距不是绝对的像素值,而是行间距与字高的比值。当字号为 16px、行高为 24px 时,行间距是 8px(24 - 16),行间距与字高的比值是 0.5。当字号为 32px、行高为 48px 时,行间距是 16px,比值仍然是 0.5——但人眼感知到的"松散度"却不同。
原因在于:大字号字符的内部留白更多。32px 的字符中,字形的实际高度可能只有 24px(字符的 ascender 到 descender),其余 8px 是字符内部的留白。当行高为 48px 时,行间距是 16px——但用户感知到的"行与行之间的空白"是 16px + 8px(字符内部留白)= 24px,这比小字号时的 8px 行间距松散得多。
解决方案:随着字号增大,逐步减小行高比例,让绝对行间距的增长慢于字号的增长——这样大字号的行间距在感知上与小字号的行间距保持一致的"松紧度"。
3.3 行高 Token 的定义
:root {
--leading-xs: 1.6; /* 12px → 19.2px */
--leading-sm: 1.55; /* 14px → 21.7px */
--leading-base: 1.5; /* 16px → 24px */
--leading-lg: 1.45; /* 18px → 26.1px */
--leading-xl: 1.4; /* 20px → 28px */
--leading-2xl: 1.35; /* 24px → 32.4px */
--leading-3xl: 1.25; /* 30px → 37.5px */
--leading-4xl: 1.15; /* 40px → 46px */
}
行高 Token 与字号 Token 的配对关系:每个字号 Token 都有一个对应的行高 Token——它们是成对使用的,不存在"用 --leading-base 配 --text-2xl"这种跨级配对。
.title {
font-size: var(--text-2xl); /* 24px */
line-height: var(--leading-2xl); /* 1.35 → 32.4px */
}
.body {
font-size: var(--text-base); /* 16px */
line-height: var(--leading-base); /* 1.5 → 24px */
}
为什么行高也要做成 Token? 因为行高与字号是联动的——当字号比例系统调整时(如从 Minor Third 切换到 Major Third),行高比例也需要相应调整。如果行高是硬编码的,每次调整字号系统都需要手动搜索和修改所有行高值;如果行高是 Token,只需要修改 Token 定义即可。
3.4 单行文本的行高特例
并非所有文本都需要行高比例——单行文本(按钮文字、标签、导航项)的行高应该等于字号,即 line-height: 1。
.btn {
font-size: var(--text-sm); /* 14px */
line-height: 1; /* 14px — 不产生额外行间距 */
}
.status-badge {
font-size: var(--text-xs); /* 12px */
line-height: 1; /* 12px — 紧凑容器 */
}
为什么单行文本需要 line-height: 1? 因为单行文本不会换行——行高对它没有"阅读舒适度"的影响。但行高会影响元素的垂直高度:line-height: 1.5 的 14px 文字会让按钮的高度增加 7px(14 × 0.5 = 7),这 7px 的额外高度让按钮看起来"膨胀"了。line-height: 1 让按钮的高度精确等于字号——按钮的垂直尺寸由 padding 控制,而非 line-height。
核心原则:行高是为"多行阅读"设计的——单行文本不需要行高带来的行间距,它的垂直空间应该由 padding 精确控制。
四、字重语义:从"加粗"到"信息层级"
4.1 字重不是装饰
最常见的字重使用方式是:“这个文字很重要,所以加粗”。但这种思维方式把字重当成了装饰工具——“加粗让文字看起来更突出”。
字重应该被视为信息层级的语义标记——不同的字重传递不同的信息角色:
| 字重 | 语义角色 | 使用场景 |
|---|---|---|
| 400 (Regular) | 正文 — 默认阅读内容 | 段落、描述、列表项 |
| 500 (Medium) | 强调 — 次要标题和关键数据 | 卡片标题、指标数值、导航项 |
| 600 (Semibold) | 标题 — 区域级标题 | section 标题、面板标题 |
| 700 (Bold) | Hero — 页面级标题 | 页面主标题、CTA 标题 |
核心原则:字重的选择应该基于文字的信息角色,而非"看起来够不够突出"。400 是"我是内容",500 是"我是关键信息",600 是"我是一个区域的标题",700 是"我是整个页面的标题"。
4.2 为什么不用 300 (Light)
在 Apple 的设计语言中,Light(300)字重常用于大标题——Apple 官网的产品标题就是 Light 字重。但本次项目没有使用 300:
原因一:300 字重在 16px 以下的字号中几乎不可读——笔画太细,在非 Retina 屏幕上会"断裂"。效率工具的界面中有大量 12-14px 的辅助文字,如果混入 300 字重,会破坏整体的可读性。
原因二:300 字重的视觉权重太轻——在信息密度较高的效率工具界面中,Light 字重的标题会"飘"在页面上,缺乏"锚定"感。效率工具的标题需要"压住"它下面的内容区域,600 字重的"分量感"更适合这个角色。
什么时候用 300? 当字号足够大(40px+)且界面风格偏"优雅"而非"功能"时——比如营销页面的 Hero 标题。效率工具不需要这种优雅感。
4.3 字重与字号的配对策略
字重和字号不是独立选择的——它们需要配对使用:
/* Hero 标题:最大字号 + 最重字重 */
.hero h1 {
font-size: var(--text-4xl); /* 40px */
font-weight: 700; /* Bold */
line-height: var(--leading-4xl); /* 1.15 */
}
/* Section 标题:中等字号 + 中等字重 */
.section-title {
font-size: var(--text-2xl); /* 24px */
font-weight: 600; /* Semibold */
line-height: var(--leading-2xl); /* 1.35 */
}
/* 卡片标题:小字号 + 中等字重 */
.card-title {
font-size: var(--text-lg); /* 18px */
font-weight: 500; /* Medium */
line-height: var(--leading-lg); /* 1.45 */
}
/* 正文:基准字号 + 常规字重 */
.body {
font-size: var(--text-base); /* 16px */
font-weight: 400; /* Regular */
line-height: var(--leading-base); /* 1.5 */
}
/* 辅助文字:小字号 + 常规字重 */
.caption {
font-size: var(--text-xs); /* 12px */
font-weight: 400; /* Regular */
line-height: var(--leading-xs); /* 1.6 */
}
配对的规律:字号越大,字重越重;字号越小,字重越轻(但不低于 400)。这是因为大字号 + 重字重的组合产生最大的视觉冲击力——适合 Hero 标题;小字号 + 重字重的组合产生"压迫感"——12px 的 Bold 文字看起来像"警告标签",不适合常规辅助文字。
例外:指标卡片中的数字——虽然是小字号(14-16px),但使用 500 或 600 字重:
.metric-value {
font-size: var(--text-lg); /* 18px */
font-weight: 600; /* Semibold — 数字需要"分量感" */
}
为什么数字用重字重? 因为数字是指标卡片的核心信息——用户看指标卡片就是为了看数字。重字重让数字"有分量",在卡片中占据视觉中心。如果数字用 400 字重,它会与标签文字(也是 400)混为一谈,用户无法快速区分"哪个是数据,哪个是说明"。
4.4 字重的渐进过渡
当两个相邻元素使用不同字重时(如标题 600 + 正文 400),字重的差异应该是一个"渐进过渡",而非"突然跳跃":
Hero 标题: 700
Section 标题: 600 ← 与 Hero 差 100
卡片标题: 500 ← 与 Section 差 100
正文: 400 ← 与卡片标题差 100
为什么渐进而非跳跃? 因为字重的突然跳跃(如从 700 直接到 400)会让视觉层级"断裂"——用户感知到"这里是标题,这里是正文,中间没有过渡"。渐进的字重差异让信息层级"平滑下降"——用户从标题到正文的阅读过程中,视觉权重逐步减轻,不会产生"突然掉下来"的感觉。
反面案例:如果 Hero 标题用 700,正文也用 700(“全加粗”),视觉层级就消失了——所有文字的视觉权重相同,用户无法通过字重判断"哪个是标题,哪个是内容"。这就是为什么"全加粗"的界面看起来"吵"——每个文字都在争夺注意力。
五、排版 Token 的架构设计
5.1 三层 Token 在排版中的映射
回顾第一篇博客讨论的三层 Token 架构(Primitive → Semantic → Component),排版 Token 也遵循这个架构:
/* Primitive Token — 排版原子 */
:root {
--ratio: 1.2; /* Minor Third 比例 */
--text-base-px: 16; /* 基准字号(数值,用于计算) */
--weight-regular: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
}
/* Semantic Token — 排版语义 */
:root {
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.5rem;
--leading-xs: 1.6;
--leading-sm: 1.55;
--leading-base: 1.5;
--leading-lg: 1.45;
--leading-xl: 1.4;
--leading-2xl: 1.35;
--leading-3xl: 1.25;
--leading-4xl: 1.15;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
}
/* Component Token — 排版组件 */
:root {
--font-hero: var(--weight-bold) var(--text-4xl) / var(--leading-4xl) var(--font-body);
--font-title: var(--weight-semibold) var(--text-2xl) / var(--leading-2xl) var(--font-body);
--font-card-title: var(--weight-medium) var(--text-lg) / var(--leading-lg) var(--font-body);
--font-body-text: var(--weight-regular) var(--text-base) / var(--leading-base) var(--font-body);
--font-caption: var(--weight-regular) var(--text-xs) / var(--leading-xs) var(--font-body);
--font-metric: var(--weight-semibold) var(--text-lg) / 1 var(--font-body);
--font-badge: var(--weight-medium) var(--text-xs) / 1 var(--font-body);
}
Component Token 的价值:一个 --font-hero Token 包含了字重、字号、行高、字体族四个维度的完整定义——使用时只需要一行:
.hero h1 {
font: var(--font-hero);
}
而非四行:
.hero h1 {
font-weight: 700;
font-size: var(--text-4xl);
line-height: var(--leading-4xl);
font-family: var(--font-body);
}
核心优势:当排版系统调整时(如切换比例系统或调整字重语义),只需要修改 Semantic Token 的定义,所有使用 Component Token 的元素自动更新——这就是 Token 架构在排版维度的力量。
5.2 字体族的选择策略
本次项目使用系统字体栈而非自定义字体:
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
为什么用系统字体而非 Google Fonts?
| 维度 | 系统字体 | 自定义字体 |
|---|---|---|
| 加载速度 | 0ms(本地已有) | 100-500ms(需下载) |
| 离线可用 | 是 | 否 |
| 跨平台一致性 | 各平台不同(macOS 用 SF,Windows 用 Segoe) | 完全一致 |
| 品牌辨识度 | 低(与系统原生应用相同) | 高(独特字体) |
| FOUT/FOIT | 无 | 有(字体加载闪烁) |
决策:效率工具优先考虑加载速度和离线可用性——系统字体是最佳选择。品牌辨识度的损失可以通过排版比例和字重语义来弥补——即使使用相同的系统字体,不同的字号比例和字重配对也能产生独特的排版风格。
字体栈的顺序逻辑:
-apple-system → macOS/iOS 上使用 San Francisco
BlinkMacSystemFont → Chrome on macOS 的回退
'Segoe UI' → Windows 上使用 Segoe UI
Roboto → Android/Linux 上使用 Roboto
sans-serif → 最终回退
每个平台使用各自的系统字体——macOS 用户看到 SF,Windows 用户看到 Segoe UI,Android 用户看到 Roboto。这些字体虽然不同,但都是各自平台上最舒适的无衬线字体——用户在效率工具中看到的字体与他们系统原生应用中的字体一致,产生"原生感"。
5.3 等宽字体的特殊角色
--font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
等宽字体在效率工具中有特定的语义角色——它标记"这是数据"或"这是代码":
.task-id {
font-family: var(--font-mono);
font-size: var(--text-xs);
color: var(--ink-3);
}
.metric-num {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
}
等宽字体的两个语义:
- 标识符(任务ID、编号、代码片段):等宽字体让每个字符占据相同的宽度,
TASK-001和TASK-100的长度相同——用户可以快速对齐和比较标识符。 - 数字(指标值、统计数据):等宽字体的
tabular-nums特性让数字对齐——87和12的宽度相同,在表格或卡片中不会因为数字位数不同而产生错位。
font-variant-numeric: tabular-nums:这个 CSS 属性让等宽数字在非等宽字体中也生效——即使使用系统无衬线字体,数字也会按等宽排列。这对于指标卡片中的数字对齐至关重要:
.metric-num {
font-family: var(--font-body); /* 无衬线字体 */
font-variant-numeric: tabular-nums; /* 但数字等宽 */
}
为什么不全用等宽字体? 因为等宽字体的阅读舒适度低于比例字体——等宽字体的每个字符宽度相同,但人眼阅读时需要的是"字符间距与字符形状的动态适配",而非"所有字符宽度一致"。等宽字体适合短字符串(标识符、数字),不适合长段落。
六、响应式排版:从"固定字号"到"流动缩放"
6.1 断点策略
排版系统在不同屏幕尺寸下需要调整——但调整方式不是"每个断点重新定义字号",而是按比例缩放整个字号系统:
:root {
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.5rem;
}
@media (max-width: 900px) {
:root {
--text-3xl: 1.5rem; /* 30px → 24px */
--text-4xl: 2rem; /* 40px → 32px */
}
}
@media (max-width: 640px) {
:root {
--text-3xl: 1.25rem; /* 30px → 20px */
--text-4xl: 1.5rem; /* 40px → 24px */
}
}
关键策略:只调整大字号 Token(--text-3xl 和 --text-4xl),小字号 Token 保持不变。这是因为:
- 大字号在移动端过大:40px 的标题在 375px 宽的手机屏幕上占据太多空间——一行可能只能显示 4-5 个字,标题被迫换行,视觉冲击力反而下降。
- 小字号在移动端无需调整:12-16px 的字号在移动端和桌面端的阅读体验基本相同——手机的阅读距离更近,16px 在手机上的视觉大小与桌面端接近。
为什么不缩放所有字号? 如果把基准字号从 16px 缩小到 14px,所有正文的阅读舒适度都会下降——14px 在移动端偏小,长时间阅读会疲劳。移动端的排版调整应该只影响"过大"的字号,而非"舒适"的字号。
6.2 clamp() 的流动缩放
clamp() 提供了一种更优雅的响应式排版方案——字号随视口宽度连续变化,而非在断点处跳跃:
.hero h1 {
font-size: clamp(1.5rem, 4vw + 1rem, 2.5rem);
}
clamp 的三个参数:
- 最小值(1.5rem):字号不会小于 24px——在最小的手机屏幕上也保持可读
- 首选值(4vw + 1rem):字号随视口宽度变化——视口越宽,字号越大
- 最大值(2.5rem):字号不会大于 40px——在超宽屏幕上不会过大
4vw + 1rem 的计算:
- 375px 视口(手机):4 × 3.75 + 16 = 31px → 被 clamp 到 24px(最小值)
- 768px 视口(平板):4 × 7.68 + 16 = 46.7px → 被 clamp 到 40px(最大值)
- 1440px 视口(桌面):4 × 14.4 + 16 = 73.6px → 被 clamp 到 40px(最大值)
为什么用 4vw + 1rem 而非纯 4vw? 纯 4vw 在 375px 视口下只有 15px——太小了。加上 1rem(16px)确保最小字号不低于 16px + 4vw 的增长量。4vw + 1rem 的公式确保了:视口宽度每增加 100px,字号增加 4px——这是一个温和的缩放速度。
clamp() 的适用范围:只用于大标题(Hero 标题、Section 标题)——这些标题在不同屏幕尺寸下需要明显的字号变化。正文和辅助文字不需要 clamp()——它们的字号在所有屏幕尺寸下保持一致。
6.3 移动端的行高调整
移动端的行高需要微调——由于手机屏幕宽度有限,每行容纳的字数更少,文字换行更频繁。更频繁的换行意味着行与行之间的视觉关联更重要——行高需要略微增加:
@media (max-width: 640px) {
:root {
--leading-base: 1.55; /* 1.5 → 1.55,增加行间距 */
--leading-lg: 1.5; /* 1.45 → 1.5 */
}
}
为什么移动端行高需要增加? 在桌面端,一行可以容纳 60-80 个中文字符,用户阅读时眼睛沿水平方向移动,行与行之间的跳跃较少。在移动端,一行只能容纳 15-20 个中文字符,用户需要频繁换行——如果行高不够,行与行之间的文字会"粘"在一起,用户在换行时容易"跳错行"(读到下一行或上一行的错误位置)。
增加 0.05 的行高比例(从 1.5 到 1.55)在 16px 字号下只增加了 0.8px 的行间距——但这个微小的增量足以让移动端的换行感知更清晰。
七、排版与设计系统其他维度的关系
7.1 排版与色彩
排版和色彩是设计系统中两个最基础的维度——它们共同决定了信息的"可读性"。一个常见的错误是:字号足够大但色彩对比度不足,或色彩对比度足够但字号太小。
WCAG 的双重约束:
| 级别 | 正文字号 | 对比度要求 | 辅助文字 | 对比度要求 |
|---|---|---|---|---|
| AA | ≥ 16px | ≥ 4.5:1 | ≥ 12px | ≥ 4.5:1 |
| AAA | ≥ 16px | ≥ 7:1 | ≥ 12px | ≥ 7:1 |
| AA (大字号) | ≥ 18px (Bold) 或 ≥ 24px | ≥ 3:1 | — | — |
关键洞察:大字号(≥ 24px 或 ≥ 18px Bold)的对比度要求更低(3:1 vs 4.5:1)。这是因为大字号本身的视觉面积更大,即使对比度较低也能被清晰识别。这意味着排版层级可以补偿色彩对比度的不足——如果你希望使用低对比度的辅助色(如 var(--ink-3)),确保使用它的文字字号足够大。
本次项目中的实践:
/* 辅助文字:小字号 + 低对比度 → 不满足 WCAG AA */
.caption-low-contrast {
font-size: var(--text-xs); /* 12px */
color: var(--ink-4); /* #999 — 对比度约 2.8:1 */
}
/* 辅助文字:小字号 + 足够对比度 → 满足 WCAG AA */
.caption {
font-size: var(--text-xs); /* 12px */
color: var(--ink-3); /* #666 — 对比度约 5.7:1 */
}
原则:小字号必须用高对比度色彩,大字号可以用低对比度色彩。排版和色彩不是独立的可访问性维度——它们是联合约束。
7.2 排版与间距
排版和间距的关系体现在垂直节奏(Vertical Rhythm)上——每行文字的底部与下一行文字的顶部之间的距离应该遵循间距系统。
:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--space-12: 48px;
}
垂直节奏的理想状态:所有垂直方向的间距(行高、段落间距、section 间距)都是间距 Token 的整数倍。例如,如果基准行高是 24px(= --space-6),那么段落间距应该是 48px(= --space-12,2 倍行高),section 间距应该是 96px(= 4 倍行高)。
本次项目的实践:
.section {
padding: var(--space-12) 0; /* 48px — 2 倍基准行高 */
}
.paragraph {
margin-bottom: var(--space-6); /* 24px — 1 倍基准行高 */
}
为什么垂直节奏重要? 因为它让页面在垂直方向上形成"网格感"——所有文字行和间距都对齐到一个隐含的网格上。这种网格感让页面看起来"有序"——即使用户看不到网格线,他们也能感知到"这个页面的垂直布局是有规则的"。
反面案例:如果段落间距是 20px(不是间距 Token 的倍数),section 间距是 45px——这些"不规则"的间距让页面在垂直方向上失去节奏感,用户会感觉"这个页面的布局有点乱",虽然他们说不清具体哪里不对。
7.3 排版与动效
排版与动效的关系体现在文字入场动画和count-up 数字动画上——这些在第五篇(微交互)和第六篇(叙事节奏)中已经详细讨论过。这里补充一个排版特有的动效维度:字重过渡。
.nav-item {
font-weight: 400;
transition: font-weight 0.2s var(--ease);
}
.nav-item:hover {
font-weight: 500;
}
字重过渡的问题:大多数字体只有 400 和 700 两个字重——从 400 过渡到 700 时,浏览器会"插值"中间的字重,但这种插值是"假"的——它不是真正的 500 字重,而是 400 和 700 之间的视觉混合。这种假插值在某些字体中会产生"模糊"的效果——文字在过渡过程中看起来像"失焦"了。
解决方案:使用可变字体(Variable Font)——可变字体支持连续的字重轴(如 400-700 之间的任何值),字重过渡是"真"的,不会产生模糊。
@font-face {
font-family: 'SystemVariable';
src: local('San Francisco'), local('Segoe UI');
font-weight: 100 900; /* 可变字重范围 */
}
.nav-item {
font-family: 'SystemVariable';
font-weight: 400;
transition: font-weight 0.2s var(--ease);
}
.nav-item:hover {
font-weight: 500;
}
但系统字体不一定是可变字体——macOS 的 San Francisco 从 macOS 11 开始支持可变字重,但 Windows 的 Segoe UI 不支持。这意味着字重过渡在 macOS 上是"真"的,在 Windows 上是"假"的。
实用策略:如果字重过渡在你的目标平台上不可靠,用色彩过渡替代——hover 时改变文字颜色而非字重,效果类似但更可靠:
.nav-item {
color: var(--ink-3);
transition: color 0.2s var(--ease);
}
.nav-item:hover {
color: var(--ink); /* 从灰色变为黑色 — "加重"效果 */
}
色彩从 --ink-3(灰色)变为 --ink(黑色)的视觉效果与字重从 400 变为 500 类似——都是"增加视觉权重"。但色彩过渡在所有平台上都可靠,不存在"假插值"的问题。
八、排版系统的工程原则
8.1 字号比例的验证方法
如何验证你的字号比例是否合理?用一个简单的方法:截图所有字号,并排排列。
12px 14px 16px 18px 20px 24px 30px 40px
─────────────────────────────────────────────────
小 小 基准 中 中 大 大 Hero
验证标准:
- 相邻字号之间应该有可感知的差异——如果你看不出 14px 和 16px 的区别,比例太小
- 相邻字号之间不应该有过大的跳跃——如果 24px 和 30px 看起来像"两个不同世界的字号",比例太大
- 整体序列应该有均匀的递增感——从最小到最大,视觉权重应该平滑递增,而非"突然变大"
8.2 行高的验证方法
段落测试:用你的正文字号和行高写一段 5-6 行的文字,观察阅读体验:
- 如果行与行之间"粘"在一起——行高太小(低于 1.4)
- 如果行与行之间"断"开——行高太大(高于 1.7)
- 如果阅读时眼睛能自然地从一行移到下一行——行高合适(1.4-1.6)
标题测试:用你的标题字号和行高写一个 2-3 行的标题,观察视觉效果:
- 如果标题的行与行之间间距与正文的行间距相同——标题行高太大(标题需要更紧凑的行高)
- 如果标题的行与行之间几乎"重叠"——标题行高太小(低于 1.1)
- 如果标题的行间距比正文紧凑但仍然清晰——标题行高合适(1.15-1.35)
8.3 字重层级的验证方法
灰度测试:将页面截图转为灰度模式,观察不同字重的"明暗差异":
- 400 字重的文字应该是最"淡"的(笔画最细)
- 700 字重的文字应该是最"浓"的(笔画最粗)
- 500 和 600 字重应该在 400 和 700 之间,且 500 比 600 更"淡"
如果灰度模式下 400 和 500 看起来一样——说明你的字体在这个字号下不支持 500 字重(浏览器用 400 替代了 500)。这是一个常见问题——某些系统字体在 12px 以下不支持 500 字重。
解决方案:对于小字号(12px 以下),不使用 500 字重——直接从 400 跳到 600,因为 600 在所有字号下都可靠。
8.4 排版 Token 的命名规范
/* ✅ 语义命名 — 表达"这是什么角色" */
--text-hero: 2.5rem;
--text-title: 1.5rem;
--text-body: 1rem;
--text-caption: 0.75rem;
/* ✅ 尺寸命名 — 表达"这是多大" */
--text-4xl: 2.5rem;
--text-2xl: 1.5rem;
--text-base: 1rem;
--text-xs: 0.75rem;
/* ❌ 数值命名 — 表达"这是多少像素" */
--text-40: 2.5rem;
--text-24: 1.5rem;
--text-16: 1rem;
--text-12: 0.75rem;
本次项目选择尺寸命名(--text-2xl)而非语义命名(--text-title)——因为语义命名把"角色"和"尺寸"绑定在了一起,当你需要把标题从 24px 改为 20px 时,--text-title 这个名字就不再准确了。尺寸命名让 Token 保持"纯尺寸"语义,角色由 Component Token(--font-title)来表达。
九、总结:排版系统的五个层次
| 层次 | 做什么 | 核心原则 | 实践 |
|---|---|---|---|
| 1. 字号比例 | 定义字号之间的数学关系 | 等比而非等差,比例随字号递增 | Minor Third (1.2),16px 基准 |
| 2. 行高节奏 | 定义行高与字号的联动关系 | 字号越大,行高比例越小 | 1.6 → 1.15 的递减序列 |
| 3. 字重语义 | 定义字重与信息层级的映射 | 字重是语义标记,不是装饰 | 400/500/600/700 四级语义 |
| 4. 字体族策略 | 定义字体族与平台的关系 | 系统字体优先,等宽标记数据 | 系统字体栈 + mono 字体栈 |
| 5. 响应式缩放 | 定义排版在不同屏幕下的调整 | 只缩放大字号,小字号保持不变 | clamp() + 断点调整 |
核心洞察:排版不是"选择字体和字号",而是构建一套从数学比例到阅读节奏的工程系统。字号比例确保层级之间的数学关系一致,行高节奏确保阅读舒适度与字号联动,字重语义确保信息层级有明确的视觉标记,字体族策略确保跨平台的一致体验,响应式缩放确保不同屏幕下的排版适配。
当这五个层次叠加在一起时,排版系统就像建筑的结构框架——用户看不到框架本身,但他们能感受到"这个界面的文字排列是有秩序的"。好的排版系统让用户在阅读时"忘记排版的存在"——他们专注于内容,而非"这个字号是不是太小了"或"这些行是不是太挤了"。排版系统的终极目标不是让文字"好看",而是让文字"好读"——好看是审美判断,好读是工程结果。
本文是设计系统系列的第八篇。系列文章:1. 从 Token 到主题 · 2. 从装饰到功能 · 3. 动效系统工程 · 4. 设计语言迁移实践 · 5. 组件级微交互设计 · 6. 页面叙事节奏 · 7. 数据可视化的组件级实践 · 8. 排版系统工程
- 点赞
- 收藏
- 关注作者
评论(0)