我认为您使用 React Portal 的解决方案实际上很有意义,但您只需要更多技巧来定位元素。
{showMenu &&
createPortal(
<div className="SelectMenu" style={menuStyle}>
I'm the menu.
</div>,
PORTAL_HOST_NODE
)}
menuStyle 只是 useMemo 返回的计算值,这只是我们需要应用的 CSS 样式来定位您的菜单。您想要的是使用position: fixed 的菜单,然后只需使用触发元素的top、left、width 和height 属性(在本例中为“打开选择”按钮)即可定位元素。
为了确保在滚动视口时更新位置,您还需要跟踪 window.scrollY 值:
const [scrollY, setScrollY] = useState(window.scrollY);
useEffect(() => {
const onScroll = () => setScrollY(window.scrollY);
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
我看到您有一个手动 0.5rem 偏移量,您希望选择菜单远离其触发元素。我们可以存储为 CSS 变量:
.SelectMenu {
--top-offset: 0.5rem;
position: fixed;
top: 0;
left: 0;
z-index: 99;
border: 1px solid #bebebe;
background-color: white;
border-radius: 0.5rem;
padding: 2rem 1rem;
box-sizing: border-box;
}
然后,就像前面提到的那样设置其他代码。一些注意事项:
- 使用 CSS 变换,以便我们可以处理子像素放置(而不是顶部/左侧)
- 设置宽度,使您的选择菜单跟随其触发元素的宽度
- 使用上面设置的 CSS 自定义属性计算 CSS 变换的 y 轴值
const [menuTriggerElementRect, setMenuTriggerElementRect] = useState(null);
const toggleMenu = useCallback((e) => {
setShowMenu((old) => !old);
setMenuTriggerElementRect(e.currentTarget.getBoundingClientRect());
}, []);
const menuStyle = useMemo(() => {
if (menuTriggerElementRect === null) return {};
const { top, left, width, height } = menuTriggerElementRect;
return {
transform: `translate(${left}px, calc(${
top + height - scrollY
}px + var(--top-offset)))`,
width: `${width}px`
};
}, [menuTriggerElementRect, scrollY]);
在此处查看您更新的 CodeSandbox 示例: