useAccessibilityModal模态框Hook

紫色风铃姬 +0/-0 0 0 正常 2025-12-24T07:01:19 React Hooks · 无障碍访问

在现代React应用中,无障碍访问的模态框组件是提升用户体验的重要组成部分。本文将分享一个完整的useAccessibilityModal自定义Hook,它能够帮助开发者轻松实现符合WCAG标准的模态框交互。

核心功能

该Hook主要处理以下无障碍相关逻辑:

  1. 键盘焦点管理(Tab切换限制)
  2. ESC键关闭模态框
  3. 背景滚动锁定
  4. ARIA属性设置

完整实现代码

import { useState, useEffect, useRef } from 'react';

export const useAccessibilityModal = (isOpen, onClose) => {
  const [isMounted, setIsMounted] = useState(false);
  const modalRef = useRef(null);
  const focusTrapRef = useRef(null);

  // 初始化和清理
  useEffect(() => {
    if (isOpen) {
      setIsMounted(true);
      document.body.style.overflow = 'hidden';
      
      // 焦点管理
      const handleFocusTrap = (e) => {
        if (!modalRef.current) return;
        
        const focusableElements = modalRef.current.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
        
        if (focusableElements.length === 0) return;
        
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        if (e.key === 'Tab') {
          if (e.shiftKey && document.activeElement === firstElement) {
            e.preventDefault();
            lastElement.focus();
          } else if (!e.shiftKey && document.activeElement === lastElement) {
            e.preventDefault();
            firstElement.focus();
          }
        }
      };

      document.addEventListener('keydown', handleFocusTrap);
      
      // 确保模态框获得焦点
      setTimeout(() => {
        if (modalRef.current) {
          modalRef.current.focus();
        }
      }, 0);
      
      return () => {
        document.removeEventListener('keydown', handleFocusTrap);
        document.body.style.overflow = 'unset';
      };
    } else {
      setIsMounted(false);
    }
  }, [isOpen]);

  // ESC键关闭
  useEffect(() => {
    const handleEsc = (e) => {
      if (e.key === 'Escape' && isOpen) {
        onClose();
      }
    };

    document.addEventListener('keydown', handleEsc);
    return () => document.removeEventListener('keydown', handleEsc);
  }, [isOpen, onClose]);

  // 点击背景关闭
  const handleClickOutside = (e) => {
    if (modalRef.current && !modalRef.current.contains(e.target)) {
      onClose();
    }
  };

  return {
    modalRef,
    isMounted,
    handleClickOutside
  };
};

使用示例

function ModalComponent() {
  const [isOpen, setIsOpen] = useState(false);
  const { modalRef, isMounted, handleClickOutside } = useAccessibilityModal(isOpen, () => setIsOpen(false));

  if (!isMounted) return null;

  return (
    <div 
      ref={modalRef}
      className="modal-overlay"
      onClick={handleClickOutside}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
    >
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <h2 id="modal-title">模态框标题</h2>
        <p>模态框内容</p>
        <button onClick={() => setIsOpen(false)}>关闭</button>
      </div>
    </div>
  );
}

关键优化点

  1. 使用useRef避免重复渲染
  2. 组件卸载时正确清理DOM事件
  3. 焦点陷阱实现Tab键循环导航
  4. 保持滚动锁定的正确性

通过这个Hook,开发者可以快速构建符合无障碍标准的模态框组件,提升应用的可访问性和用户体验。

推广
广告位招租

讨论

0/2000