useAccessibilityFocus焦点管理Hook

SwiftLion +0/-0 0 0 正常 2025-12-24T07:01:19 React Hooks · 无障碍访问 · 焦点管理

在现代React应用中,无障碍访问(Accessibility)已成为不可或缺的开发要求。本文将深入探讨如何通过自定义Hook实现焦点管理,确保用户能够通过键盘导航顺畅地操作应用。

核心需求分析

当用户使用键盘导航时,焦点应当在可交互元素间正确流转。特别是在动态内容、模态框、下拉菜单等场景中,焦点管理显得尤为重要。传统的focus()方法虽然有效,但在复杂组件结构中容易出现焦点丢失或错误聚焦的问题。

自定义Hook实现

import { useEffect, useRef } from 'react';

const useAccessibilityFocus = (shouldFocus = true) => {
  const focusRef = useRef(null);
  
  useEffect(() => {
    if (shouldFocus && focusRef.current) {
      // 确保元素已渲染并可获取焦点
      const element = focusRef.current;
      if (element && typeof element.focus === 'function') {
        // 检查元素是否可见且可交互
        if (element.offsetParent !== null || 
            element.offsetWidth > 0 || 
            element.offsetHeight > 0) {
          element.focus({ preventScroll: true });
        }
      }
    }
  }, [shouldFocus]);
  
  return focusRef;
};

高级应用示例

针对模态框场景,我们可进一步封装:

const useModalFocus = (isOpen) => {
  const modalRef = useRef(null);
  const initialFocusRef = useRef(null);
  
  useEffect(() => {
    if (isOpen) {
      // 记录当前焦点
      initialFocusRef.current = document.activeElement;
      
      // 聚焦到模态框
      if (modalRef.current) {
        modalRef.current.focus();
      }
      
      // 键盘导航控制
      const handleKeyDown = (e) => {
        if (e.key === 'Escape') {
          // ESC键关闭模态框
          return;
        }
        
        // Tab键循环聚焦
        if (e.key === 'Tab') {
          const focusableElements = modalRef.current.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
          );
          
          const firstElement = focusableElements[0];
          const lastElement = focusableElements[focusableElements.length - 1];
          
          if (e.shiftKey && document.activeElement === firstElement) {
            lastElement.focus();
            e.preventDefault();
          } else if (!e.shiftKey && document.activeElement === lastElement) {
            firstElement.focus();
            e.preventDefault();
          }
        }
      };
      
      document.addEventListener('keydown', handleKeyDown);
      return () => document.removeEventListener('keydown', handleKeyDown);
    }
  }, [isOpen]);
  
  // 关闭时恢复焦点
  useEffect(() => {
    return () => {
      if (initialFocusRef.current && 
          typeof initialFocusRef.current.focus === 'function') {
        initialFocusRef.current.focus();
      }
    };
  }, []);
  
  return modalRef;
};

使用方式

const MyComponent = () => {
  const [isOpen, setIsOpen] = useState(false);
  const modalRef = useModalFocus(isOpen);
  
  return (
    <>
      <button onClick={() => setIsOpen(true)}>打开模态框</button>
      {isOpen && (
        <div ref={modalRef} tabIndex={-1}>
          <h2>模态框标题</h2>
          <input placeholder="输入内容" />
          <button>确认</button>
          <button onClick={() => setIsOpen(false)}>关闭</button>
        </div>
      )}
    </>
  );
};

性能优化建议

  1. 使用useRef避免不必要的重新渲染
  2. 合理使用防抖和节流处理高频事件
  3. 在组件卸载时清理事件监听器防止内存泄漏
  4. 通过条件渲染减少不必要的焦点操作

通过以上实现,我们不仅解决了焦点管理的核心问题,还提供了良好的可复用性和扩展性,让无障碍访问变得更加简单可靠。

推广
广告位招租

讨论

0/2000