本规范相当长。为了便于阅读和专注于特定区域,以下提供了一些复选框。选中它们可以隐藏部分规范。这仅作为阅读辅助工具,规范仍然是完整的文档。
1. 介绍
本节非规范性。
历史上,大多数浏览器没有提供功能让用户方向性地移动焦点。一些浏览器,如电视浏览器,出于必要让用户使用方向键移动焦点,因为典型的电视遥控器上没有其他输入机制。
其他一些浏览器支持不同的组合键来控制空间导航,例如按住 Shift
键与方向键一起使用。
这种在页面上方向性移动的能力被称为 空间导航。
空间导航
对于使用网格布局或其他以非线性为主的布局构建的网页很有用。下图显示了一个以网格布局排列的相册。如果用户按 Tab
键在图片间移动焦点,他们需要多次按键才能到达目标图片元素。

此外,空间导航
可以根据可聚焦元素的位置在它们之间移动,使焦点移动对用户来说是可预测的。有时页面上的元素并不是独立于它们的源顺序排列的。因此,与 不同,使用 Tab
键的顺序导航使焦点导航变得不可预测。
虽然方向键自然适合控制空间导航,但之前没有规范描述这种方式如何工作,或如何控制它。本规范引入了空间导航的处理模型,以及允许作者控制和覆盖空间导航工作方式的 API。
注意: 本规范的某些方面,例如 JavaScript 事件和 API,也可以扩展到顺序导航,以确保键盘导航具有一致且定义明确的模型。
注意: 作为一般原则,键盘导航,尤其是空间导航,应该在没有 JavaScript 的情况下也可以使用和控制,因此声明式解决方案是首选。由于空间导航依赖于布局,CSS 通常是定义空间导航相关控制的正确机制。然而,本着可扩展 Web 宣言 [EXTENSIBLE] 的精神,我们认为提供合适的 JavaScript 原语让作者进行实验和探索问题空间是很重要的。基于通过这些 JavaScript 使用获得的反馈和经验,可能会添加更多的声明式特性。
注意: 一些特性被标记为 风险。本规范的编辑认为这些特性代表了用户或作者体验的重要部分。同时,本规范的核心功能可以在没有实现这些特性的情况下实现,因此似乎可能实现者会选择降低它们的优先级,以减少首次实现的范围。虽然希望这些特性也能被实现,但它们被标记为风险,以承认它们可能不会在初期被实现。
2. 模块交互
本文档依赖于 Infra 标准 [infra]。
关键字 "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"NOT RECOMMENDED"、"MAY" 和 "OPTIONAL" 的解释方式如 RFC 2119 中所述。[RFC2119]
3. 概述
本节非规范性。
使用 UA 定义的机制(通常是方向键,可能与修饰键如 Shift
或 Control
组合使用),用户可以请求用户代理向特定方向导航。这将会使焦点从当前位置移动到请求方向上的一个新的可聚焦项,或者在没有适当项时进行滚动。
更具体地说,用户代理将首先在当前 空间导航容器(默认情况下为根元素、可滚动元素和 iframe ,但其他元素也可以通过使用 spatial-navigation-contain 属性将其设为 空间导航容器)中搜索可见且可聚焦的元素。
如果找到任何元素,它将选择该方向上最合适的元素并移动焦点到那里。
如果未找到任何元素,它将滚动请求方向上的 空间导航容器,而不是移动焦点。这样做可能会显示出可聚焦的元素,这些元素在下一次请求同一方向的空间导航时将成为合格目标。
如果 空间导航容器 不能滚动,无论是因为它不是可滚动的元素还是因为它在该方向上已经滚动到最大,用户代理将选择祖先链中的下一个 ,并递归地重复上述过程,直到找到焦点元素或可滚动的元素,或者到达根元素。
注意: 由于这种处理模型,通过顺序导航和空间导航可达的元素几乎相同。在可滚动元素视口之外的元素只有在被滚动到视图中后才能通过空间导航到达。因此,默认情况下无法滚动到视图中的元素。
preventDefault()
),并在需要时提供替代动作,例如使用
focus()
方法将焦点移动到作者选择的其他元素。
为了帮助作者编写这些替代动作,作为根据 Extensible Web 原则公开底层平台原语的一部分,本规范还定义了 JavaScript API,公开底层模型的关键构造。
有关 JavaScript API 的详细信息,请参阅 § 5 JavaScript API;有关各种事件的详细信息,请参阅 § 6.2 导航事件类型;有关 CSS 属性的详细信息,请参阅 § 9 通过声明方式控制空间导航。


在图 2 的左侧,"Box 2" 处于焦点。按下 ArrowDown
键将焦点移动到 "Box 3",因为 "Box 3" 位于 scrollport 的可见区域中,所以没有进行滚动。




在图 3 的第一个图中,在 "Box 3" 下面,scrollport
中没有任何可见的元素。因此,按下 ArrowDown
键的效果是向下滚动,如第二张图所示。再按 ArrowDown
键,将 "Box 4" 滚动到 scrollport,当再一次按下 ArrowDown
键时,焦点将移动到
"Box 4"。
本示例使用的标记如下:
#scroller { width : 700 px ; height : 700 px ; overflow-x : hidden; overflow-y : auto; } .box { width : 150 px ; height : 110 px ; background-color : blue; } .box:focus { background-color : red; }
< div id = "scroller" > < div class = "box" tabindex = "0" > Box 1</ div > < div class = "box" tabindex = "0" > Box 2</ div > < div class = "box" tabindex = "0" > Box 3</ div > < div class = "box" tabindex = "0" > Box 4</ div > </ div >
4. 触发空间导航
当用户在某个方向上触发空间导航时,用户代理必须在该方向上运行 空间导航步骤。
本规范未定义用户代理应向用户提供何种 UI 机制来触发空间导航。这特意留给用户代理决定。
虽然用户代理可以实现本规范定义的处理模型和 API,但本规范建议用户代理应为用户提供一种直接触发空间导航的方式,而无需使用 API。
注意:相反,作者应假定即使未调用任何 API,用户代理也可能会因用户操作而触发空间导航。
无论实际选择了何种机制来触发空间导航,以下要求适用:
-
如果用户用于触发空间导航的机制通常会触发
UIEvent
,则必须在运行 空间导航步骤 之前触发该事件,并且如果该事件的 取消标志 被设置,则不得运行这些步骤。游戏设备可能基于按下方向键触发空间导航,这将导致触发 keydown 事件,键设置为ArrowDown
、ArrowLeft
、ArrowRight
或ArrowUp
之一,随后(如果未取消)运行 空间导航步骤,包括触发相关的NavigationEvent
事件。在桌面计算机上的用户代理使用键盘的方向键触发空间导航时,也会遵循相同的顺序。
-
如果用户用于触发空间导航的机制在某些上下文中还会执行其他操作,则用户代理应优先执行这些其他操作,而不是空间导航,不得同时触发两者。
在使用方向键(不带修饰键)触发空间导航的用户代理中,如果这些方向键还用于移动可编辑元素上的文本插入光标,则方向键默认应用于移动光标。仅当焦点元素不可编辑或可编辑但光标无法在请求方向上进一步移动时,才会由方向键触发空间导航。滚动是一个例外:由于空间导航本身处理滚动(除了移动焦点之外),用户代理不应提供相同的机制来同时触发空间导航和与空间导航分开的滚动行为。但是,用户代理可以提供一种方式,让用户在不同模式之间切换,或基于不同的 UI 机制提供两者。
5. JavaScript API
5.1. 以编程方式触发导航
navigate()
方法允许作者以编程方式触发空间导航,就像用户手动触发一样(例如在浏览器中按下方向键来触发空间导航)。
注意: 由于它触发与手动导航相同的处理模型,因此应预期相同的结果:会触发相同的事件链,并且相同的元素将被滚动或聚焦。
注意: 作者可以使用此方法基于与用户代理分配的不同 UI 机制来触发空间导航,例如映射到不同的键,或从可点击的屏幕方向键触发空间导航,或响应其他非 UI 的事件。它也可用于当作者想要中断导航以执行一些异步操作(例如在无限滚动器中加载更多内容)然后恢复到取消时的导航位置。
注意: 此 API 也非常适用于测试目的,因为很难触发不依赖于供应商特定 UI 约定的空间导航。
enum {
SpatialNavigationDirection ,
"up" ,
"down" ,
"left" , };
"right" partial interface Window {void navigate (SpatialNavigationDirection ); };
dir
navigate(dir)
方法时,用户代理必须运行以下步骤:
-
如果方向 dir 是
"up"
、"down"
、"left"
或"right"
,则在 dir 方向上运行 空间导航步骤。
此 API 的名称正在讨论中 <https://github.com/w3c/csswg-drafts/issues/3387>
5.2. 低级 API
这些 API 旨在成为紧密遵循处理模型的低级构造。因此,它们应易于供作者使用来扩展或覆盖空间导航的工作方式。
enum {
FocusableAreaSearchMode ,
"visible" };
"all" dictionary {
FocusableAreasOption FocusableAreaSearchMode ; };
mode dictionary {
SpatialNavigationSearchOptions sequence <Node >?;
candidates Node ?; };
container partial interface Element {Node getSpatialNavigationContainer ();sequence <Node >focusableAreas (optional FocusableAreasOption );
option Node ?(
spatialNavigationSearch SpatialNavigationDirection ,
dir optional SpatialNavigationSearchOptions ); };
options
注意: 表达方向的方式使我们可以在必要时扩展到超过4个方向的导航,可以添加更多方向关键字或数值角度。
注意: focusableAreas()
和 getSpatialNavigationContainer()
方法是风险。
当调用这些方法时,用户代理必须运行以下步骤:
getSpatialNavigationContainer()
-
注意: 如果该元素本身是 空间导航容器,
getSpatialNavigationContainer()
也会返回其最近的 ,而不是元素本身。
focusableAreas()
获取当前页面中所有可见的可聚焦元素。如果该方法找到了 空间导航容器,它会递归地查找其中的可聚焦区域。由于此方法中 mode
属性的值为 visible
,因此结果中排除了那些不在 滚动视口 内的可聚焦元素。
< body >
< button ></ button >
< div style = "width:300px; height:200px; overflow-x: scroll;" >
< button style = "left:25px;" ></ button >
< button style = "left:150px;" ></ button >
< button style = "left:350px;" ></ button >
</ div >
</ body >
const focusableAreas = document. body. focusableAreas({ mode: 'visible' });
focusableAreas && focusableAreas. forEach( focusable => {
focusable. style. outline = '5px solid red' ;
});
下图是该代码的执行结果。

注意: 当既未提供容器也未提供候选列表时,只会在最近的 空间导航容器
祖先的可见可聚焦区域内进行搜索。如果没有找到,则不会继续向上查找祖先链,结果将为 null
。
6. 导航事件
6.1. 接口 NavigationEvent
NavigationEvent
接口提供与空间导航相关的特定上下文信息。
要创建 NavigationEvent
接口的实例,使用 NavigationEvent
构造函数,并传递一个可选的 NavigationEventInit
字典。
[Exposed =Window ]interface :
NavigationEvent UIEvent {(
constructor DOMString ,
type optional NavigationEventInit );
eventInitDict readonly attribute SpatialNavigationDirection ;
dir readonly attribute EventTarget ?; };
relatedTarget dictionary :
NavigationEventInit UIEventInit {SpatialNavigationDirection ;
dir EventTarget ?=
relatedTarget null ; };
6.2. 导航事件类型
本节及其子章节不是规范性的。
导航事件类型概述如下。有关完整的规范细节,请参阅第 8 节 处理模型。
6.2.1. navbeforefocus
当有有效的选择最佳候选项结果时,会发生此事件。
类型 | navbeforefocus
|
---|---|
接口 | NavigationEvent
|
冒泡 | 是 |
可取消 | 是 |
事件的属性 |
|
在空间导航移动焦点之前,用户代理会分派 navbeforefocus 事件。事件目标是具有焦点的元素,而relatedTarget
是即将获得焦点的元素。
如果 navigation-override 在 节点文档中对于来源的活动文档被禁用,则此事件不会被分派。
ArrowRight
键时的UI 事件
§事件顺序。为了简化描述,假设此示例中的用户代理通过箭头键触发空间导航。
事件类型 | KeyboardEvent .key
|
备注 | |
---|---|---|---|
1 | keydown | ArrowRight |
必须是可以激活空间导航的键,例如箭头键,否则不会激活空间导航。 |
2 | navbeforefocus | 如果空间导航候选项不是 null ,则发送此事件,否则不会生成。 |
|
3 | focusin | 在目标元素获得焦点之前发送。 | |
4 | focus | 在目标元素获得焦点后发送。 |
document. addEventListener( 'navbeforefocus' , e => {
e. preventDefault();
let nextTarget = e. relatedTarget;
if ( isSpatialNavigationContainer( nextTarget)) {
const areas = nextTarget. focusableAreas();
if ( areas. length > 0 ) {
nextTarget = nextTarget. spatialNavigationSearch( e. dir, { candidates: areas });
}
}
nextTarget. focus();
});
function isSpatialNavigationContainer( element) {
return ( ! element. parentElement) ||
( element. nodeName === 'IFRAME' ) ||
( isScrollContainer( element)) ||
( isCSSSpatNavContain( element));
}
6.2.2. navnotarget
当空间导航在当前
内未能找到任何候选项时, 在向上树结构搜索最近的祖先 的候选项之前,或者在 可滚动且无法进一步滚动的情况下,会触发此事件。类型 | navnotarget
|
---|---|
接口 | NavigationEvent
|
冒泡 | 是 |
可取消 | 是 |
事件的属性 |
|
用户代理会分派 navnotarget 事件,初始化 事件目标为具有焦点的元素,而relatedTarget
为事件目标的空间导航容器。
如果navigation-override在eventTarget的节点文档中对于来源的活动文档被禁用,则此事件不会被分派。
ArrowDown
键时的
UI 事件 §事件顺序,如下图所示。
为了简化描述,本示例假设用户代理使用箭头键来触发空间导航。

事件类型 | 事件目标 | relatedTarget |
备注 | |
---|---|---|---|---|
1 | keydown | #box2 |
N/A | 必须是可以激活空间导航的按键,例如箭头键,否则不会触发空间导航。 |
2 | navnotarget | #box2 |
#scrollContainer |
如果 #scrollContainer 中没有候选项且无法滚动,则会发送此事件,否则不会生成。 |
3 | navbeforefocus | #box2 |
#box3 |
如果 #container 中的候选项不为 null ,则会发送此事件,否则不会触发。 |
4 | focusin | #box3 |
#box2 |
在目标元素获得焦点之前发送。 |
5 | focus | #box3 |
#box2 |
在目标元素获得焦点之后发送。 |
该示例的结果如下图所示:

此示例使用的标记如下:
#container { width : 900 px ; height : 1400 px ; } #scrollContainer { width : 700 px ; height : 700 px ; overflow-x : hidden; overflow-y : auto; } .item { width : 150 px ; height : 110 px ; background-color : blue; } .item:focus { background-color : red; }
< div id = "container" > < div id = "scrollContainer" > < div id = "box1" class = "item" tabindex = "0" > Box 1</ div > < div id = "box2" class = "item" tabindex = "0" > Box 2</ div > </ div > < div id = "box3" class = "item" tabindex = "0" > Box 3</ div > </ div >
不过,焦点仍可以通过顺序导航、鼠标交互或通过调用 focus()
方法以编程方式移出容器。
scrollContainer. addEventListener( 'navnotarget' , e => {
let nextTarget = null ;
const verticalDir = [ 'up' , 'down' ];
const candidates = e. relatedTarget. focusableAreas({ 'mode' : 'all' });
// 仅在导航方向为 y 轴时阻止默认行为
if ( verticalDir. includes( e. dir) && ( candidates. length > 0 )) {
e. preventDefault();
if ( e. dir === 'down' ) {
nextTarget = candidates[ 0 ];
} else if ( e. dir === 'up' ) {
nextTarget = candidates[ candidates. length- 1 ];
}
nextTarget. focus();
}
});
7. navigation-override 受政策控制的特性
navigation-override 是一种 受政策控制的特性,它控制页面作者是否可以启用相关机制,以控制空间导航的行为或完全取消它。
-
该特性的名称为 "
navigation-override
" -
默认允许列表 对于 navigation-override 是 "
self
"
如 § 8.3 Navigation 中进一步详细定义的那样,如果 navigation-override 在文档中被禁用,则不会触发导航事件(参见 § 6 Navigation Events)。
注意:这样做是为了防止恶意 iframe 使用这些事件来劫持焦点。我们认识到在空间导航之前已经存在一些机制,恶意作者可以使用这些机制来干扰用户控制焦点去向的能力。尽管如此,尽力不增加这种攻击面还是有意义的,尽管这可能已经是一个很容易执行的攻击点,以至于这样做已经是徒劳。欢迎基于实现经验或减轻此类攻击的经验,进一步对此主题提供反馈。
8. 处理模型
本章节定义了相应的规范行为,目标是在必要的情况下提供尽可能多的细节,以完全定义这种行为。
8.1. 术语表
以下术语定义用于解释空间导航的处理模型。有关详细信息,请参见定义中的链接。
边界框 的定义如下:
内部区域 的定义如下:
注意:如果对象不在屏幕上,内部区域应为最近的可见祖先容器。
CSS 应该有一个术语来表示“考虑边角形状属性(如 border-radius)的边框盒”。<https://github.com/w3c/csswg-drafts/issues/2324>
搜索起点 是搜索下一个目标的起点。
空间导航起点 是由用户代理设置的搜索下一个目标的起点。最初未设置,可能是元素或点。
注意:例如,当用户点击文档内容时,用户代理可以将其设置为用户点击的位置,当焦点被移动(通过空间导航或其他方式)时未设置。
如果用户代理同时设置了 空间导航起点 和 顺序聚焦导航起点,它们的设置必须一致。
8.2. 元素的分组
空间导航的处理模型是基于文档的布局和可聚焦元素的相对位置,用户代理必须优先在局部逻辑分组中查找元素,只有在未找到合适的元素时才查找分组外的可聚焦元素(详情参见 § 8.3 Navigation)。
此类分组称为 空间导航容器。
默认情况下, 空间导航容器 由以下元素建立:
8.3. 导航
空间导航的处理模型描述了空间导航的整体工作方式。
此图并非规范性内容。它概述了在本节进一步定义的处理模型,假设 spatial-navigation-action 属性的初始值为 auto。
-
设 searchOrigin 为设置搜索起点的结果。
-
如果 eventTarget 是文档或文档元素,则设 eventTarget 为主体元素,如果其不为
null
,否则设为文档元素。 -
- 如果 eventTarget 是滚动容器,且在 eventTarget 上的 spatial-navigation-action 属性的计算值为 scroll,并且 eventTarget 可以手动滚动,则按方向滚动该元素 eventTarget 并返回。
-
否则,如果 eventTarget 是 滚动容器 或 文档
-
设 candidates 为在 eventTarget 中寻找可聚焦区域的结果,参数 visibleOnly 设置为
false
,如果在 eventTarget 上 spatial-navigation-action 属性的计算值为 focus,否则为true
。 -
- 如果 eventTarget 上的 spatial-navigation-action 属性的计算值不是 focus 且 eventTarget 可以手动滚动,则按方向滚动该元素 eventTarget 并返回。
-
否则,如果 candidates 至少包含 1 项:
-
设 bestCandidate 为在 candidates 中从 searchOrigin 开始,沿 方向 选择最佳候选项 的结果。
-
在 eventTarget 上分发 navbeforefocus 事件,包括 方向 和 bestCandidate。
-
运行 聚焦步骤以聚焦 bestCandidate 并返回。
-
- 否则,回退到下一步。
-
- 否则,回退到下一步。
-
设 container 为 eventTarget 最近的祖先,它是一个空间导航容器。
-
循环:设 candidates 为在 container 中寻找可聚焦区域的结果,参数 visibleOnly 设置为
false
,如果在 container 上 spatial-navigation-action 属性的计算值为 focus,否则为true
,排除 eventTarget。 -
如果 candidates 为空:
- 如果在 container 上 spatial-navigation-action 属性的计算值不是 focus,且 container 是一个滚动容器,且可以手动滚动,则按方向滚动该元素 container 并返回。
- 否则,
-
在 eventTarget 上分发 navnotarget 事件,包括 方向 和 container。
-
-
设 bestCandidate 为在 candidates 中从 eventTarget 开始,沿 方向 选择最佳候选项 的结果。
-
在 eventTarget 上分发 navbeforefocus 事件,包括 方向 和 bestCandidate。
-
运行 聚焦步骤以聚焦 bestCandidate 并返回。
8.4. 焦点导航启发式算法
注意:以下算法受到 Chrome 的实现以及 旧 WICD 规范的启发。我们强烈鼓励实现者提供更好的方法或改进这些方法的建议,以帮助改进此规范,以最大程度地实现互操作性。特别是用户代理在寻找可聚焦区域时的差异可能会导致某些元素在某些用户代理中可聚焦,而在其他用户代理中不可聚焦,这对用户不利。
本节中定义的所有几何操作都适用于 CSS 布局的结果,包括所有图形变换,例如相对定位或[CSS-TRANSFORMS-1]。
要 设置 搜索起点,请执行以下步骤:
-
设 searchOrigin 为当前顶级浏览上下文的聚焦区域的 DOM 锚点。
-
如果空间导航起点不为
null
且在 searchOrigin 内,则返回该起点。 -
否则,返回 searchOrigin。
如果 焦点目标的状态没有通过移动焦点而改变,按如下方式更新 搜索起点:
-
如果 焦点目标完全离屏,则设 searchOrigin 为 焦点目标最近可见的空间导航容器的视口。
注意:用户代理应该更新搜索起点,例如当聚焦的元素被鼠标滚动移出或从视口中消失时。
要在包含元素 C 内寻找可聚焦区域,可选参数 visibleOnly 默认为 true
,请执行以下步骤:
-
设 focusables 为所有可聚焦区域的DOM 锚点是 C 的后代元素的集合。对于具有多个框片段的框,每个框片段分别考虑。
-
用户代理应从 focusables 中移除其 DOM 锚点的
tabindex
属性设置为负值的项目。注意:这在一定程度上是为了反映那些带负值 tabindex 的元素的排除,避免它们出现在顺序焦点导航顺序中,正如 tabindex 中定义的。
-
如果 visibleOnly 为
false
,返回 focusables。注意:focusables 可能为空
-
设 visibles 为 focusables 中其边界框至少部分位于 C 的内部区域的子集。
除了那些位于滚动条不可见部分的元素,空间导航不会自动排除因某些其他元素覆盖而无法被点击的元素。为了避免用户实际聚焦并激活这些元素时破坏应用逻辑中的假设,并避免通过聚焦不可见或表面上无法到达的元素而混淆用户,作者应使用与使元素无法通过顺序导航到达的最佳实践相同的方法使这些元素在空间导航中也不可到达,例如使用tab-index="-1"
或inert
属性。 -
返回 visibles。
注意:visibles 可能为空
要在一个集合中的 candidates 内、按方向 dir 以 searchOrigin 为起点选择最佳候选项,请执行以下步骤:
-
如果 candidates 是空的,返回
null
-
如果 candidates 只有一个项目,返回该项目
-
设 insiders 为 candidates 的子集
-
其边界框完全与 内部区域的 searchOrigin 重叠
-
其边界框部分与 内部区域的 searchOrigin 重叠,具体为
注意:有关元素与搜索起点重叠的更多详细条件会影响焦点移动的顺序。焦点移动的顺序与用户体验有关,因此取决于用户代理定义的机制。
注意:此子集设置是为了避免沿相反方向移动。
-
-
-
如果 insiders 非空,
-
否则
-
distance = euclidean + displacement - alignment - sqrt(Overlap)
每个术语的含义如下:
- euclidean
- P1 和 P2 之间的欧几里得距离
- displacement
-
在 dir 方向上 reference 和 candidate 之间的位移度量,定义为
displacement = (P1 和 P2 在与 dir 正交的轴上的绝对距离 + orthogonalBias) * orthogonalWeight
- orthogonalBias:
- orthogonalWeight:
- alignment
-
在 dir 方向上 reference 和 candidate 之间的对齐度量,定义为:
alignment = alignBias * alignWeight
- alignBias:
- projectedOverlap:
-
projectedOverlap - alignWeight:
- 5
- sqrt(Overlap)
- reference 和 candidate 之间的重叠区域面积的平方根,如果它们没有重叠则为 0。
注意: 该通用公式是从几种合理的替代方案中挑选出来的,基于其在选择最佳候选项时最符合直觉的次数(参见用户体验测试用例)。类似地,alignWeight 和 orthogonalWeight 的值也是通过相同的测试用例实验确定的。结果公式可能有点复杂,但似乎能给出良好的结果。欢迎提供改进或简化的建议。
9. 通过声明方式控制空间导航
9.1. 创建额外的空间导航容器:spatial-navigation-contain属性
名称: | spatial-navigation-contain |
---|---|
值: | auto | contain |
初始值: | auto |
适用于: | 所有元素 |
继承: | 否 |
百分比: | 不适用 |
计算值: | 按指定 |
规范顺序: | 按语法 |
动画类型: | 离散的 |
注意:此外,根据§ 8.2 元素的分组,浏览上下文的视口(不限于顶级浏览上下文)也会建立一个空间导航容器。
在这种情况下,网格相当稀疏,因此如果用户试图从“Foo”向下移动,焦点将移动到“下一周”,因为它在向下方向上更接近。同样地,从“Bar”向下移动,焦点将移动到“上一周”。
< div >
< button > 上一周</ button >
< table >
< tr >< td >< th > 一< th > 二< th > 三< th > 四< th > 五< th > 六< th > 日
< tr >< td > 0-6< td >< td >< td >< td >< td >< td >< td >< a href = "#" > Foo</ a >
< tr >< td > 6-9< td >< a href = "#" > Bar</ a >< td >< td >< td >< td >< td >< td >
< tr >< td > 9-12< td >< td >< a href = "#" > Bat</ a >< td >< td >< td >< td >< td >
< tr >< td > 12-18< td >< td >< td >< td >< td >< td >< td >
< tr >< td > 18-21< td >< td >< td >< td >< td >< td >< td >< a href = "#" > Woo</ a >
< tr >< td > 21-24< td >< td >< td >< td >< td >< td >< a href = "#" > Baz</ a >< td >
</ table >
< button > 下一周</ button >
</ div >
然而,作者可能希望提供不同的导航体验,优先考虑在网格内移动的情况,因为表中的元素在语义上是相关的。
在样式表中添加
会实现这种行为。
之后,焦点会从“Foo”向下移动到“Woo”而不是“下一周”,从“Bar”移动到“Bat”而不是“上一周”。
仍然可以将焦点移出表格。例如,焦点会从“Foo”向右移动到“下一周”,因为在网格中没有东西在右侧。
注意: spatial-navigation-contain 属性处于风险中。
9.2. 控制与滚动的交互:spatial-navigation-action 属性
名称: | spatial-navigation-action |
---|---|
值: | auto | focus | scroll |
初始值: | auto |
适用于: | 滚动容器 |
继承: | 否 |
百分比: | 不适用 |
计算值: | 按指定 |
规范顺序: | 按语法 |
动画类型: | 离散的 |
当焦点位于滚动容器内部并且用户触发空间导航时,有点模棱两可他们是否在请求将焦点移动到该方向,或是要求文档在该方向上滚动。默认情况下,系统会自动决定,但此属性允许作者在聚焦或滚动之间进行选择。
具体行为定义在§ 8.3 导航中,但下面提供了每个值的效果的高层次描述。
当触发空间导航时,行为取决于当前聚焦元素上的spatial-navigation-action的值(如果该元素是滚动容器),或者是其最近的滚动容器祖先(如果不是)。
- auto
- 如果在请求的方向上滚动容器内有可见的可聚焦元素,则最近的那个会获得焦点。否则,滚动容器会在请求的方向上滚动。
- focus
-
焦点移动到滚动容器内最近的可聚焦元素,不论它是否可见。如果没有可聚焦元素,滚动容器不会滚动,搜索会继续沿祖先链进行。
注意: 滚动容器可能因聚焦到之前不可见的元素而产生滚动的副作用,但它不会进行方向性的滚动。
注意: 如果将focus值赋予spatial-navigation-action,当在视口内的方向上没有任何可见候选元素时,即使容器可以继续滚动,也会触发navnotarget事件。
- scroll
-
如果当前聚焦的元素本身不是一个滚动容器,该值对其祖先滚动容器的效果与auto相同。
如果当前聚焦元素是一个滚动容器,它会在请求的方向上滚动而不改变聚焦的元素,无论是否存在可聚焦的子元素。
注意: 这意味着空间导航可以用于将焦点移动到滚动容器并对其进行滚动,但不能将焦点移动到其子元素。然而,如果通过其他方式(例如按下Tab键或使用
focus()
方法)将焦点移至子元素,则可以使用空间导航将焦点移至其他可聚焦的子元素。注意: scroll值处于风险中。
注意: 本规范的早期版本未提供声明方式选择进入focus值所定义的行为,而是提供了在滚动之前触发的可取消事件,以便作者自行实现该行为。然而,与滚动相关的可取消事件可能会导致性能问题,因此该事件被移除,取而代之的是引入spatial-navigation-action属性。
spatial-navigation-action: focus
的可滚动容器。在该容器内,有一个元素在滚动视口中不可见。按下下箭头键直接将焦点移动到该元素,而无需手动滚动。

< div class = 'scroller' >
< button class = 'item' > Box 1</ button >
< button class = 'item' > Box 2</ button >
< button class = 'item' > Box 3</ button >
</ div >
.scroller {
display : grid;
grid-template-columns : repeat ( 1 , 1 fr );
height : 300 px ;
width : 200 px ;
overflow-y : scroll;
spatial-navigation-action : focus;
}
.item {
height : 100 px ;
width : 100 px ;
margin : 50 px auto;
background-color : blue;
}
:focus {
background-color : red;
}
9.3. 选择导航算法:spatial-navigation-function 属性
名称: | spatial-navigation-function |
---|---|
值: | normal | grid |
初始值: | normal |
适用于: | 空间导航容器 |
继承: | 否 |
百分比: | 不适用 |
计算值: | 按指定 |
规范顺序: | 按语法 |
动画类型: | 离散的 |
在§ 8 处理模型中指定的默认空间导航算法可能需要根据布局类型进行微调。此属性允许作者指示哪种导航算法适合空间导航行为。
其值定义如下:
- normal
-
使用用户代理定义的默认焦点导航算法移动焦点。
通常情况下,焦点移动到通过找到最短距离计算出的最近的元素。
- grid
-
将焦点移动到与导航方向最对齐的元素。
-
如果在导航方向上有多个对齐的候选元素,则选择沿与导航方向对应的轴距离最近的元素。在多个元素具有相同距离的情况下,选择对齐度最小的元素。
-
如果在给定方向上没有对齐的候选元素,则选择沿与导航方向对应的轴距离最近的元素。在多个元素具有相同距离的情况下,选择沿与导航方向正交的轴距离最小的元素。
-
注意: 这些值会与用户的偏好进行协商,以便提供看起来自然的空间导航行为。
spatial-navigation-function
的值不同而移动不同。

设包含“A”、“B”和“C”的元素是一个空间导航容器。
当用户按下下箭头键时,如果给该元素的spatial-navigation-function
赋值为normal
,焦点将移动到“B”。否则,如果给该元素指定了grid
值,焦点将移动到“C”。
附录 A. 滚动扩展
本节提出了一些 CSS 扩展,这些扩展应当整合到上游规范中,但在整合之前会暂时托管于此。
这种术语应包含在 [CSSOM-VIEW-1]、[CSS-OVERFLOW-3]、[CSS-SCROLL-SNAP-1] 中。<https://github.com/w3c/csswg-drafts/issues/2322>
当满足以下条件时,元素e可以在给定方向d上手动滚动:
-
如果d是
up
或down
,则overflow-y属性的计算值不为 ,并且 -
如果d是
left
或right
,则overflow-x属性的计算值不为 ,并且 -
e在方向d上没有达到滚动边界,并且
-
e在方向d上未与最后一个强制对齐点对齐
[CSSOM-VIEW-1] 应该定义如何在没有明确位置的情况下进行方向滚动。在此之前,我们将自行定义。<https://github.com/w3c/csswg-drafts/issues/2323>
为了dir方向上方向性地滚动元素e:
-
令d为用户代理定义的距离。
-
令x为e在x轴上的当前滚动位置。
-
令y为e在y轴上的当前滚动位置。
-
使用滚动元素算法从[CSSOM-VIEW-1]对e进行滚动到:
-
如果dir是
up
,则(x, y - d) -
如果dir是
down
,则(x, y + d) -
如果dir是
left
,则(x - d, y) -
如果dir是
right
,则(x + d, y)
-
附录 B. 隐私与安全注意事项
规范的贡献者们认为与本规范相关的所有已知潜在安全风险都已得到充分解决。以下提供了更多详细信息。
TAG 开发了一个自我评审问卷,以帮助编辑和工作组评估其规范引入的风险。以下提供了答案。
- 本规范是否处理个人可识别信息?
- 否。
- 本规范是否涉及高价值数据?
- 否。
- 本规范是否引入了会跨浏览会话持久存在的状态?
- 否。
- 本规范是否向网络公开持久的跨域状态?
- 否。
- 本规范是否公开了其他当前无法访问的数据?
- 基本上没有。
唯一确定的例外情况是:如果作者在跨域 iframe 中聚焦时使用 `window.navigate`,如果他们根本没有收到事件,这意味着要么 iframe 中有可滚动或可聚焦的内容,因为唯一能接收到事件的情况是搜索完全失败时会向上遍历。
这种信息非常有限,看起来不会引发实际的安全风险,但就编辑们所知,这确实是作者无法通过其他方式获得的信息。
- 本规范是否启用新的脚本执行/加载机制?
- 否。
- 本规范是否允许一个源访问用户的位置?
- 否。
- 本规范是否允许一个源访问用户设备上的传感器?
- 否。
- 本规范是否允许一个源访问用户本地计算环境的某些方面?
- 否。
- 本规范是否允许一个源访问其他设备?
- 否。
- 本规范是否允许一个源对用户代理的原生 UI 进行某种程度的控制?
- 不允许对用户代理 UI 的外观进行控制。只允许对用户代理的空间导航行为进行一些控制,这可能被视为其用户界面的一部分。这样做是为了让作者根据页面定制空间导航的行为。为了防止恶意作者干涉用户对焦点和文档导航的控制,默认情况下在跨域 iframe 中禁用这种覆盖机制。详见 § 7 导航覆盖策略控制的特性。
- 本规范是否向网络暴露临时标识符?
- 否。
- 本规范是否区分一方和第三方上下文的行为?
- 否。
- 本规范在用户代理的“隐身”模式下应如何工作?
- 无差异。
- 本规范是否将数据持久保存到用户的本地设备?
- 否。
- 本规范是否有“安全注意事项”和“隐私注意事项”章节?
- 是,这正是您现在阅读的章节。
- 本规范是否允许降低默认的安全特性?
- 不允许降低任何无关的安全机制。
它 **确实** 允许作者选择加入允许在他们信任的跨域 iframe 中覆盖空间导航的默认行为所需的事件,使用 [feature-policy]。详见 § 7 导航覆盖策略控制的特性。
致谢
本规范的编辑想感谢以下个人(按字母顺序排列)对规范的反馈和贡献:
-
Alice Boxhall
-
Brian Kardell
-
Elika Etemad
-
Eric Seong
-
Hugo Holgersson
-
Hyojin Song
-
Jeonghee Ahn
-
Junho Seo
-
Rob Dodson
-
Seungcheon Baek
更改记录
本节是非规范性的。
自 2019 年 4 月 23 日首次公开工作草案以来,进行了以下更改:
-
将
getSpatialNavigationContainer()
的结果更改为返回最近的 空间导航容器 祖先 -
新增了 更新搜索起点 的步骤
-
更改了
spatialNavigationSearch()
的 IDL,分离了 dir 属性和SpatialNavigationSearchOptions
-
将完全重叠的非 空间导航容器 的焦点元素作为候选项,以解决不可达问题