中文排版与国际化设计规范
原创
灵阙教研团队
B 基础 提升 |
约 9 分钟阅读
更新于 2026-02-28 AI 导读
中文排版与国际化设计规范 CJK 排版、竖排文字与多语言界面设计 1. 中文排版的独特性 中文排版与西文排版有本质差异。西文是"词"为基本单位,用空格分隔,字母等宽或变宽混合;中文是"字"为基本单位,无空格分隔,每个字符占据一个正方形的"字身"。这些差异深刻影响了换行、对齐、间距和字体选择。 西文排版: The quick brown fox jumps. | | | | | | [词] [词]...
中文排版与国际化设计规范
CJK 排版、竖排文字与多语言界面设计
1. 中文排版的独特性
中文排版与西文排版有本质差异。西文是"词"为基本单位,用空格分隔,字母等宽或变宽混合;中文是"字"为基本单位,无空格分隔,每个字符占据一个正方形的"字身"。这些差异深刻影响了换行、对齐、间距和字体选择。
西文排版:
The quick brown fox jumps.
| | | | | |
[词] [词] [词] [词] [词] <- 以词为单位,空格分隔
中文排版:
这是一段中文排版示例文字。
| | | | | | | | | | | | |
[字][字][字]...[字][字][字] <- 以字为单位,无分隔
1.1 关键术语
| 术语 | 定义 | 设计影响 |
|---|---|---|
| 字身 | 每个字符占据的虚拟方块 | 字号 = 字身宽度 = 字身高度 |
| 避头尾 | 特定标点不出现在行首/行尾 | 需要换行算法支持 |
| 全角/半角 | 全角占一个字身,半角占半个 | 中英混排时需处理 |
| 直排/横排 | 文字方向:纵向/横向 | CSS writing-mode |
| 标点悬挂 | 行尾标点可悬挂在版心外 | hanging-punctuation |
2. 字体选择
2.1 中文字体分类
| 类别 | 特征 | 适用场景 | 代表字体 |
|---|---|---|---|
| 黑体 | 笔画粗细均匀,无衬线 | 标题、UI 界面 | 思源黑体、苹方 |
| 宋体 | 横细竖粗,有衬线 | 正文、印刷 | 思源宋体、方正书宋 |
| 楷体 | 模仿手写,笔画有起收 | 引用、文学 | 楷体、方正楷体 |
| 圆体 | 笔画末端圆润 | 儿童、轻松场景 | 苹方-简 粗、圆体 |
| 等宽 | 所有字符等宽 | 代码显示 | Sarasa Mono SC |
2.2 中文 Web 字体策略
/* 推荐的中文字体栈 */
/* 方案 A: 系统字体优先(加载快,体验一致) */
.font-system-zh {
font-family:
-apple-system, /* macOS/iOS 苹方 */
"PingFang SC", /* macOS 苹方(显式声明) */
"Microsoft YaHei", /* Windows 微软雅黑 */
"Noto Sans SC", /* Android/Linux */
"Source Han Sans SC", /* 跨平台开源 */
sans-serif;
}
/* 方案 B: Web Font(品牌一致性,加载较慢) */
@font-face {
font-family: "Source Han Sans SC";
src: url("/fonts/SourceHanSansSC-Regular.woff2") format("woff2");
font-weight: 400;
font-display: swap; /* 先显示后替换,避免 FOIT */
unicode-range: U+4E00-9FFF, U+3400-4DBF; /* 只加载中文字符 */
}
/* 方案 C: 子集化(仅加载页面用到的字符) */
/* 使用 fonttools 的 pyftsubset 工具 */
/* pyftsubset font.otf --text-file=chars.txt --output-file=subset.woff2 */
2.3 字体文件大小
| 字体 | 全量 | 常用 6000 字子集 | 格式 |
|---|---|---|---|
| 思源黑体 Regular | 8.5 MB | ~1.2 MB | OTF |
| 思源黑体 Regular | 4.2 MB | ~600 KB | WOFF2 |
| Noto Sans SC Regular | 8.8 MB | ~1.3 MB | OTF |
| Noto Sans SC Regular | 4.5 MB | ~650 KB | WOFF2 |
3. 中文排版 CSS 规范
3.1 基础排版
/* 中文正文排版基础 */
.zh-body {
/* 字体 */
font-family: "Source Han Sans SC", "PingFang SC", sans-serif;
font-size: 16px;
font-weight: 400;
/* 行高: 中文推荐 1.6-1.8 倍(高于西文的 1.4-1.5) */
line-height: 1.75;
/* 段落间距 */
margin-bottom: 1em;
/* 对齐: 两端对齐 */
text-align: justify;
text-justify: inter-ideograph; /* CJK 字间调整 */
/* 换行控制 */
word-break: normal;
overflow-wrap: break-word;
line-break: strict; /* 严格避头尾规则 */
/* 字间距: 中文一般不需要额外字间距 */
letter-spacing: 0;
/* 标点处理 */
hanging-punctuation: allow-end; /* 行尾标点悬挂 */
/* 颜色: 中文正文推荐深灰而非纯黑 */
color: #333333;
}
/* 标题 */
.zh-heading {
font-weight: 700; /* 中文加粗需要 700 */
line-height: 1.3; /* 标题行高较紧 */
letter-spacing: 0.02em; /* 标题可微调字间距 */
}
3.2 避头尾规则
不可出现在行首的字符(避头):
,。、;:!?)》」』】〉》)
不可出现在行尾的字符(避尾):
(《「『【〈《(
/* CSS 原生避头尾 */
.zh-text {
line-break: strict;
/* strict 模式下,浏览器自动处理 CJK 避头尾 */
}
3.3 中英文混排
/* 中英文混排间距 */
.zh-mixed {
/* 方案 A: CSS text-autospace (新标准,浏览器支持有限) */
text-autospace: ideograph-alpha ideograph-numeric;
/* 方案 B: 手动处理 */
/* 使用 JavaScript 在中文与英文之间插入 thin space */
}
// JavaScript 自动插入中英文间距
function addCJKSpacing(text) {
return text
// 中文后跟英文/数字
.replace(/([\u4e00-\u9fff])([\w])/g, '$1\u2006$2')
// 英文/数字后跟中文
.replace(/([\w])([\u4e00-\u9fff])/g, '$1\u2006$2');
// \u2006 = Six-Per-Em Space (1/6 em)
}
4. 竖排文字
4.1 CSS 竖排
/* 竖排排版 */
.vertical-text {
writing-mode: vertical-rl; /* 从右到左竖排 */
text-orientation: mixed; /* 中文竖排,英文横转 */
/* 或 */
writing-mode: vertical-lr; /* 从左到右竖排(蒙古文等) */
}
/* 竖排中的数字处理 */
.vertical-text .number {
text-combine-upright: all; /* 两位数字横排 */
/* 或 */
text-orientation: upright; /* 数字也竖排 */
}
4.2 竖排常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 标点位置错误 | 标点未随文字旋转 | font-feature-settings: "vrt2" |
| 英文方向错误 | 默认旋转90度 | text-orientation: mixed |
| 行距不均 | 全角半角混排 | 统一使用全角标点 |
| 连续数字 | 3-4位数字挤在一竖 | text-combine-upright: digits 2 |
| 表情符号 | 可能被旋转 | unicode-bidi: plaintext |
5. 国际化(i18n)设计
5.1 多语言界面挑战
| 挑战 | 中文 | 英文 | 阿拉伯文 | 日文 |
|---|---|---|---|---|
| 文字方向 | LTR | LTR | RTL | LTR/竖排 |
| 文字长度 | 短(表意文字) | 中 | 中 | 短-中 |
| 换行规则 | 按字换行 | 按词换行 | 按词换行 | 复合规则 |
| 字体体积 | 大(数万字) | 小(百字母) | 小 | 大 |
| 复数形式 | 无 | 2 种 | 6 种 | 无 |
| 数字格式 | 1,234.56 | 1,234.56 | 1,234.56 | 1,234.56 |
| 日期格式 | 2026年2月28日 | Feb 28, 2026 | 28/02/2026 | 2026/02/28 |
5.2 文本扩展预算
翻译后文本长度变化参考(以英文为基准):
| 目标语言 | 扩展比例 | 示例 |
|---|---|---|
| 中文 | 0.6-0.8x | "Save" -> "保存" |
| 日文 | 0.8-1.2x | "Save" -> "保存する" |
| 德文 | 1.3-1.8x | "Save" -> "Speichern" |
| 法文 | 1.2-1.6x | "Save" -> "Sauvegarder" |
| 阿拉伯文 | 1.2-1.5x | 从右到左 |
| 俄文 | 1.3-1.7x | "Save" -> "Сохранить" |
设计中必须为文本预留 50% 的扩展空间。
5.3 Flex 布局应对文本扩展
/* 不要固定宽度 */
.button {
/* 错误: width: 80px; */
/* 正确: */
min-width: 80px;
padding: 8px 16px;
white-space: nowrap;
}
/* 使用 flex 自适应 */
.nav-bar {
display: flex;
flex-wrap: wrap; /* 允许换行,避免溢出 */
gap: 8px;
}
.nav-item {
flex-shrink: 0; /* 不压缩 */
}
5.4 RTL(从右到左)支持
/* 逻辑属性替代物理属性 */
.card {
/* 物理属性 (不推荐): */
/* margin-left: 16px; */
/* padding-right: 24px; */
/* text-align: left; */
/* 逻辑属性 (推荐): */
margin-inline-start: 16px;
padding-inline-end: 24px;
text-align: start;
}
/* 图标翻转 */
[dir="rtl"] .icon-arrow-right {
transform: scaleX(-1); /* 水平翻转箭头 */
}
/* 但是! 某些图标不应翻转 */
[dir="rtl"] .icon-clock,
[dir="rtl"] .icon-phone,
[dir="rtl"] .icon-search {
transform: none; /* 时钟、电话、搜索不翻转 */
}
6. 数字与日期格式化
6.1 Intl API
// 数字格式化
function formatNumber(value, locale) {
return new Intl.NumberFormat(locale).format(value);
}
formatNumber(1234567.89, 'zh-CN'); // "1,234,567.89"
formatNumber(1234567.89, 'de-DE'); // "1.234.567,89"
formatNumber(1234567.89, 'ja-JP'); // "1,234,567.89"
// 货币格式化
function formatCurrency(value, locale, currency) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(value);
}
formatCurrency(1234.5, 'zh-CN', 'CNY'); // "CN\u00a51,234.50"
formatCurrency(1234.5, 'en-US', 'USD'); // "$1,234.50"
formatCurrency(1234.5, 'ja-JP', 'JPY'); // "JP\u00a51,235"
// 日期格式化
function formatDate(date, locale) {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
}
formatDate(new Date(), 'zh-CN'); // "2026年2月28日"
formatDate(new Date(), 'en-US'); // "February 28, 2026"
formatDate(new Date(), 'ja-JP'); // "2026年2月28日"
// 相对时间
function formatRelative(date, locale) {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const diff = Math.round((date - Date.now()) / (1000 * 60 * 60 * 24));
return rtf.format(diff, 'day');
}
formatRelative(yesterday, 'zh-CN'); // "昨天"
formatRelative(yesterday, 'en-US'); // "yesterday"
7. 排版质量检查
7.1 自动化检查清单
| 检查项 | 规则 | 工具 |
|---|---|---|
| 字体回退 | 中文字体不可用时有合理回退 | Playwright 截图对比 |
| 行首标点 | 无避头标点出现在行首 | 正则扫描 + 视觉检查 |
| 中英间距 | 中英文之间有适当间距 | 自动检测脚本 |
| 行长 | 中文每行 25-40 字 | CSS 检查 max-width |
| 行高 | 中文正文行高 >= 1.6 | CSS 检查 line-height |
| 字号 | 正文 >= 14px | CSS 检查 font-size |
| 文本扩展 | 界面元素容纳 1.5x 文本 | 伪翻译测试 |
| RTL 布局 | 逻辑属性替代物理属性 | stylelint 规则 |
7.2 伪翻译测试
// 伪翻译: 用扩展字符替换,测试布局弹性
function pseudoTranslate(text, expansionFactor = 1.5) {
const expanded = text
.split('')
.map(char => {
if (/[a-zA-Z]/.test(char)) {
// 用带音调的字符替换,增加视觉标识
const map = { a: 'a', e: 'e', i: 'i', o: 'o', u: 'u' };
return (map[char.toLowerCase()] || char).repeat(
Math.ceil(expansionFactor)
);
}
return char;
})
.join('');
return `[${expanded}]`; // 方括号标识伪翻译
}
// "Save" -> "[Saavee]"
// "Cancel" -> "[Caannceell]"
8. CJK 特殊场景处理
8.1 表格中的中文
.zh-table {
/* 中文表格不要 nowrap,允许单元格内换行 */
word-break: break-all; /* CJK 允许任意位置换行 */
/* 数字列右对齐 */
td.number { text-align: right; font-variant-numeric: tabular-nums; }
/* 中文列左对齐 */
td.text { text-align: start; }
}
8.2 输入法兼容
// 中文输入法组合事件处理
let isComposing = false;
input.addEventListener('compositionstart', () => {
isComposing = true;
});
input.addEventListener('compositionend', (e) => {
isComposing = false;
// 在这里处理最终输入,而不是在 input 事件中
handleInput(e.target.value);
});
input.addEventListener('input', (e) => {
if (isComposing) return; // 组合输入中,不触发搜索/验证
handleInput(e.target.value);
});
8.3 文本截断
/* 中文单行截断 */
.zh-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 中文多行截断 */
.zh-line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* 最多 3 行 */
overflow: hidden;
}
9. 性能优化
9.1 字体加载策略
策略对比:
+------------------+--------+----------+
| 策略 | FOIT | FOUT |
+------------------+--------+----------+
| font-display:auto | 3s闪白 | 无 |
| font-display:swap | 无 | 字体闪跳 |
| font-display:optional | 无 | 无(可能不加载)|
| 预加载 + swap | 极短 | 极短 |
+------------------+--------+----------+
推荐方案: 预加载 + swap
<!-- 预加载中文字体 -->
<link
rel="preload"
href="/fonts/SourceHanSansSC-Regular-subset.woff2"
as="font"
type="font/woff2"
crossorigin
>
9.2 按需加载
// Google Fonts 按 unicode-range 分片加载
// 浏览器只下载页面中实际使用到的字符范围
@font-face {
font-family: 'Noto Sans SC';
src: url('NotoSansSC-Regular.1.woff2') format('woff2');
unicode-range: U+fee3, U+fef3, U+ff03-ff04, U+ff07, U+ff0a-ff0b;
}
@font-face {
font-family: 'Noto Sans SC';
src: url('NotoSansSC-Regular.2.woff2') format('woff2');
unicode-range: U+5408, U+5409, U+540a-540d, U+540f-5411, U+5413;
}
/* ... 数十个分片 */
10. 工具与参考
| 工具 | 用途 |
|---|---|
| pyftsubset (fonttools) | 字体子集化 |
| cn-font-split | 中文字体自动分包 |
| Google Fonts | 中文 Web 字体 CDN |
| text-autospace polyfill | 中英间距自动处理 |
| stylelint-i18n | i18n CSS 规则检查 |
| ICU MessageFormat | 国际化消息格式 |
| Intl API | 浏览器原生国际化 |
| i18next | 前端 i18n 框架 |
参考规范:
| 规范 | 说明 |
|---|---|
| W3C CLReq | 中文排版需求 (Requirements for Chinese Text Layout) |
| W3C JLReq | 日文排版需求 |
| Unicode UAX #14 | Unicode 换行算法 |
| Unicode UAX #11 | 东亚字符宽度 |
| CSS Writing Modes L4 | 竖排排版规范 |
| CSS Text L4 | 文本处理规范 |
Maurice | maurice_wen@proton.me