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

如何实现Web应用上下键聚焦前后可聚焦元素?现有方案存缺陷

优化上下键焦点导航方案(解决不可聚焦元素问题)

核心问题分析

原方案直接聚焦相邻兄弟元素,未判断元素是否具备可聚焦能力,导致会选中<div>这类不可交互元素。解决关键是先识别可聚焦元素,再调整导航逻辑。

优化实现方案

1. 可聚焦元素判断函数

先实现工具函数,精准判断元素是否可聚焦:

function isFocusable(element) {
  // 元素不存在或隐藏直接排除
  if (!element || element.hidden) return false;

  // 原生可聚焦元素(需未禁用,a标签需带href)
  const nativeFocusableTags = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA', 'A', 'AREA', 'IFRAME', 'OBJECT', 'EMBED'];
  if (nativeFocusableTags.includes(element.tagName)) {
    if (element.tagName === 'A' && !element.href) return false;
    return !element.disabled;
  }

  // 自定义tabindex的元素(tabindex >=0 且未禁用)
  const tabIndex = parseInt(element.getAttribute('tabindex'), 10);
  return !isNaN(tabIndex) && tabIndex >= 0 && !element.disabled;
}

2. 优化后的焦点导航函数

下一个焦点元素导航

export function focusNext(focused, lockParent = false) {
  // 递归查找下一个可聚焦元素
  function findNext(current) {
    // 先在当前元素的兄弟节点中查找
    let nextSibling = current.nextElementSibling;
    while (nextSibling) {
      if (isFocusable(nextSibling)) return nextSibling;
      nextSibling = nextSibling.nextElementSibling;
    }

    // 兄弟节点无匹配,处理父元素逻辑
    if (!lockParent) {
      const parent = current.parentElement;
      if (!parent) return null;

      // 查找父元素的下一个兄弟节点,再递归找其内部第一个可聚焦元素
      const parentNext = parent.nextElementSibling;
      if (parentNext) {
        const firstFocusable = findFirstFocusable(parentNext);
        if (firstFocusable) return firstFocusable;
        // 父元素兄弟无匹配,继续向上查找
        return findNext(parent);
      } else {
        // 到达根节点末尾,循环回到页面第一个可聚焦元素
        return findFirstFocusable(document.body);
      }
    } else {
      // 锁定父元素,回到父元素内第一个可聚焦元素
      return findFirstFocusable(current.parentElement);
    }
  }

  // 查找元素内第一个可聚焦子元素(深度优先)
  function findFirstFocusable(element) {
    if (isFocusable(element)) return element;
    let child = element.firstElementChild;
    while (child) {
      const found = findFirstFocusable(child);
      if (found) return found;
      child = child.nextElementSibling;
    }
    return null;
  }

  const target = findNext(focused);
  target && target.focus();
}

上一个焦点元素导航

export function focusPrevious(focused, lockParent = false) {
  // 递归查找上一个可聚焦元素
  function findPrevious(current) {
    // 先在当前元素的兄弟节点中查找
    let prevSibling = current.previousElementSibling;
    while (prevSibling) {
      if (isFocusable(prevSibling)) return prevSibling;
      prevSibling = prevSibling.previousElementSibling;
    }

    // 兄弟节点无匹配,处理父元素逻辑
    if (!lockParent) {
      const parent = current.parentElement;
      if (!parent) return null;

      // 查找父元素的上一个兄弟节点,再递归找其内部最后一个可聚焦元素
      const parentPrev = parent.previousElementSibling;
      if (parentPrev) {
        const lastFocusable = findLastFocusable(parentPrev);
        if (lastFocusable) return lastFocusable;
        // 父元素兄弟无匹配,继续向上查找
        return findPrevious(parent);
      } else {
        // 到达根节点开头,循环回到页面最后一个可聚焦元素
        return findLastFocusable(document.body);
      }
    } else {
      // 锁定父元素,回到父元素内最后一个可聚焦元素
      return findLastFocusable(current.parentElement);
    }
  }

  // 查找元素内最后一个可聚焦子元素(深度优先)
  function findLastFocusable(element) {
    if (isFocusable(element)) return element;
    let child = element.lastElementChild;
    while (child) {
      const found = findLastFocusable(child);
      if (found) return found;
      child = child.previousElementSibling;
    }
    return null;
  }

  const target = findPrevious(focused);
  target && target.focus();
}

3. 事件绑定示例

在页面中监听上下键事件,触发导航逻辑:

document.addEventListener('keydown', (e) => {
  // 阻止默认滚动行为
  if (e.key === 'ArrowDown') {
    e.preventDefault();
    focusNext(document.activeElement);
  } else if (e.key === 'ArrowUp') {
    e.preventDefault();
    focusPrevious(document.activeElement);
  }
});

优化亮点

  • 精准过滤:通过isFocusable排除所有不可交互、隐藏的元素
  • 智能导航:循环查找兄弟元素,跨父层级自动检索,支持父元素锁定
  • 边界处理:到达页面首尾时自动循环,避免焦点丢失
  • 兼容性:覆盖原生可聚焦元素和自定义tabindex元素的场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:05:22