You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js中如何实现Tab键仅在设置tabindex的元素间循环聚焦

Vue表单Tab键焦点闭环实现方案

核心逻辑

原生tabindex属性只能控制元素的聚焦顺序,无法限制焦点遍历范围,遍历完配置过的元素后必然会落到页面其他可聚焦元素、浏览器地址栏等原生控件上,要实现6个元素的循环聚焦,需要做两层处理:

  • 监听表单容器内的Tab按键事件,在首尾焦点节点手动接管跳转逻辑,实现正向/反向循环
  • 表单激活期间临时将表单外所有默认可聚焦元素设为不可聚焦,彻底阻断焦点外溢路径,组件销毁时恢复原有配置,避免全局污染

代码实现

以Vue3 <script setup> 语法为例:

<template>
  <form ref="formRef" @keydown.tab="handleTabCycle">
    <input type="text" tabindex="1" data-form-focus placeholder="字段1" />
    <input type="text" tabindex="2" data-form-focus placeholder="字段2" />
    <input type="text" tabindex="3" data-form-focus placeholder="字段3" />
    <input type="text" tabindex="4" data-form-focus placeholder="字段4" />
    <input type="text" tabindex="5" data-form-focus placeholder="字段5" />
    <button type="submit" tabindex="6" data-form-focus>提交</button>
  </form>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const formRef = ref(null)
const focusableElements = ref([])
const outsideElementsCache = []

const handleTabCycle = (e) => {
  const currentActive = document.activeElement
  const currentIndex = focusableElements.value.findIndex(el => el === currentActive)

  // 正向Tab到最后一个元素时,跳回第一个
  if (!e.shiftKey && currentIndex === focusableElements.value.length - 1) {
    e.preventDefault()
    focusableElements.value[0].focus()
    return
  }

  // 反向Shift+Tab到第一个元素时,跳到最后一个
  if (e.shiftKey && currentIndex === 0) {
    e.preventDefault()
    focusableElements.value.at(-1).focus()
    return
  }
}

// 收集表单内可聚焦元素,按tabindex排序
const collectFocusableElements = () => {
  const elements = Array.from(
    formRef.value.querySelectorAll('[data-form-focus]:not(:disabled)')
  )
  focusableElements.value = elements.sort((a, b) => {
    return Number(a.getAttribute('tabindex')) - Number(b.getAttribute('tabindex'))
  })
}

onMounted(() => {
  collectFocusableElements()
  // 默认聚焦第一个输入框
  focusableElements.value[0]?.focus()

  // 临时禁用表单外所有可聚焦元素的焦点能力
  const outsideFocusable = document.querySelectorAll(
    'a[href], button, input:not([data-form-focus]), select, textarea, [tabindex]:not([data-form-focus])'
  )
  outsideFocusable.forEach(el => {
    outsideElementsCache.push({
      el,
      originTabindex: el.getAttribute('tabindex')
    })
    el.setAttribute('tabindex', '-1')
  })
})

onUnmounted(() => {
  // 恢复外部元素原有tabindex配置
  outsideElementsCache.forEach(({ el, originTabindex }) => {
    if (originTabindex !== null) {
      el.setAttribute('tabindex', originTabindex)
    } else {
      el.removeAttribute('tabindex')
    }
  })
})
</script>

适配说明

  • 如果使用Vue2,把onMounted/onUnmounted替换为对应生命周期钩子,ref取值逻辑调整为this.$refs.formRef即可
  • 如果表单存在动态显隐、动态禁用的字段,在字段状态变更后重新调用collectFocusableElements方法更新可聚焦元素列表,避免顺序错乱
  • 不要把keydown事件绑定到window全局,绑定在form容器上即可,性能更好也不会干扰页面其他区域的快捷键逻辑

内容的提问来源于stack exchange,提问作者newCode

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 06:57:24