缓冲区的内容顺序基于文档结构而非样式表定义的视觉位置
别名: 源顺序 · 文档顺序 · CSS order · 视觉顺序
概念解释
虚拟缓冲区按什么次序排列节点,问的不是「屏幕上谁在谁左边」。阅读器走的是无障碍树的深度优先展开,而那棵树来自文档结构:DOM 顺序、aria-owns 改挂、影子树拍平。flex-direction: row-reverse、order、grid-area、position: absolute、transform 可以让盒子在视觉上对调,却进不了这份序列化——布局引擎的 used value 和树的遍历是两套计算。
这不是在说「阅读顺序应当跟视觉阅读一致」那条产品判据。那是另一件事。这里只回答机器:缓冲区怎么被建出来,CSS 的坐标根本没有被读进去。
机制
无障碍树的扁平化是一次树遍历。节点的先后由父子与兄弟关系决定,偶尔被 aria-owns 改写归属。CSS 视觉格式化是另一次计算:包含块、flex 主轴、grid 轨道、层叠上下文。两次计算的输入几乎不共享。「距左边 400 像素」从来不是「先念谁」的参数。
第二层因此很硬:视觉重排是免费的,结构重排才进缓冲区。把侧栏用 order: -1 挪到主列左边,看见的人先读侧栏,缓冲区仍先走 DOM 里靠前的主列。绝对定位把按钮钉在视口角落,语音还在它在源码里的那个段落附近出现。阅读器不是读错了布局,它从未订阅布局。
怎么研究
做一对材料:视觉顺序相反、DOM 顺序相反,其余文案相同。遮住显示器,用阅读器方向键记下语音序列,再对照截图上的从左到右、从上到下。再加一档:只用 aria-owns 改挂,不改 CSS,看缓冲区是否跟着改。Windows 用 NVDA+Firefox;WebKit 用 VoiceOver+Safari,因为个别引擎开始把有限的 CSS order 映进无障碍树,结果会分叉。
自变量:重排手段(flex 反向 / order / 绝对定位 / DOM 对调 / aria-owns)、引擎。
因变量:语音序列、检查器树序、与视觉阅读路径是否同构。
边界
规格和引擎正在争论 CSS 视觉顺序该不该影响无障碍树;部分浏览器对 flex order 的映射已经和「纯 DOM 序」不完全相同,结论不能当成所有引擎的恒等式。display: contents 会拆掉盒子但仍留子孙,遍历会跳过那一层。多栏、表格、SVG 阅读顺序各有自己的拍平规则,不是一篇从左到右的文章。地图、画布、无限画板没有稳定的文档序,缓冲区要么空,要么按作者塞进去的离轴节点走——那已经不是「CSS 对不上」,是根本没有结构可拷。
怎么落地
- 用户应当先遇到的内容,写在源结构的前面;不要用 flex 反向、
order、绝对定位去「视觉上提前」。 - 必须视觉重排时,同步改 DOM 或用
aria-owns把树挂对,让遍历与视觉阅读同构。 - 侧栏、工具条、浮动操作按钮先在结构里想清楚插入点,再写定位。
- 验证:把页面打印成灰度并从左到右标阅读路径,再遮屏用方向键走一遍,两条序列逐项对照。错位的每一处都是结构没跟着视觉走。换一个引擎再走一次,避免把某一家对
order的映射当成普遍事实。