MUI Popover的transformOrigin属性失效问题求助
MUI Popover 关闭后重新打开位置偏移问题
问题描述
我有一个点击后打开Popover的按钮,Popover内包含异步加载数据的表格。遇到了transformOrigin属性失效的异常:
- 首次加载页面后,Popover显示位置完全符合预期
- Popover处于打开状态时调整窗口大小,它会自动修正到正确位置
- 但关闭Popover后再次打开,它会偏移到非预期位置,甚至可能因按钮所处位置导致超出屏幕范围
相关代码
按钮组件(ChooseRacesBtn.js)
import Button from "@mui/material/Button"; import Popover from "@mui/material/Popover"; import { useState } from "react"; import ChooseRacesPanel from "./ChooseRacesPanel"; export default function ChooseRacesBtn({ date, setIndex }) { const [anchorEl, setAnchorEl] = useState(null); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const open = Boolean(anchorEl); return ( <div> <Button sx={{ m: 2 }} variant="contained" onClick={handleClick}> Choose Races </Button> <Popover open={open} anchorEl={anchorEl} onClose={handleClose} anchorOrigin={{ vertical: "bottom", horizontal: "right", }} transformOrigin={{ vertical: "top", horizontal: "right", }} > <ChooseRacesPanel date={date} setIndex={setIndex} /> </Popover> </div> ); }
表格组件(ChooseRacesPanel.js)
import Table from "@mui/material/Table"; import TableCell from "@mui/material/TableCell"; import TableBody from "@mui/material/TableBody"; import TableContainer from "@mui/material/TableContainer"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import { useState, useEffect } from "react"; import { NavLink } from "react-router-dom"; export default function ChooseRacesPanel({ date, setIndex }) { const [meetingNames, setMeetingNames] = useState([]); const [meetingsRaces, setMeetingsRaces] = useState([]); useEffect(() => { const fetchData = async () => { const response = await fetch( `http://localhost:3000/meeting/${date}`, { method: "GET", headers: { "Content-Type": "application/json" }, } ); const json = await response.json(); console.log(json); setMeetingNames(json.meetingNames); setMeetingsRaces(json.meetingRaces); }; fetchData(); }, [date]); return ( <TableContainer component={Paper}> <Table> <TableBody> {meetingNames.map((mName, index) => ( <TableRow key={index} sx={{ "&:last-child td, &:last-child th": { border: 0, }, }} > <TableCell align="center">{mName}</TableCell> {meetingsRaces[index].map((race, i) => ( <TableCell key={i} align="center" onClick={() => setIndex(parseInt(race.index))}> <NavLink to={race.link} style={({ isActive }) => ({ color: isActive ? '#fff' : '#545e6f', background: isActive ? '#ee9154' : '#f0f0f0', })} > {race.time} </NavLink> </TableCell> ))} </TableRow> ))} </TableBody> </Table> </TableContainer> ); }
问题原因
这个问题的核心是Popover会缓存首次渲染时的内容尺寸,而表格内容是异步加载的:
- 首次打开时,Popover先以空内容渲染,获取初始尺寸并计算位置;随后数据加载完成,内容尺寸变化,但MUI会自动触发位置更新
- 关闭再打开时,Popover复用了之前缓存的旧尺寸信息,而此时按钮的位置可能因窗口变化或页面布局调整发生了改变,导致位置计算错误
解决方案
方案1:强制Popover每次打开时重新渲染
通过给Popover添加动态key,每次打开时更新key值,让Popover完全重新渲染,从而重新计算内容尺寸和位置:
export default function ChooseRacesBtn({ date, setIndex }) { const [anchorEl, setAnchorEl] = useState(null); const [popoverKey, setPopoverKey] = useState(0); const handleClick = (event) => { setAnchorEl(event.currentTarget); setPopoverKey(prev => prev + 1); // 每次打开更新key }; const handleClose = () => { setAnchorEl(null); }; const open = Boolean(anchorEl); return ( <div> <Button sx={{ m: 2 }} variant="contained" onClick={handleClick}> Choose Races </Button> <Popover key={popoverKey} // 添加动态key open={open} anchorEl={anchorEl} onClose={handleClose} anchorOrigin={{ vertical: "bottom", horizontal: "right", }} transformOrigin={{ vertical: "top", horizontal: "right", }} > <ChooseRacesPanel date={date} setIndex={setIndex} /> </Popover> </div> ); }
方案2:数据加载完成后手动更新位置
利用Popover的updatePosition方法,在表格数据加载完成后强制重新计算位置:
- 父组件中获取Popover引用并定义更新方法:
import { useRef } from "react"; export default function ChooseRacesBtn({ date, setIndex }) { const [anchorEl, setAnchorEl] = useState(null); const popoverRef = useRef(null); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const updatePopoverPosition = () => { popoverRef.current?.updatePosition(); }; const open = Boolean(anchorEl); return ( <div> <Button sx={{ m: 2 }} variant="contained" onClick={handleClick}> Choose Races </Button> <Popover ref={popoverRef} open={open} anchorEl={anchorEl} onClose={handleClose} anchorOrigin={{ vertical: "bottom", horizontal: "right", }} transformOrigin={{ vertical: "top", horizontal: "right", }} > <ChooseRacesPanel date={date} setIndex={setIndex} onDataLoaded={updatePopoverPosition} /> </Popover> </div> ); }
- 子组件中数据加载完成后调用更新方法:
export default function ChooseRacesPanel({ date, setIndex, onDataLoaded }) { const [meetingNames, setMeetingNames] = useState([]); const [meetingsRaces, setMeetingsRaces] = useState([]); useEffect(() => { const fetchData = async () => { const response = await fetch( `http://localhost:3000/meeting/${date}`, { method: "GET", headers: { "Content-Type": "application/json" }, } ); const json = await response.json(); setMeetingNames(json.meetingNames); setMeetingsRaces(json.meetingRaces); // 延迟调用确保DOM已更新 setTimeout(() => onDataLoaded?.(), 0); }; fetchData(); }, [date, onDataLoaded]); // 其余代码不变 }
方案3:固定Popover内容尺寸(可选)
如果表格的尺寸可以提前确定,直接给内容容器设置固定宽高,避免动态尺寸变化导致的位置问题:
<TableContainer component={Paper} sx={{ width: 600, maxHeight: 400, overflowY: 'auto' }}>
内容的提问来源于stack exchange,提问作者Bilal Atique
相关产品推荐
相关产品推荐

