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

React中MUI v5悬浮Popover页面滚动时未消失的问题解决

MUI v5 Popover滚动时未自动关闭的解决方案

问题描述

按照MUI v5的标准模式实现鼠标悬浮元素显示Popover功能时,常规场景运行正常,但存在以下异常:

  • 鼠标悬浮到触发元素上
  • 不移动鼠标,使用滚轮滚动页面
  • 触发元素滚出视野后,Popover仍保持可见

常规实现代码

<div style={{ height: "100vh", overflow: "auto" }}>
  <div style={{ height: 1000 }}>
    <Typography
      aria-owns={open ? "mouse-over-popover" : undefined}
      aria-haspopup="true"
      onMouseEnter={handlePopoverOpen}
      onMouseLeave={handlePopoverClose}
    >
      Hover and then scroll
    </Typography>
    <Popover
      id="mouse-over-popover"
      className={classes.popover}
      classes={{
        paper: classes.paper
      }}
      open={open}
      anchorEl={anchorEl}
      anchorOrigin={{
        vertical: "bottom",
        horizontal: "left"
      }}
      transformOrigin={{
        vertical: "top",
        horizontal: "left"
      }}
      onClose={handlePopoverClose}
      disableRestoreFocus
    >
      <Typography>I use Popover.</Typography>
    </Popover>
  </div>
</div>

解决方案

要实现滚动时关闭Popover且不禁止页面滚动,我们可以给滚动容器添加滚动事件监听,在滚动触发时关闭Popover。具体实现如下:

修改后的完整代码

import React, { useState, useEffect, useRef } from 'react';
import { Typography, Popover, makeStyles } from '@mui/material';

const useStyles = makeStyles((theme) => ({
  popover: {
    pointerEvents: 'none',
  },
  paper: {
    padding: theme.spacing(1),
  },
}));

export default function MouseOverPopover() {
  const classes = useStyles();
  const [open, setOpen] = useState(false);
  const [anchorEl, setAnchorEl] = useState(null);
  const scrollContainerRef = useRef(null); // 滚动容器的引用

  const handlePopoverOpen = (event) => {
    setAnchorEl(event.currentTarget);
    setOpen(true);
  };

  const handlePopoverClose = () => {
    setOpen(false);
    setAnchorEl(null);
  };

  useEffect(() => {
    const scrollContainer = scrollContainerRef.current;
    if (!scrollContainer) return;

    // 滚动事件回调:关闭Popover
    const handleScroll = () => {
      if (open) {
        handlePopoverClose();
      }
    };

    scrollContainer.addEventListener('scroll', handleScroll);
    // 组件卸载时移除监听
    return () => {
      scrollContainer.removeEventListener('scroll', handleScroll);
    };
  }, [open]); // 依赖open,确保状态变化时监听逻辑正确

  return (
    <div ref={scrollContainerRef} style={{ height: "100vh", overflow: "auto" }}>
      <div style={{ height: 1000 }}>
        <Typography
          aria-owns={open ? "mouse-over-popover" : undefined}
          aria-haspopup="true"
          onMouseEnter={handlePopoverOpen}
          onMouseLeave={handlePopoverClose}
        >
          Hover and then scroll
        </Typography>
        <Popover
          id="mouse-over-popover"
          className={classes.popover}
          classes={{
            paper: classes.paper
          }}
          open={open}
          anchorEl={anchorEl}
          anchorOrigin={{
            vertical: "bottom",
            horizontal: "left"
          }}
          transformOrigin={{
            vertical: "top",
            horizontal: "left"
          }}
          onClose={handlePopoverClose}
          disableRestoreFocus
        >
          <Typography>I use Popover.</Typography>
        </Popover>
      </div>
    </div>
  );
}

关键逻辑说明

  1. 滚动容器引用:通过useRef获取滚动容器的DOM节点,确保监听的是正确的滚动事件(示例中是内部div滚动,而非全局window)
  2. 滚动事件监听:在useEffect中给滚动容器添加scroll事件监听,只要滚动发生且Popover处于打开状态,就调用关闭函数
  3. 清理监听:组件卸载时移除滚动事件监听,避免内存泄漏

如果需要更精确的判断(仅当触发元素滚出视口才关闭),可以结合getBoundingClientRect()检查元素是否在视口内:

const handleScroll = () => {
  if (open && anchorEl) {
    const rect = anchorEl.getBoundingClientRect();
    // 检查元素是否完全在视口外
    const isOutOfView = rect.bottom < 0 || rect.top > window.innerHeight;
    if (isOutOfView) {
      handlePopoverClose();
    }
  }
};

这样就能实现滚动时自动关闭Popover,同时不影响页面的正常滚动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:50:27