
最近在两个多语言项目的改版里我反复被同一个问题折磨一套界面在中文、英文下都好好的只要切到阿拉伯语或者日文竖排布局就碎成一地。改完 margin-left 改 padding改完 padding 又发现绝对定位跑偏整个下午都在做无用功。后来我把视线收回到“文本方向布局”这个方向上把 CSS 里那些平时没人细看的逻辑属性、书写模式重新梳理了一遍再做了一套轻量封装。没错就是标题里说的 Pretext。它不是什么重型 UI 库而是一套聚焦文本方向、文本装饰与前排队列的布局方案让开发者不用在物理属性里反复横跳直接按“文本实际流向”来写样式。这篇文章不聊虚的就围绕 Pretext 这套思路把文本布局这件事从原理、实操到踩坑、面试怎么讲一次性说透。适合正在做国际化项目、电子书阅读器、中文竖排排版或者准备高级前端面试的朋友。你会看到它到底解决了什么问题我会带着你从零实现一个能用的文本布局层也会把我在竖排、RTL、混排里踩过的坑全部摊开。1. 项目定位与核心需求拆解1.1 Pretext 到底解决什么问题先说结论Pretext 解决的是“文本在页面里按什么方向流动”这件事。传统 CSS 布局里大多数人默认文字是从左往右、从上往下走的所以写样式的时候也习惯用 margin-left、padding-top、text-align: left 这类物理属性。可一旦文本方向变了比如阿拉伯语从右往左读或者中文古籍按列从上往下排这些物理属性全部失效。我举个真实例子。一个卡片组件内部有个图标和一段说明文字我最初用 margin-left 把图标和文字拉开距离。英文版本没问题阿拉伯语版本一上图标应该挪到文字右侧但 margin-left 纹丝不动最后我只能写两套样式用 dir 属性去切换。这还不算最惨的遇到绝对定位的元素如果参照系还是左上角那 RTL 下整个位置就是错的。Pretext 的核心思路就是把 margin、padding、border、inset 这些属性全部换成逻辑属性让布局元素跟着文字方向自动翻转。同时Pretext 也把文本装饰和文本方向做了统一处理。text-decoration、text-emphasis、underline-offset 这些属性在竖排文本里表现完全不一样而大多数项目根本不会去关心这些细节。Pretext 把它们也纳入布局层管理避免出现“方向对了、装饰线错位”这种问题。1.2 为什么传统方案不够用很多人会觉得我又不做阿拉伯语和竖排这东西跟我没关系。但有三个场景早晚会找上你。第一个是国际化。现在的后台管理系统、出海应用几乎都要支持多语言。就算现在只做中英文保不准哪天产品经理就会提一嘴“我们下个版本要上中东市场”。到那时候再改布局体系成本比从开始就用逻辑属性高好几倍。我见过一个项目RTL 切换靠一个全局 class 覆盖几百个选择器每次版本迭代都提心吊胆。第二个是响应式大屏。vue3element plus 那套自适应大屏方案里很多布局是用 flex 或者 grid 撑开的。flex 本身会跟着 direction 翻转可一旦某个子元素用了 margin-left栅格错位就来了。Pretext 的逻辑属性体系在大屏自适应里也站得住脚因为它不关心具体屏幕宽度只关心块级方向和内联方向。第三个是文档排版。电子书、古籍阅读器、诗词展示、甚至简历设计都有横排竖排混排的需求。纯用 writing-mode 切竖排不难难的是竖排之后段落间距、标点压缩、数字横排这些细节怎么处理。Pretext 把这些细节封装成统一的类让排版设计者不用去背 CSS 规范。1.3 适用场景和对象这套思路最适合三类人。第一类是国际化项目的前端需要处理 RTL、多语言文本布局第二类是阅读器、文档站、排版工具的前端需要处理竖排、中西混排第三类是准备面试的前端工程师文本方向布局几乎是高级岗位躲不开的考点把 Pretext 这套方法论讲清楚比背八股文有说服力得多。换句话说Pretext 不是一个必须安装的 npm 包它更像一种设计约定用文本的逻辑流向组织布局而不是用屏幕的物理坐标组织布局。掌握了这个约定不管以后出现什么新框架你都能在文本布局这块游刃有余。2. 文本方向布局的关键原理2.1 物理属性与逻辑属性的博弈物理属性就是 margin-left、padding-top、border-right、left、top 这些它们绑定的是屏幕坐标。逻辑属性是 margin-inline-start、padding-block-end、border-inline-end、inset-inline-start 这些它们绑定的是文本流方向。这里的 inline 指的是文字书写方向上的“行内方向”block 指的是段落堆叠的“块级方向”。我打个比方。物理属性像是按地图方位指路“往北走 100 米再往西拐”逻辑属性是按人的左右指路“往前走 100 米再往左拐”。在中文里“前”“后”“左”“右”是相对的换个人面朝另一个方向指令依然成立但“北”“南”是绝对的人换了方向就错了。文本布局也一样只要界面的书写方向一变物理属性就全乱逻辑属性会自动跟着文字方向走。在实际开发中我会给自己定一条硬性规则布局相关的间距、定位、边框一律使用逻辑属性只有盒模型本身相关的 width、height 这种明确的物理尺寸才继续用传统写法。这样做的好处是当项目需要做 RTL 或者竖排的时候我的样式代码基本不用动只要在根节点切换 direction 和 writing-mode 就行。2.2 writing-mode、direction 与 unicode-bidi 三兄弟文本方向布局里有三个 CSS 属性最容易让人混淆分别是 writing-mode、direction 和 unicode-bidi。writing-mode 决定的是整个文档或者某个元素内部的书写模式。默认是 horizontal-tb即横向书写、块级方向自上而下vertical-rl 是纵向书写、块级方向从右到左类似古书排版vertical-lr 是纵向书写、块级方向从左到右常见于部分日文排版。direction 决定的是内联元素的起始方向。ltr 是左到右rtl 是右到左。它不改变块级方向只改变一行内文字和行内元素的走向。大多数情况下RTL 布局只需要把 direction 切到 rtlflex 和 grid 会自动跟随翻转但前提是你的 margin、padding 都是逻辑属性。unicode-bidi 则处理的是双向文本算法中的嵌套方向。比如一段阿拉伯语里夹杂英文浏览器怎么决定谁从左往右、谁从右往左就是 unicode-bidi 和 direction 协同工作的结果。Pretext 在封装时把这三个属性统一到一个方向上下文里通过一个>:root { --ptx-writing-mode: horizontal-tb; --ptx-direction: ltr; --ptx-text-orientation: mixed; --ptx-block-gap: 1.5rem; --ptx-inline-gap: 1rem; } [data-ptx-modertl] { --ptx-direction: rtl; } [data-ptx-modevertical] { --ptx-writing-mode: vertical-rl; --ptx-text-orientation: upright; }细看这段代码我并没有直接把 writing-mode 写到某个类上而是统一通过变量来控制。好处是后续你可以在 JavaScript 里动态修改>.ptx-context { writing-mode: var(--ptx-writing-mode); direction: var(--ptx-direction); text-orientation: var(--ptx-text-orientation); }这段代码看似简单但它把 writing-mode、direction、text-orientation 三者的组合关系封装成了一个类。你在 HTML 里只要写div classptx-context>div classptx-context>.ptx-card { padding-inline: var(--ptx-inline-gap); padding-block: calc(var(--ptx-block-gap) * 0.75); border-inline-start: 4px solid #2563eb; } .ptx-card__title { margin-block-end: 0.5rem; } .ptx-card__desc { text-align: start; } .ptx-card__tag { margin-inline-start: auto; }这段代码里藏着几个关键点。padding-inline 就是左右方向的内边距在 RTL 下它会自动左右互换你不用写两套。border-inline-start 是文本起始方向的边框在 LTR 下是左边框在 RTL 下变成右边框一个卡片左边框变右边框布局就完成了镜像。text-align: start 是文本对齐的逻辑写法比 left 更安全。最有意思的是 margin-inline-start: auto这个用法可以把一个元素推到文本流的末尾方向在 RTL 下标签会自动跑到左侧卡片布局瞬间镜像样式代码一行都不用改。竖排模式下这套逻辑同样是有效的。还是这个卡片只要把>import { ref, computed } from vue; const modeMap { ltr: { writingMode: horizontal-tb, direction: ltr, textOrientation: mixed }, rtl: { writingMode: horizontal-tb, direction: rtl, textOrientation: mixed }, vertical: { writingMode: vertical-rl, direction: rtl, textOrientation: upright }, }; export function useTextLayout(initialMode ltr) { const mode ref(initialMode); const ptxStyle computed(() { const conf modeMap[mode.value]; return { writingMode: conf.writingMode, direction: conf.direction, textOrientation: conf.textOrientation, }; }); function setMode(nextMode) { mode.value nextMode; } function toggleMode() { const keys Object.keys(modeMap); const idx keys.indexOf(mode.value); mode.value keys[(idx 1) % keys.length]; } return { mode, ptxStyle, setMode, toggleMode }; }这段代码里我特意把 writingMode、direction、textOrientation 一起塞进了 ptxStyle这样在模板里可以直接用 :style 绑定。不要只在 direction 上做文章writing-mode 不变的话再怎么切 direction中文古籍也不会变成竖排。在组件里用的时候也很简单template section classptx-context :styleptxStyle slot / /section button clicktoggleMode切换排版方向/button /template script setup import { useTextLayout } from /composables/useTextLayout; const { ptxStyle, toggleMode } useTextLayout(ltr); /script这样一个面向方向的容器组件就出来了。你可以在它内部放任何业务组件切方向只动容器内部组件的逻辑属性自动生效。我之前在一个阅读器项目里试过整套排版从横排切竖排只改了这个容器组件的状态内部十几个组件完全没有动过样式实测下来很稳。3.4 用配置化 JSON 驱动整个页面布局到这里单组件方案已经能用了但离“革命性突破”还差一步。我把 Pretext 的路子再往前走了一点让文本布局可以像配置数据字典一样用一个 JSON 对象全盘描述。为什么要这样做因为前端项目里布局方案的调整往往不是开发者一个人说了算产品、设计、运营都会参与他们不懂 CSS但都看得懂 JSON。{ page: { mode: vertical, contextClass: ptx-context }, header: { tag: header, direction: ltr, blockGap: 1.25rem }, content: { tag: main, direction: rtl, textAlign: start, deco: { emphasis: filled, underlineOffset: 0.25em } }, footer: { tag: footer, direction: ltr } }这个 JSON 里page 定义了整页的书写模式header、content、footer 各自定义了方向、间距和装饰配置。前端要做的事情就是把这个 JSON 解析成 CSS 变量和类名渲染到对应的 DOM 节点上。这里我只展示一个小型的实现思路。function applyConfig(config) { document.documentElement.style.setProperty(--ptx-block-gap, config.page.blockGap || 1.5rem); const sectionEls document.querySelectorAll([data-ptx-section]); sectionEls.forEach((el) { const key el.dataset.ptxSection; const section config[key]; if (!section) return; if (section.direction rtl) { el.setAttribute(dir, rtl); } else { el.removeAttribute(dir); } if (section.deco) { el.classList.add(ptx-deco); el.style.textDecorationStyle section.deco.emphasis ? wavy : solid; el.style.textUnderlineOffset section.deco.underlineOffset || 0.1em; } }); }这段脚本的好处在于后续如果产品要做 A/B 测试或者运营要临时调整某个版块的排版方向只需要改配置文件前端不用发版。我在一个双语资讯站里跑过这套方案中东版和中文版共用同一套代码只差一份 JSON 配置效果比预期好很多。4. 常见问题与排查技巧实录4.1 兼容性逻辑属性和 writing-mode 的底线在哪很多同行一听逻辑属性就担心老旧浏览器实际上现代浏览器的支持已经非常完善。Chrome、Edge、Firefox、Safari 在 2023 年之后的主流版本里对 margin-inline、padding-block、border-inline-start 这些属性基本都支持了。writing-mode 更不用多说IE 时代就有只是当时用的还带前缀现在早就不用了。如果你必须支持 IE11 或者极老的内核浏览器我建议用 supports 做降级。.ptx-card { padding-left: 1rem; padding-right: 1rem; } supports (padding-inline: 1rem) { .ptx-card { padding-left: unset; padding-right: unset; padding-inline: 1rem; } }先写一遍物理属性兜底再在支持的浏览器里用逻辑属性覆盖。这样至少能保证老浏览器不崩新浏览器享受逻辑属性的便利。如果项目本身有 PostCSS 构建链也可以查一下 postcss-logical 插件它会把逻辑属性自动转成物理属性省去手写兜底的麻烦。4.2 竖排文本与标点处理的坑竖排模式好看但细节非常多。我最开始做竖排时第一个遇到的问题是中英文和数字混排。默认竖排模式下数字和英文单词会整体旋转 90 度看起来特别别扭。我查了一圈发现 text-combine-upright 这个属性可以解决数字横排在竖排文本里的问题。.ptx-deco-numeral { text-combine-upright: digits 2; }text-combine-upright: digits 2 的意思是把最多两位数字横向压缩整体正立出现在竖排文本里。这个在竖排的日期、页码、电话号码场景里非常有用。需要注意的是这个属性适合两到三位数字长度太长会压缩得没法看。第二个坑是标点压缩。竖排中文时句号、逗号、引号应该压在文字的一角而不是单独占一个宽字符位置。CSS 的 text-spacing-trim 属性是可以处理这个的但这个属性目前 Chrome 支持得还不好。退而求其次我会在竖排容器里手动把标点的字号缩小一点或者给标点加一层 white-space 处理让标点贴着上一个字符走。第三个坑是下划线。横排里 text-decoration: underline 很简单竖排里下划线默认跑到文字底下看起来像是装饰消失了。我实测下来的处理方式是竖排容器里用 text-underline-offset 配合 writing-mode 调整偏移量同时改成 text-decoration-line: underline 加 text-underline-position: under让竖排里的强调线能顺着文字方向走。4.3 切换语言时布局崩掉的排查顺序遇到切语言布局崩溃不要慌按顺序排查大概率几分钟就能定位。先看方向上下文有没有生效。打开开发者工具选中最外层容器检查 writing-mode 和 direction 实际计算值确认不是别的样式覆盖掉了。再看内部元素用的是物理属性还是逻辑属性。如果某个元素还在用 margin-left 这种写法方向切换它自然不会动。接着看定位类型。absolute 定位的元素方向切换后参考点不会自动翻转。我后来养成的习惯是能用 inset-inline-start、inset-inline-end 就用不要写 left、right。再看 flex 和 grid 内部是不是有显式排序。flex 默认感知 direction但 flex-direction: row 不会变flex-direction: row-reverse 也不会变所以尽量不要手动去 reverse让 direction 自己去控制。最后看文本对齐。text-align: left 是个隐蔽杀手很多布局崩了查来查去就是因为它。把所有 text-align: left/right 换成 start/end至少能避免一半以上的 RTL 问题。4.4 面试里怎么把 Pretext 思路讲出价值文本方向布局在面试题里出现频率不低。常见的问法是“怎么实现一个双语网站的无障碍布局”或者“RTL 下你的样式会出什么问题”。如果你只回答“加个 dirrtl”那基本就是初级水平。把 Pretext 这套思路讲清楚面试观感会完全不同。我会这样拆解回答先抛出问题文本方向是布局的隐藏维度物理属性在这里会失效。然后分三层讲解决方案第一层是逻辑属性把 margin、padding、border、inset 全部换成 inline/block 维度保证布局随方向自动镜像第二层是书写模式用 writing-mode 和 text-orientation 处理竖排和字符方向第三层是工程化封装通过组合式 API 或者配置化 JSON把方向状态从组件里抽离出去让设计师和运营也能参与排版调整。这三层讲完面试官基本能判断你是踩过坑的人而不是背八股文的。最后你还可以补一句真实项目里这套方案最重要的收益是开发、产品和设计之间多了一层配置化沟通语言方向切换不再是代码改动而是数据改动。我在实际使用中还有一个自己的体会做文本方向布局第一步不是写代码而是把你项目里所有物理属性全部查一遍。你可以在编辑器里全局搜margin-left、padding-right、left:、right:、text-align: left把它们换成逻辑属性再上方向切换基本所有布局问题都会浮出水面。最后再分享一个小技巧在你做好的方向容器根节点上加一个transition: writing-mode 0.2s, direction 0.2s这样切换横排竖排的时候整个版面会有一个平滑过渡效果视觉上比硬切高级不少用户在阅读器里来回切换时体验会很舒服。