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

Blazor无JS实现焦点控制下拉菜单:修复失焦不关闭问题

修复Dropdown组件失焦无法关闭菜单的问题

问题核心

需要实现的Dropdown组件需满足三个交互要求:

  • 点击下拉按钮显示菜单
  • 点击菜单外部或菜单项时关闭菜单
  • 点击菜单项跳转至指定URL
    当前组件存在失焦时无法关闭菜单的问题。

原因分析

常见的失焦关闭失效原因包括:

  1. 未监听全局点击事件,无法识别菜单外部的点击操作
  2. 按钮的blur事件处理不当,点击菜单时因焦点切换误触发或不触发关闭逻辑
  3. 未正确处理组件内外的焦点范围判断

修复方案与完整代码

1. DropDown 核心组件

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

export const DropDown = ({ children, buttonText, setIsOpen, isOpen }) => {
  const dropdownRef = useRef(null);

  // 监听全局点击,点击外部关闭菜单
  useEffect(() => {
    const handleClickOutside = (e) => {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
        setIsOpen(false);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [setIsOpen]);

  // 处理按钮失焦,排除菜单元素获得焦点的情况
  const handleButtonBlur = () => {
    // 延迟判断,确保菜单元素已完成焦点切换
    setTimeout(() => {
      const activeElement = document.activeElement;
      if (!dropdownRef.current?.contains(activeElement)) {
        setIsOpen(false);
      }
    }, 0);
  };

  const toggleMenu = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div ref={dropdownRef} className="dropdown-container">
      <button
        onClick={toggleMenu}
        onBlur={handleButtonBlur}
        aria-haspopup="true"
        aria-expanded={isOpen}
        className="dropdown-button"
      >
        {buttonText}
      </button>
      {isOpen && <div className="dropdown-menu">{children}</div>}
    </div>
  );
};

2. DropDownMenuItem 菜单项组件

export const DropDownMenuItem = ({ url, children, setIsOpen }) => {
  const handleItemClick = (e) => {
    // 先关闭菜单,再执行跳转
    setIsOpen(false);
    // 若使用React Router,可替换为navigate(url)
    window.location.href = url;
  };

  return (
    <a
      href={url}
      onClick={handleItemClick}
      className="dropdown-menu-item"
      tabIndex="0"
    >
      {children}
    </a>
  );
};

3. 使用示例

import { useState } from 'react';
import { DropDown, DropDownMenuItem } from './DropDown';

const MainNavigation = () => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  return (
    <nav className="main-nav">
      <DropDown
        buttonText="主导航"
        isOpen={isMenuOpen}
        setIsOpen={setIsMenuOpen}
      >
        <DropDownMenuItem url="/dashboard" setIsOpen={setIsMenuOpen}>控制台</DropDownMenuItem>
        <DropDownMenuItem url="/profile" setIsOpen={setIsMenuOpen}>个人中心</DropDownMenuItem>
        <DropDownMenuItem url="/settings" setIsOpen={setIsMenuOpen}>设置</DropDownMenuItem>
      </DropDown>
    </nav>
  );
};

export default MainNavigation;

关键修复说明

  • 全局点击监听:通过useEffect绑定mousedown事件,判断点击目标是否在Dropdown容器范围内,不在则关闭菜单,解决点击外部关闭的问题。
  • 失焦延迟判断:按钮的blur事件中使用setTimeout延迟检查当前焦点元素,避免点击菜单时因焦点切换误触发关闭。
  • 菜单项交互同步:点击菜单项时先关闭菜单再跳转,保证交互逻辑的一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 18:03:26