排版系统工程:从字号比例到阅读节奏的层级构建

举报
霄均 发表于 2026/10/15 16:46:54 2026/10/15
【摘要】 排版系统工程:从字号比例到阅读节奏的层级构建本文是设计系统系列的第八篇。前七篇分别讨论了 Token 架构、风格光谱、动效系统、设计语言迁移、组件级微交互、页面叙事节奏和数据可视化组件——涵盖了色彩、动效、布局、交互等维度。本文则聚焦一个贯穿所有维度但始终未被独立展开的基础支柱:排版系统。排版不是"选个字体调个字号",而是一套从数学比例到阅读节奏的工程体系——它决定了用户能否在你的界面上舒...

排版系统工程:从字号比例到阅读节奏的层级构建

本文是设计系统系列的第八篇。前七篇分别讨论了 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? 因为效率工具的排版需要满足两个约束:

  1. 层级差异可感知——标题和正文必须有明显的字号差异,用户一眼就能区分
  2. 层级数量有限——效率工具不需要营销页面那种 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;
}

等宽字体的两个语义:

  1. 标识符(任务ID、编号、代码片段):等宽字体让每个字符占据相同的宽度,TASK-001 和 TASK-100 的长度相同——用户可以快速对齐和比较标识符。
  2. 数字(指标值、统计数据):等宽字体的 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. 排版系统工程

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。