CSS 空间导航 第 1 级

W3C 工作草案 2019 年 11 月 26 日

此版本:
https://www.w3.org/TR/2019/WD-css-nav-1-20191126/
最新发布的版本:
https://www.w3.org/TR/css-nav-1/
编辑草案:
https://drafts.csswg.org/css-nav-1/
以前的版本:
问题跟踪:
规范中的内联问题
GitHub 问题
编辑:
(LG 电子)
Florian Rivoal (受邀专家)
建议对本规范进行编辑:
GitHub 编辑器

摘要

本规范定义了使用方向键导航焦点的通用模型,以及相关的 CSS、JavaScript 特性和事件。

CSS 是一种用于描述结构化文档(如 HTML 和 XML)在屏幕、纸张等上的渲染的语言。

本文档状态

本节描述了本文档在发布时的状态。其他文档可能会取代本文档。W3C 当前发布的文档列表及本技术报告的最新版本可在 W3C 技术报告索引 https://www.w3.org/TR/ 找到。

作为工作草案的发布不意味着 W3C 成员对其的认可。此文档为草案,可能会随时更新、替换或被其他文档废止。在引用时,本文档只能作为正在进行的工作引用。

GitHub 问题是讨论本规范的首选方式。 提交问题时,请在标题中注明“css-nav”, 最好像这样:“[css-nav] …评论摘要…”。 所有问题和评论都已 归档, 并且还有一个历史归档

本规范由 CSS 工作组 制定。

本文件由按照 W3C 专利政策 运作的小组编制。 W3C 维护一份 任何专利披露的公开列表, 该页面还包括披露专利的说明。任何有实际专利信息并认为包含 基本声明 的个人, 必须按照 W3C 专利政策第 6 条 披露该信息。

本文件受 2019 年 3 月 1 日 W3C 流程文档 管辖。

以下功能存在风险,可能会在 CR 期间被删除:

“存在风险”是 W3C 流程的一个术语,并不一定意味着该功能有被删除或延迟的危险。这意味着工作组认为该功能可能在可互操作性实施上遇到困难,标记为此类允许工作组在过渡到提议推荐阶段时,如果必要,可以删除该功能,而无需先发布一个不包含该功能的新的候选推荐文档。

本规范相当长。为了便于阅读和专注于特定区域,以下提供了一些复选框。选中它们可以隐藏部分规范。这仅作为阅读辅助工具,规范仍然是完整的文档。




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 定义的机制(通常是方向键,可能与修饰键如 ShiftControl 组合使用),用户可以请求用户代理向特定方向导航。这将会使焦点从当前位置移动到请求方向上的一个新的可聚焦项,或者在没有适当项时进行滚动。

更具体地说,用户代理将首先在当前 空间导航容器默认情况下为根元素、可滚动元素和 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: 700px;
    height: 700px;
    overflow-x: hidden;
    overflow-y: auto;
}

.box {
    width: 150px;
    height: 110px;
    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,用户代理也可能会因用户操作而触发空间导航。

无论实际选择了何种机制来触发空间导航,以下要求适用:

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()
  1. 返回元素的最近祖先,如果它是 空间导航容器,或者返回 文档,如果最近的 空间导航容器 是视口。

注意: 如果该元素本身是 空间导航容器getSpatialNavigationContainer() 也会返回其最近的 空间导航容器,而不是元素本身。

focusableAreas(option)
  1. 如果存在 option 且其值等于 all,则令 visibleOnlyfalse,否则为 true

  2. areas 为使用 visibleOnly 作为参数在元素中 找到的可聚焦区域 的结果。

  3. 返回 areas

以下代码显示了如何使用 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';
});

下图是该代码的执行结果。

关于 focusableAreas() 的图片
查找文档内所有可见的可聚焦区域。
spatialNavigationSearch(dir, options)
  1. directiondir 的值。

  2. container 为:

  3. areas 为:

  4. 返回在 container 中从该元素起 direction 方向内 选择最佳候选项 的结果。

注意: 当既未提供容器也未提供候选列表时,只会在最近的 空间导航容器 祖先的可见可聚焦区域内进行搜索。如果没有找到,则不会继续向上查找祖先链,结果将为 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
冒泡
可取消
事件的属性
Event.target
被聚焦的元素,或如果没有聚焦元素,则为body 元素(如果可用),否则为根元素
NavigationEvent.relatedTarget
将要获得焦点的可聚焦区域的 DOM 锚点
NavigationEvent.dir
用户请求的导航方向

在空间导航移动焦点之前,用户代理会分派 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
冒泡
可取消
事件的属性
Event.target
聚焦的元素,如果没有聚焦元素,则为body 元素(如果可用),否则为根元素
NavigationEvent.relatedTarget
搜索的空间导航容器
NavigationEvent.dir
用户请求的导航方向

用户代理会分派 navnotarget 事件,初始化 事件目标为具有焦点的元素,而relatedTarget事件目标空间导航容器

如果navigation-overrideeventTarget节点文档中对于来源活动文档被禁用,则此事件不会被分派。

这个示例展示了当按下 ArrowDown 键时的 UI 事件 §事件顺序,如下图所示。 为了简化描述,本示例假设用户代理使用箭头键来触发空间导航。
关于 navnotarget 的图片
滚动容器中没有任何候选项时移动焦点。
事件类型 事件目标 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 在目标元素获得焦点之后发送。

该示例的结果如下图所示:

navnotarget 的结果图片
滚动视口滚动容器无法滚动时,移动焦点的结果。

此示例使用的标记如下:

#container {
    width: 900px;
    height: 1400px;
}

#scrollContainer {
    width: 700px;
    height: 700px;
    overflow-x: hidden;
    overflow-y: auto;
}

.item {
    width: 150px;
    height: 110px;
    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 是一种 受政策控制的特性,它控制页面作者是否可以启用相关机制,以控制空间导航的行为或完全取消它。

§ 8.3 Navigation 中进一步详细定义的那样,如果 navigation-override 在文档中被禁用,则不会触发导航事件(参见 § 6 Navigation Events)。

注意:这样做是为了防止恶意 iframe 使用这些事件来劫持焦点。我们认识到在空间导航之前已经存在一些机制,恶意作者可以使用这些机制来干扰用户控制焦点去向的能力。尽管如此,尽力不增加这种攻击面还是有意义的,尽管这可能已经是一个很容易执行的攻击点,以至于这样做已经是徒劳。欢迎基于实现经验或减轻此类攻击的经验,进一步对此主题提供反馈。

8. 处理模型

§ 3 概述 章节大致介绍了空间导航如何工作,以帮助本规范的读者建立一般的心智模型。它使用了直观但不精确的术语,并为了可读性略过了许多细节。

本章节定义了相应的规范行为,目标是在必要的情况下提供尽可能多的细节,以完全定义这种行为。

8.1. 术语表

以下术语定义用于解释空间导航的处理模型。有关详细信息,请参见定义中的链接。

边界框 的定义如下:

内部区域 的定义如下:

注意:如果对象不在屏幕上,内部区域应为最近的可见祖先容器。

CSS 应该有一个术语来表示“考虑边角形状属性(如 border-radius)的边框盒”。<https://github.com/w3c/csswg-drafts/issues/2324>

搜索起点 是搜索下一个目标的起点。

空间导航起点 是由用户代理设置的搜索下一个目标的起点。最初未设置,可能是元素或点。

注意:例如,当用户点击文档内容时,用户代理可以将其设置为用户点击的位置,当焦点被移动(通过空间导航或其他方式)时未设置。

如果用户代理同时设置了 空间导航起点顺序聚焦导航起点,它们的设置必须一致。

8.2. 元素的分组

空间导航的处理模型是基于文档的布局和可聚焦元素的相对位置,用户代理必须优先在局部逻辑分组中查找元素,只有在未找到合适的元素时才查找分组外的可聚焦元素(详情参见 § 8.3 Navigation)。

此类分组称为 空间导航容器

默认情况下, 空间导航容器 由以下元素建立:

可以使用 空间导航容器spatial-navigation-contain 属性来创建额外的空间导航容器(见 § 9.1 使用 spatial-navigation-contain 属性创建额外的空间导航容器)。

空间导航的处理模型描述了空间导航的整体工作方式。

空间导航处理模型图
空间导航处理模型概述
此图并非规范性内容。它概述了在本节进一步定义的处理模型,假设 spatial-navigation-action 属性的初始值为 auto
要在 方向 中运行 空间导航步骤,请执行以下操作:
  1. searchOrigin设置搜索起点的结果。

    • 如果 searchOrigin 是一个节点,则设 eventTargetsearchOrigin

    • 否则(断言searchOrigin 是一个位置),设 eventTarget 为包含 searchOrigin节点

  2. 如果 eventTarget文档文档元素,则设 eventTarget主体元素,如果其不为 null,否则设为文档元素

  3. containereventTarget 最近的祖先,它是一个空间导航容器

  4. 循环:设 candidates 为在 container寻找可聚焦区域的结果,参数 visibleOnly 设置为 false ,如果在 containerspatial-navigation-action 属性的计算值为 focus,否则为 true,排除 eventTarget

  5. 如果 candidates 为空:

  6. 如果在 containerspatial-navigation-action 属性的计算值不是 focus,且 container 是一个滚动容器,且可以手动滚动,则按方向滚动该元素 container 并返回。
  7. 否则,
    1. eventTarget 上分发 navnotarget 事件,包括 方向container

      • 如果 container顶级浏览上下文的文档元素,则返回。用户代理可根据 方向 将焦点转移到其自身控件(如果有)。

      • 否则,如果 container嵌套浏览上下文的文档元素,则:

        1. searchOrigincontainer浏览上下文容器

        2. eventTargetsearchOrigin

        3. containereventTarget 最近的祖先,它是一个空间导航容器

        4. 返回到标为循环的步骤。

      • 否则,设 container 为其最接近的祖先,它本身是一个空间导航容器,并返回到标为 循环 的步骤。

  8. bestCandidate 为在 candidates 中从 eventTarget 开始,沿 方向 选择最佳候选项 的结果。

  9. eventTarget 上分发 navbeforefocus 事件,包括 方向bestCandidate

  10. 运行 聚焦步骤以聚焦 bestCandidate 并返回。

8.4. 焦点导航启发式算法

注意:以下算法受到 Chrome 的实现以及 旧 WICD 规范的启发。我们强烈鼓励实现者提供更好的方法或改进这些方法的建议,以帮助改进此规范,以最大程度地实现互操作性。特别是用户代理在寻找可聚焦区域时的差异可能会导致某些元素在某些用户代理中可聚焦,而在其他用户代理中不可聚焦,这对用户不利。

本节中定义的所有几何操作都适用于 CSS 布局的结果,包括所有图形变换,例如相对定位[CSS-TRANSFORMS-1]

设置 搜索起点,请执行以下步骤:

  1. searchOrigin当前顶级浏览上下文的聚焦区域DOM 锚点

  2. 如果空间导航起点不为 null 且在 searchOrigin 内,则返回该起点。

  3. 否则,返回 searchOrigin

如果 焦点目标的状态没有通过移动焦点而改变,按如下方式更新 搜索起点

  1. 如果 焦点目标变得实际禁用明确惰性或未被渲染,则设 searchOrigin焦点目标边界框

  2. 如果 焦点目标被移除,则设 searchOrigin焦点目标边界框,并保持其存在时的位置。

  3. 如果 焦点目标完全离屏,则设 searchOrigin焦点目标最近可见的空间导航容器的视口。

注意:用户代理应该更新搜索起点,例如当聚焦的元素被鼠标滚动移出或从视口中消失时。

要在包含元素 C寻找可聚焦区域,可选参数 visibleOnly 默认为 true,请执行以下步骤:

  1. focusables 为所有可聚焦区域DOM 锚点C 的后代元素的集合。对于具有多个框片段,每个框片段分别考虑。

  2. 用户代理应从 focusables移除DOM 锚点tabindex 属性设置为负值的项目。

    注意:这在一定程度上是为了反映那些带负值 tabindex 的元素的排除,避免它们出现在顺序焦点导航顺序中,正如 tabindex 中定义的。

  3. 如果 visibleOnlyfalse,返回 focusables

    注意:focusables 可能为空

  4. visiblesfocusables 中其边界框至少部分位于 C内部区域的子集。

    除了那些位于滚动条不可见部分的元素,空间导航不会自动排除因某些其他元素覆盖而无法被点击的元素。为了避免用户实际聚焦并激活这些元素时破坏应用逻辑中的假设,并避免通过聚焦不可见或表面上无法到达的元素而混淆用户,作者应使用与使元素无法通过顺序导航到达的最佳实践相同的方法使这些元素在空间导航中也不可到达,例如使用 tab-index="-1"inert 属性。
  5. 返回 visibles

    注意:visibles 可能为空

要在一个集合中的 candidates 内、按方向 dirsearchOrigin 为起点选择最佳候选项,请执行以下步骤:

  1. 如果 candidates空的,返回 null

  2. 如果 candidates 只有一个项目,返回该项目

  3. insiderscandidates 的子集

    • 其边界框完全与 内部区域searchOrigin 重叠

    • 其边界框部分与 内部区域searchOrigin 重叠,具体为

      • 如果 dirdown,上边缘在 searchOrigin边界框的上边缘之下

      • 如果 dirup,下边缘在 searchOrigin边界框的下边缘之上

      • 如果 dirleft,右边缘在 searchOrigin边界框的右边缘之左

      • 如果 dirright,左边缘在 searchOrigin边界框的左边缘之右

      注意:有关元素与搜索起点重叠的更多详细条件会影响焦点移动的顺序。焦点移动的顺序与用户体验有关,因此取决于用户代理定义的机制。

    注意:此子集设置是为了避免沿相反方向移动。

    • 如果 insiders 非空,

      1. closest subsetinsiders 的子集,其 边界框

        • 如果 dirdown,则上边缘距离 内部区域searchOrigin 上边缘最近

        • 如果 dirup,则下边缘距离 内部区域searchOrigin 下边缘最近

        • 如果 dirleft,则右边缘距离 内部区域searchOrigin 右边缘最近

        • 如果 dirright,则左边缘距离 内部区域searchOrigin 左边缘最近

      2. 如果 closest subset 只有一个项目,返回该项目,否则返回 closest subset 中的第一个项目,除非其 边界框与另一个项目重叠,且该项目在 CSS 的 绘制顺序中更高。在这种情况下,返回该项目,除非它也被另一个更高的项目重叠,递归处理。

    • 否则

      1. candidates 为其项目符合以下条件之一的子集:

        • 该项目不与 searchOrigin 重叠,且其 边界框

          • 如果 dirdown,则上边缘在 searchOrigin边界框的下边缘之下

          • 如果 dirup,则下边缘在 searchOrigin边界框的上边缘之上

          • 如果 dirleft,则右边缘在 searchOrigin边界框的左边缘之左

          • 如果 dirright,则左边缘在 searchOrigin边界框的右边缘之右

      2. 对于 candidates 中的每个 candidate找到最短距离searchOrigin

      3. 返回 candidates 集合中距离最小的项目。如果有多个具有相同距离,返回文档顺序中的第一个项目,除非其 边界框与另一个项目的 边界框重叠,且该项目在 CSS 绘制顺序中更高。在这种情况下,返回该项目,除非它也被另一个更高的项目重叠,递归处理。

要在方向 dir 上查找 referencecandidate 之间的最短距离,找到 P1P2 点,分别位于 referencecandidate 的边界框内,使 distance 最小化,定义如下:
distance = euclidean + displacement - alignment - sqrt(Overlap)
        

每个术语的含义如下:

euclidean
P1P2 之间的欧几里得距离
displacement
dir 方向上 referencecandidate 之间的位移度量,定义为
displacement = (P1P2 在与 dir 正交的轴上的绝对距离 + orthogonalBias) * orthogonalWeight
        
orthogonalBias
  • 如果 dirleftright,则为 reference 的轴对齐边界框的高度 / 2

  • 如果 dirupdown,则为 reference 的轴对齐边界框的宽度 / 2

orthogonalWeight
  • 如果 dirleftright,值为 30

  • 如果 dirupdown,值为 2

alignment
dir 方向上 referencecandidate 之间的对齐度量,定义为:
alignment = alignBias * alignWeight
        
alignBias
  • 如果 dirleftright,则为 projectedOverlap / reference 的轴对齐边界框的高度

  • 如果 dirupdown,则为 projectedOverlap / reference 的轴对齐边界框的宽度

projectedOverlap
  • 如果 dirleftright,则为 referencecandidate 的水平投影在垂直轴上的重叠长度

  • 如果 dirupdown,则为 referencecandidate 的垂直投影在水平轴上的重叠长度

projectedOverlap
alignWeight
5
sqrt(Overlap)
referencecandidate 之间的重叠区域面积的平方根,如果它们没有重叠则为 0。

注意: 该通用公式是从几种合理的替代方案中挑选出来的,基于其在选择最佳候选项时最符合直觉的次数(参见用户体验测试用例)。类似地,alignWeightorthogonalWeight 的值也是通过相同的测试用例实验确定的。结果公式可能有点复杂,但似乎能给出良好的结果。欢迎提供改进或简化的建议。

9. 通过声明方式控制空间导航

9.1. 创建额外的空间导航容器:spatial-navigation-contain属性

名称: spatial-navigation-contain
值: auto | contain
初始值: auto
适用于: 所有元素
继承:
百分比: 不适用
计算值: 按指定
规范顺序: 按语法
动画类型: 离散的
auto
如果该元素是一个滚动容器,则它会建立一个空间导航容器,否则不会。
contain
该元素会建立一个空间导航容器

注意:此外,根据§ 8.2 元素的分组浏览上下文的视口(不限于顶级浏览上下文)也会建立一个空间导航容器

以下示例展示了一个简化的电视节目表或日历。 它有一个代表电视节目或日历条目的元素网格及其周围的一些UI按钮。

在这种情况下,网格相当稀疏,因此如果用户试图从“Foo”向下移动,焦点将移动到“下一周”,因为它在向下方向上更接近。同样地,从“Bar”向下移动,焦点将移动到“上一周”。

0-6 Foo
6-9 Bar
9-12 Bat
12-18
18-21 Woo
21-24 Baz
<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>

然而,作者可能希望提供不同的导航体验,优先考虑在网格内移动的情况,因为表中的元素在语义上是相关的。

在样式表中添加table { spatial-navigation-contain: contain; }会实现这种行为。

之后,焦点会从“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的可滚动容器。在该容器内,有一个元素在滚动视口中不可见。按下下箭头键直接将焦点移动到该元素,而无需手动滚动。
从“Box 2”移动焦点到“Box 3”而无需手动滚动
<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, 1fr);
    height: 300px;
    width: 200px;
    overflow-y: scroll;
    spatial-navigation-action: focus;
}
.item {
    height: 100px;
    width: 100px;
    margin: 50px 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”移动焦点到候选元素之一

设包含“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手动滚动

[CSSOM-VIEW-1] 应该定义如何在没有明确位置的情况下进行方向滚动。在此之前,我们将自行定义。<https://github.com/w3c/csswg-drafts/issues/2323>

为了dir方向上方向性地滚动元素e

  1. d为用户代理定义的距离。

  2. xe在x轴上的当前滚动位置。

  3. ye在y轴上的当前滚动位置。

  4. 使用滚动元素算法从[CSSOM-VIEW-1]e进行滚动到:

    • 如果dirup,则(x, y - d)

    • 如果dirdown,则(x, y + d)

    • 如果dirleft,则(x - d, y)

    • 如果dirright,则(x + d, y)

附录 B. 隐私与安全注意事项

规范的贡献者们认为与本规范相关的所有已知潜在安全风险都已得到充分解决。以下提供了更多详细信息。

TAG 开发了一个自我评审问卷,以帮助编辑和工作组评估其规范引入的风险。以下提供了答案。

本规范是否处理个人可识别信息?
否。
本规范是否涉及高价值数据?
否。
本规范是否引入了会跨浏览会话持久存在的状态?
否。
本规范是否向网络公开持久的跨域状态?
否。
本规范是否公开了其他当前无法访问的数据?
基本上没有。

唯一确定的例外情况是:如果作者在跨域 iframe 中聚焦时使用 `window.navigate`,如果他们根本没有收到事件,这意味着要么 iframe 中有可滚动或可聚焦的内容,因为唯一能接收到事件的情况是搜索完全失败时会向上遍历。

这种信息非常有限,看起来不会引发实际的安全风险,但就编辑们所知,这确实是作者无法通过其他方式获得的信息。

本规范是否启用新的脚本执行/加载机制?
否。
本规范是否允许一个源访问用户的位置?
否。
本规范是否允许一个源访问用户设备上的传感器?
否。
本规范是否允许一个源访问用户本地计算环境的某些方面?
否。
本规范是否允许一个源访问其他设备?
否。
本规范是否允许一个源对用户代理的原生 UI 进行某种程度的控制?
不允许对用户代理 UI 的外观进行控制。只允许对用户代理的空间导航行为进行一些控制,这可能被视为其用户界面的一部分。这样做是为了让作者根据页面定制空间导航的行为。为了防止恶意作者干涉用户对焦点和文档导航的控制,默认情况下在跨域 iframe 中禁用这种覆盖机制。详见 § 7 导航覆盖策略控制的特性
本规范是否向网络暴露临时标识符?
否。
本规范是否区分一方和第三方上下文的行为?
否。
本规范在用户代理的“隐身”模式下应如何工作?
无差异。
本规范是否将数据持久保存到用户的本地设备?
否。
本规范是否有“安全注意事项”和“隐私注意事项”章节?
是,这正是您现在阅读的章节。
本规范是否允许降低默认的安全特性?
不允许降低任何无关的安全机制。

它 **确实** 允许作者选择加入允许在他们信任的跨域 iframe 中覆盖空间导航的默认行为所需的事件,使用 [feature-policy]。详见 § 7 导航覆盖策略控制的特性

致谢

本规范的编辑想感谢以下个人(按字母顺序排列)对规范的反馈和贡献:

更改记录

本节是非规范性的。

2019 年 4 月 23 日首次公开工作草案以来,进行了以下更改:

符合性

文档约定

符合性要求是通过描述性声明和 RFC 2119 术语的组合来表达的。本规范的规范性部分中的关键字“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 的描述进行解释。然而,为了便于阅读,本规范中这些词不会全部大写。[RFC2119]

本规范的所有文本都是规范性的,除非明确标记为非规范性部分、示例和注释。

本规范中的示例以“例如”引入,或者与规范性文本分开,用 class="example" 标记,如下所示:

这是一个信息性示例。

信息性注释以“注意”一词开头,并用 class="note" 与规范性文本分开,如下所示:

注意,这是一个信息性注释。

劝告性部分是规范性章节,旨在引起特别关注,并用 <strong class="advisement"> 与其他规范性文本分开,如下所示:用户代理必须提供可访问的替代方案。

符合性类别

本规范的符合性定义适用于三个符合性类别:

样式表
CSS 样式表
渲染器
解释样式表语义并渲染使用它们的文档的 用户代理(UA)
作者工具
用于编写样式表的 用户代理(UA)

如果样式表中使用本模块定义的语法的所有语句都符合通用 CSS 语法和本模块中定义的各个特性的语法,则该样式表符合本规范。

如果渲染器不仅按适当的规范解释样式表,还支持本规范定义的所有特性,正确解析并相应地渲染文档,则该渲染器符合本规范。然而,由于设备的限制,用户代理无法正确渲染文档,这并不意味着用户代理不符合规范。(例如,用户代理不需要在单色显示器上渲染颜色。)

如果作者工具编写的样式表在语法上符合通用 CSS 语法和本模块中各个特性的语法,并且满足本模块中描述的样式表的所有其他符合性要求,则该作者工具符合本规范。

CSS 负责任实现要求

以下章节定义了若干符合性要求,以负责任地实现 CSS,促进现在和未来的互操作性。

部分实现

为了让作者可以利用向前兼容的解析规则来分配后备值,CSS 渲染器必须将所有不支持的 at-rules、属性、属性值、关键字和其他语法结构视为无效(并根据需要 忽略)。特别地,用户代理不得在单个多值属性声明中有选择地忽略不支持的属性值而保留支持的值:如果任何值被认为无效(因为不支持的值必须如此),CSS 规定整个声明必须被忽略。

不稳定和专有特性的实现

为了避免与未来稳定的 CSS 特性发生冲突,CSS 工作组建议遵循最佳实践来实现不稳定特性和专有扩展

候选推荐阶段特性的实现

一旦规范进入候选推荐(CR)阶段,实现者应发布符合规范的任何 CR 级特性的非前缀实现,并应避免公开该特性的前缀变体。

为了建立和维护 CSS 在各个实现之间的互操作性,CSS 工作组请求非实验性 CSS 渲染器在发布任何 CSS 特性的非前缀实现之前,向 W3C 提交一个实现报告(如果需要,还包括用于该实现报告的测试用例)。提交给 W3C 的测试用例会由 CSS 工作组进行审查和修正。

关于提交测试用例和实现报告的更多信息,请访问 CSS 工作组网站 http://www.w3.org/Style/CSS/Test/。如有疑问,请发送邮件至 public-css-testsuite@w3.org 邮件列表。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性参考文献

[CSS-BREAK-4]
Rossen Atanassov; Elika Etemad. CSS Fragmentation Module Level 4. 2018年12月18日. WD. URL: https://www.w3.org/TR/css-break-4/
[CSS-CASCADE-4]
Elika Etemad; Tab Atkins Jr.. CSS Cascading and Inheritance Level 4. 2018年8月28日. CR. URL: https://www.w3.org/TR/css-cascade-4/
[CSS-DISPLAY-3]
Tab Atkins Jr.; Elika Etemad. CSS Display Module Level 3. 2019年7月11日. CR. URL: https://www.w3.org/TR/css-display-3/
[CSS-OVERFLOW-3]
David Baron; Elika Etemad; Florian Rivoal. CSS Overflow Module Level 3. 2018年7月31日. WD. URL: https://www.w3.org/TR/css-overflow-3/
[CSS-POSITION-3]
Rossen Atanassov; Arron Eicholz. CSS Positioned Layout Module Level 3. 2016年5月17日. WD. URL: https://www.w3.org/TR/css-position-3/
[CSS-SCROLL-SNAP-1]
Matt Rakow; 等. CSS Scroll Snap Module Level 1. 2019年3月19日. CR. URL: https://www.w3.org/TR/css-scroll-snap-1/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. 2019年1月31日. WD. URL: https://www.w3.org/TR/css-values-4/
[CSS2]
Bert Bos; 等. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 2011年6月7日. REC. URL: https://www.w3.org/TR/CSS2/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. 2016年3月17日. WD. URL: https://www.w3.org/TR/cssom-view-1/
[DOM]
Anne van Kesteren. DOM标准. 现行标准. URL: https://dom.spec.whatwg.org/
[FEATURE-POLICY]
Ian Clelland. 功能策略. 2019年4月16日. WD. URL: https://www.w3.org/TR/feature-policy-1/
[HTML]
Anne van Kesteren; 等. HTML标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. 基础设施标准. 现行标准. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 在RFC中使用的关键字来指示需求级别. 1997年3月. 最佳现行实践. URL: https://tools.ietf.org/html/rfc2119
[UIEVENTS]
Gary Kacmarcik; Travis Leithead; Doug Schepers. UI事件. 2019年5月30日. WD. URL: https://www.w3.org/TR/uievents/
[WebIDL]
Boris Zbarsky. Web IDL. 2016年12月15日. ED. URL: https://heycam.github.io/webidl/

说明性参考文献

[CSS-TRANSFORMS-1]
Simon Fraser; 等. CSS Transforms Module Level 1. 2019年2月14日. CR. URL: https://www.w3.org/TR/css-transforms-1/
[EXTENSIBLE]
The Extensible Web Manifesto. 2013年6月10日. URL: https://extensiblewebmanifesto.org/

属性索引

名称 初始值 适用于 继承 百分比 动画类型 规范顺序 计算值
spatial-navigation-action auto | focus | scroll auto 滚动容器 不适用 离散 按语法 按指定
spatial-navigation-contain auto | contain auto 所有元素 不适用 离散 按语法 按指定
spatial-navigation-function normal | grid normal 空间导航容器 不适用 离散 按语法 按指定

IDL 索引

enum SpatialNavigationDirection {
    "up",
    "down",
    "left",
    "right",
};

partial interface Window {
    void navigate(SpatialNavigationDirection dir);
};

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);
};

[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;
};

问题索引

该 API 的名称正在讨论中 <https://github.com/w3c/csswg-drafts/issues/3387>
CSS 应该有一个术语来表示“考虑边角形状属性(如 border-radius)的边框盒”。 <https://github.com/w3c/csswg-drafts/issues/2324>
此类术语应在 [CSSOM-VIEW-1][CSS-OVERFLOW-3][CSS-SCROLL-SNAP-1] 中定义。 <https://github.com/w3c/csswg-drafts/issues/2322>
[CSSOM-VIEW-1] 可能应定义如何在没有明确位置的情况下按给定方向执行滚动操作。在此之前,我们自行定义。 <https://github.com/w3c/csswg-drafts/issues/2323>