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> ); }
关键逻辑说明
- 滚动容器引用:通过
useRef获取滚动容器的DOM节点,确保监听的是正确的滚动事件(示例中是内部div滚动,而非全局window) - 滚动事件监听:在
useEffect中给滚动容器添加scroll事件监听,只要滚动发生且Popover处于打开状态,就调用关闭函数 - 清理监听:组件卸载时移除滚动事件监听,避免内存泄漏
如果需要更精确的判断(仅当触发元素滚出视口才关闭),可以结合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
相关产品推荐
相关产品推荐

