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

MUI Tooltip点击事件:无法聚焦关闭按钮且弹窗自动闪关问题

问题描述

基于MUI的Tooltip组件自定义了TooltipComponent,功能类似Dialog:点击显示“CLICK”的div后,弹出包含文本和“x”关闭按钮的内容,点击“x”可关闭弹窗。为触发元素添加了onClick和onKeyPress事件,二者作用均为打开弹窗。

出于无障碍需求,要求弹窗打开时将焦点设置到“x”按钮,通过React的ref实现此功能。但onKeyPress事件下功能正常,onClick事件不仅无法设置焦点,弹窗还会自动打开后立即关闭(按代码逻辑仅点击“x”才可关闭)。

问题原因

这是事件冒泡+MUI ClickAwayListener的判定逻辑共同导致的:

  • 点击触发div时,toggleTooltip执行将open设为true,Tooltip渲染弹窗内容。但点击事件会向上冒泡,被内部的ClickAwayListener捕获——此时弹窗刚渲染完成,触发元素的点击事件会被判定为“弹窗外部点击”,从而调用closeTooltip关闭弹窗。
  • 键盘事件(onKeyPress)不会触发ClickAwayListener的“点击外部”判定逻辑,因此能正常打开弹窗并设置焦点。
  • 弹窗被立即关闭,导致useEffect中设置焦点的代码还未执行就失效。

解决方法

核心修改点

  1. 阻止触发元素的点击事件冒泡:避免ClickAwayListener误将触发点击判定为外部点击
  2. 优化焦点设置逻辑:确保弹窗稳定打开后再执行焦点设置

修改后的代码

import React, { useEffect, useRef, useState } from 'react';
import { Tooltip, ClickAwayListener } from '@mui/material';

function TooltipComponent() {  
  const [open, setOpen] = useState(false);   
  const toggleTooltip = (e) => {
    // 阻止点击事件冒泡,避免被ClickAwayListener捕获
    e.stopPropagation();
    setOpen(!open); 
  };
  const closeTooltip = () => setOpen(false);  
  const closeBtnRef = useRef(null);

  useEffect(() => {
    if (open && closeBtnRef.current) {
      // 确保弹窗打开后再设置焦点
      closeBtnRef.current.focus();
    }
  }, [open]);

  const TooltipContent = (
    <ClickAwayListener onClickAway={closeTooltip}>
      <div style={{ position: 'relative' }}>
        <div
          tabIndex={0}
          ref={closeBtnRef}
          onClick={closeTooltip}
          onKeyPress={(e) => {
            // 仅在按下回车或空格时关闭
            if (e.key === 'Enter' || e.key === ' ') {
              closeTooltip();
            }
          }}
          aria-label="close tooltip" // 替换非标准属性为无障碍规范属性
          style={{
            position: 'absolute',
            left: '-3px',
            top: '-3px',
            fontSize: '36px',
            fontWeight: 'light',
            cursor: 'pointer',
          }}
        >
          x
        </div>
        <div aria-label="tooltip content">
          <div style={{ padding: '24px', color: 'black' }}>
            Top - Start -{' '}
            <div>
              <a href="#">I am a link</a>
            </div>
          </div>
        </div>
      </div>
    </ClickAwayListener>
  );

  return (
    <Tooltip
      title={TooltipContent}
      arrow
      disableHoverListener={true}
      open={open}
      placement="bottom"
      onClose={closeTooltip}
      PopperProps={{
        disablePortal: true,
        role: 'dialog',
        'aria-live': 'polite',
      }}
    >
      <div
        onClick={toggleTooltip}
        onKeyPress={(e) => {
          // 仅在按下回车或空格时触发打开
          if (e.key === 'Enter' || e.key === ' ') {
            toggleTooltip(e);
          }
        }}
        tabIndex={0} // 给触发元素添加tabIndex,确保键盘可访问
        style={{
          display: 'inline-block',
          textAlign: 'center',
          cursor: 'pointer',
        }}
      >
        <div style={{ margin: '6px', color: 'black' }}>
          CLICK
        </div>
      </div>
    </Tooltip>
  ); 
}

export default TooltipComponent;

额外优化说明

  • 替换了非标准的aria-description为aria-label,符合WAI-ARIA无障碍规范
  • 给触发元素添加tabIndex={0},确保键盘用户可通过Tab键聚焦触发元素
  • 优化键盘事件逻辑,仅在按下回车或空格时执行打开/关闭操作,匹配原生交互习惯

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 03:20:03