← Web 排版:一行字怎么排布 / 双向文本:RTL 混排时字符的视觉顺序 待审核 8 / 10
bidi · direction

双向文本:RTL 混排时字符的视觉顺序

阿拉伯文、希伯来文从右往左书写(RTL),英文和数字从左往右(LTR)。一句话里两种文字混排时,字符在内存里仍按 逻辑顺序(打字的顺序)存放,渲染时浏览器按 Unicode Bidirectional Algorithm(UAX #9)重排出 视觉顺序。CSS 的 direction 决定段落的 基准方向(base direction),unicode-bidi 决定一段内嵌文本如何参与(或对抗)这套算法。

下面的版心是经典混排:العربية(阿拉伯文)+ 123(数字)+ hello(英文)+ שלום(希伯来文),每个词右上角的 角标数字是它的逻辑顺序。切换按钮重排,verdict 用 getBoundingClientRect() 实测出当前的 视觉顺序(从左到右),和逻辑顺序对照。

1 · dir attribute 与 CSS direction:优先级与分工

HTML 的 dir attribute 不是「另一套方向系统」——HTML 标准要求浏览器在 UA stylesheet 里把它 映射成 CSS,而且一次给两件:direction + unicode-bidi: isolate。所以上面的实验里,设了 dir 再设 inline 的 CSS direction,后者生效(inline style 优先级高于 attribute 映射)——verdict 会标注当前生效的来源。

2 · unicode-bidi 各值在做什么

  • normal——元素不额外开 embedding level,字符直接参与外层的 bidi 算法。
  • embed——开一个新的嵌入层级:元素内部以自己的 direction 为基准方向,但 不隔离——内部的强方向字符仍可能影响外面相邻的中性字符(经典的标点跳位 bug 来源)。
  • isolate——嵌入且 隔离:对外整体表现为一个「中性团块」,内外互不干扰。dir attribute 映射的就是它,现代代码几乎总该用 isolate 而不是 embed。
  • bidi-override——无视 UAX #9,内部字符 一律强制direction 指定的方向逐字排列——英文会被逐字母倒排。只用于展示字符存储顺序之类的特殊场景。
  • isolate-override——override 的效果 + isolate 的对外隔离。
  • plaintext——无视 direction,按内容 首个强方向字符 推断基准方向(效果对应 dir="auto"),用于显示来源未知的用户文本。

数字是「弱方向」字符。 实验里把基准方向切到 rtl,会发现 123 跟着阿拉伯文跑、但数字内部仍是 1→2→3——UAX #9 里数字属于弱类型(European Number),它不决定方向、只依附于上下文。这正是「逻辑顺序不变、视觉顺序重排」的核心:内存里永远是 1,2,3,变的只是这一团放在行内的哪个位置。

3 · reading-flow:视觉顺序时代的阅读顺序

bidi 解决的是「字符级」的视觉重排;到了 flex / grid 时代,orderflex-direction: row-reverse 也能把 盒子 的视觉顺序排得和 DOM 顺序完全不同——但 Tab 焦点、屏幕阅读器仍按 DOM 顺序走,体验割裂。css-display-4 的新属性 reading-flow(取值如 flex-visual / grid-rows / source-order)让阅读 / 焦点顺序跟随视觉顺序。