CJK 排版三件套:中文 web 必开(2026)
影响力:**
text-autospace: normal(Baseline 2025)+text-spacing-trim(Chrome 123+ 默认)**第一次让中文 web 有原生标点收紧 + 中英文自动间距,告别 pangu.js 后处理时代。 干活密度:🟢 干活级 必读人群:任何做中文 web 内容站、产品 UI、文章站的工程师
🔥 影响力卡片
text-autospace: normal:Baseline 2025(Newly available since Nov 2025);Safari 18.4+ ship,Chrome 120 起 flag,目前已默认开启;Firefox Positivetext-spacing-trim:Chrome / Edge 123+ 默认开启;Safari 26.x 未支持;Firefox 未支持;全球覆盖 ~72%hanging-punctuation:只有 Safari(Chrome / Firefox 全无);已列入 Interop 2026 提议清单- 字体 subsetting:cn-font-split(Rust+WASM)是中文站事实标准
🎯 为什么必读
中文 web 有两个长期痛点:
- 中英文之间需不需要加空格?(过去靠 pangu.js 后处理或源码里手工加)
- CJK 标点 kerning(连续标点会留两个全角空白,行首/行尾标点占位过宽)
CSS 终于有了原生方案。中文产品的视觉品质,2026 年起会有肉眼可见的台阶式提升。
一句话总结
2026 中文 web 必开
text-autospace: normal+text-spacing-trim+<html lang="zh-CN">,Safari 用 OpenTypehalt兜底。
💎 金句墙
★ “text-autospace 让中文与英文/数字之间的『空格之争』终结。” —— 译者点评:这是真正的 native 解决方案。Source 写
中文 English 中文,浏览器自动渲染 1/4 空格;源码不用加空格。pangu.js 时代终结
★ “text-spacing-trim is the long-awaited native answer for CJK punctuation kerning.” “text-spacing-trim 是 CJK 标点 kerning 久违的原生答案。” —— 译者点评:Chrome/Edge 123+ 默认开启,意味着 ~70% 中文 web 用户已经能享受到。Safari/FF 暂不支持,需要 progressive enhancement
📋 核心精读
1. 三件套 CSS
:root {
/* 中英文自动间距 */
text-autospace: normal; /* = ideograph-alpha + ideograph-numeric */
/* CJK 标点 kerning */
text-spacing-trim: trim-start; /* 行首/行尾标点收紧 */
/* 或更激进 */
/* text-spacing-trim: space-all; */
}
/* 关闭 mono / 代码块 */
pre, code, kbd, samp {
text-autospace: no-autospace;
}
<!-- 必须 -->
<html lang="zh-CN">
🟢 译者点评:<html lang="zh-CN"> 不是装饰。它让浏览器走对应语言的 OpenType locl + 行首尾规则、断行规则。没有 lang 标注,以上 CSS 部分功能不工作。
2. Safari fallback(text-spacing-trim 不可用)
/* @supports 检测原生支持,不支持时用 OpenType */
@supports not (text-spacing-trim: trim-start) {
body {
font-feature-settings: "halt" 1; /* 半宽变体 */
/* 或更紧 */
/* font-feature-settings: "palt" 1; */
}
}
/* 大字号场景(hero / 标题)用 palt */
.heading {
font-feature-settings: "palt" 1, "kern" 1;
}
🟢 译者点评:palt 与 halt 互斥,只开一个。palt 比例化字宽(适合 UI 标题 / banner),halt 半宽(收紧标点)。Safari 26 / Firefox 暂时只能用这条路。
3. hanging-punctuation(渐进增强)
/* 段首引号挂边 */
blockquote {
hanging-punctuation: first last allow-end;
}
🟢 译者点评:只有 Safari 支持(且只有 first / first allow-end)。不可作为关键样式 —— 失败时不影响阅读即可。Interop 2026 已提议,有望 2026 内动起来。
4. CJK 字体 subsetting(cn-font-split)
pnpm add -D cn-font-split
npx cn-font-split \
-i SourceHanSerifCN.otf \
-o ./public/fonts/serif \
--chunk-size 100
会输出:
public/fonts/serif/
├── chunk-0.woff2 # 高频字
├── chunk-1.woff2 # 中频字
├── ...
├── chunk-N.woff2 # 低频字
└── result.css # 含 unicode-range 的 @font-face
@import url('/fonts/serif/result.css');
body { font-family: 'Source Han Serif CN', serif; }
🟢 译者点评:Rust + WASM 多线程,智能切片。中文站常从 4MB 字体压到 < 400KB;按页面字符按需加载,unicode-range 触发下载,首屏只下当前页用到的字。
字体源:https://chinese-fonts.com 直接拿可用 CDN。
5. Intl 七件套(原生,ES2026)
// 货币
new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' })
.format(1234.5); // "¥1,234.50"
// 紧凑数字
new Intl.NumberFormat('zh', { notation: 'compact' })
.format(12345); // "1.2万"
// 相对时间
new Intl.RelativeTimeFormat('zh', { numeric: 'auto' })
.format(-1, 'day'); // "昨天"
// 中日韩分词 ⭐
const seg = new Intl.Segmenter('zh', { granularity: 'word' });
[...seg.segment('我爱前端开发')]
.map(s => s.segment); // ['我', '爱', '前端', '开发']
🟢 译者点评:能用原生就别引第三方。Intl.Segmenter 让中日韩分词不再需要 jieba.js / nodejieba(几 MB 词典)。Tailwind v4 logical properties + Intl 七件套,前端国际化基础全是浏览器原生。
6. RTL(2026 必做)
/* 不再写 margin-left,写 margin-inline-start */
.card {
margin-inline-start: 1rem;
padding-block: 0.5rem;
border-inline-start: 2px solid;
}
<!-- Tailwind v4(2026-02 v4.2 扩充逻辑属性 utility) -->
<div class="ms-4 pe-5 ps-2 border-s">…</div>
<!-- 切换 dir 自动镜像 -->
<html lang="ar" dir="rtl">
🟢 译者点评:2026 起新项目应该 100% 使用逻辑属性。多语项目零成本支持阿语、希伯来语;切换 dir 整站自动镜像。
🟢 译者总评
- 现在就开:在
:root加text-autospace: normal+<html lang>—— 零风险,~72% 用户立即受益 - Safari fallback:
@supports not (text-spacing-trim)加font-feature-settings: "halt"—— 渐进增强 - 字体必 split:任何中文站都要走 cn-font-split,这是首屏 LCP 的关键
- 后处理 pangu.js 可以拆了:
text-autospace: normal之后,源码无需加空格,后处理工具可以从 build pipeline 移除 - 配套读:CSS Wrapped 2025 的 22 个特性 + Una Kravets
border-shape也在中文长文章排版有用武之地 - 关键 caveat:MF2(MessageFormat 2)仍 Stage 1/2,生产用 MF1 + Lingui/next-intl 包装