Lzh on GitHub

焦点范围

管理组件边界内的焦点,支持捕获和循环焦点导航。

焦点范围提供了对组件边界内键盘焦点管理的增强控制。它可以在其容器内捕获焦点,并可选择循环焦点导航,使其成为模态界面和需要管理焦点状态的其他交互式组件的理想选择。

API 参考

Prop默认值类型说明
as'div'AsTag | Component此组件应渲染为的元素或组件。可以通过 asChild 覆盖。
asChildboolean将默认渲染的元素更改为作为子元素传递的元素,合并它们的 props 和行为。阅读我们的 组合 指南了解更多详情。
loopfalseboolean当为 true 时,从最后一个可 tab 项按 Tab 键将聚焦第一个可 tab 项,从第一个可 tab 项按 Shift+Tab 键将聚焦最后一个可 tab 项。
trappedfalseboolean当为 true 时,焦点无法通过键盘、指针或编程方式的聚焦逃离焦点范围。

EmitPayload

EmitPayload类型说明
mountAutoFocus[event: Event]在挂载时自动聚焦时调用的事件处理程序。可以阻止默认行为。
unmountAutoFocus[event: Event]在卸载时自动聚焦时调用的事件处理程序。可以阻止默认行为。

示例

基本用法与焦点捕获

<template>
  <FocusScope :trapped="true">
    <div>
      <button>操作 1</button>
      <button>操作 2</button>
      <button>关闭</button>
    </div>
  </FocusScope>
</template>

带焦点循环

启用捕获和循环,实现完整的焦点管理:

<template>
  <FocusScope :trapped="true" :loop="true">
    <div>
      <button v-for="item in items" :key="item.id">
        {{ item.label }}
      </button>
    </div>
  </FocusScope>
</template>

处理焦点事件

<script setup>
function handleMountFocus(event) {
  // Prevent default auto-focus behavior if needed
  event.preventDefault()
}
</script>

<template>
  <FocusScope
    @mount-auto-focus="handleMountFocus"
    @unmount-auto-focus="handleUnmountFocus"
  >
    <div>
    </div>
  </FocusScope>
</template>
当使用捕获模式时,请确保在作用域内始终至少有一个可聚焦元素,以防止焦点被捕获在无法访问的状态中。
tabindex="-1" 的作用:

tabindex 属性用于指示元素是否可以被 Tab 键导航(即聚焦)以及其在 Tab 顺序中的位置。
  1. tabindex="0":
  • 表示元素是 可聚焦的 (focusable)
  • 它会按照元素在 DOM 中的 自然顺序 被 Tab 键导航到。
  • 例如:<button><input><a> 标签如果没指定 tabindex,通常会默认行为像 tabindex="0"
  1. tabindex=">0" (正整数,例如 tabindex="1", tabindex="2"):
  • 表示元素是 可聚焦的
  • 它会按照 tabindex 值从低到高的顺序进行 Tab 导航。
  • 不推荐频繁使用正整数的 tabindex,因为它会打乱自然的 DOM 顺序,增加可访问性(accessibility)的复杂性。
  1. tabindex="-1":
  • 表示元素 可以通过 JavaScript 代码被聚焦 (例如使用 element.focus())。
  • 但是,它 不能被 Tab 键导航 到。

为什么 FocusScope 中使用 tabindex="-1"

在您的 FocusScope 组件中,Primitive 元素设置 tabindex="-1" 是非常合理的,主要有以下几个原因:
  1. 作为焦点的“守门员”或容器:FocusScope 的主要目的是 管理和限制焦点在特定区域内tabindex="-1" 让这个 <Primitive> 元素本身 不参与常规的 Tab 导航流,但它仍然可以 接收代码触发的焦点。这意味着,当 FocusScope 需要将焦点“陷阱”到它的内部时(例如,当用户 Tab 到区域外时,将焦点重新导回区域内),它可以精确地将焦点设置到这个容器元素上,或者利用它作为一个起点来寻找内部可聚焦的元素。
  2. 避免不必要的 Tab 停止点:FocusScope 通常包裹着许多其他可聚焦的元素(例如按钮、输入框等)。如果 FocusScope 的根元素本身可以被 Tab 导航到,那么当使用者按 Tab 键时,会多一个不必要的停止点,这会影响用户体验和可访问性。让它 tabindex="-1" 意味着 Tab 键会直接跳过这个容器,去到它内部真正可交互的元素。
  3. 辅助焦点陷阱逻辑: 在您的代码中,有这样的逻辑:
    if (getActiveElement() === previouslyFocusedElement)
        focus(container)
    

    这段代码在特定情况下(例如当没有其他元素可聚焦时),会将焦点设置到 container(即您的 Primitive 元素)上。由于 tabindex="-1" 允许元素被代码聚焦,这使得 FocusScope 能够在没有其他合适焦点的情况下,将焦点保留在作用域内,从而实现 焦点陷阱(trapped focus)的功能。

总结:

所以,tabindex="-1" 被 Tab 键跳过是它的预期行为,也是 FocusScope 组件能够实现其核心功能(例如 焦点陷阱循环 Tab 导航)的关键之一。它允许组件在需要时程序化地控制焦点,同时不干扰使用者预期的 Tab 导航路径。