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
相关产品推荐
相关产品推荐

