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

如何配置Reach UI Menu Button使其避开可滚动元素的上下边缘

解决方案

实现逻辑

Reach UI 的 MenuPopover 组件支持传入自定义 position 函数控制菜单定位,函数接收触发按钮的位置对象 targetRect、菜单本身的位置对象 popoverRect 两个入参,返回调整后的 top、left 定位值即可。你只需要在函数内加入滚动容器的边界判断逻辑即可实现容器边缘避让。

完整代码示例

首先给滚动容器绑定ref获取它的边界信息:

import { useRef, useEffect } from 'react';
import { Menu, MenuButton, MenuPopover, MenuList, MenuItem } from '@reach/menu-button';
import '@reach/menu-button/styles.css';

const ScrollContainerMenu = () => {
  // 绑定滚动容器ref
  const scrollContainerRef = useRef(null);
  // 绑定MenuPopover的ref用于触发重定位
  const popoverRef = useRef(null);

  // 自定义定位函数
  const customPosition = (targetRect, popoverRect) => {
    const containerRect = scrollContainerRef.current.getBoundingClientRect();
    let top = targetRect.bottom;
    let left = targetRect.left;

    // 垂直方向避让逻辑
    // 检查向下展开是否超出容器下边缘
    if (top + popoverRect.height > containerRect.bottom) {
      // 尝试向上展开
      const topWhenUp = targetRect.top - popoverRect.height;
      // 向上展开不会超出容器上边缘则使用该位置
      if (topWhenUp >= containerRect.top) {
        top = topWhenUp;
      } else {
        // 上下都放不下时对齐容器上边缘
        top = containerRect.top;
      }
    }
    // 兜底:防止菜单顶部超出容器上边缘
    if (top < containerRect.top) {
      top = containerRect.top;
    }

    // 水平方向避让逻辑
    // 检查向右对齐是否超出容器右边缘
    if (left + popoverRect.width > containerRect.right) {
      // 尝试向左对齐
      const leftWhenLeft = targetRect.right - popoverRect.width;
      if (leftWhenLeft >= containerRect.left) {
        left = leftWhenLeft;
      } else {
        // 左右都放不下时对齐容器左边缘
        left = containerRect.left;
      }
    }
    // 兜底:防止菜单左侧超出容器左边缘
    if (left < containerRect.left) {
      left = containerRect.left;
    }

    return { top, left };
  };

  // 监听滚动容器滚动事件,动态更新菜单位置
  useEffect(() => {
    const container = scrollContainerRef.current;
    const handleScroll = () => {
      // 菜单存在时触发重定位
      if (popoverRef.current) {
        popoverRef.current.reposition();
      }
    };
    container.addEventListener('scroll', handleScroll);
    return () => container.removeEventListener('scroll', handleScroll);
  }, []);

  return (
    <div 
      ref={scrollContainerRef} 
      style={{ overflowY: 'scroll', height: '400px', border: '1px solid #eee' }}
    >
      {/* 滚动容器内的其他内容 */}
      <div style={{ height: '200px' }}></div>
      <Menu>
        <MenuButton>点击打开菜单</MenuButton>
        <MenuPopover 
          ref={popoverRef} 
          position={customPosition}
          // 可选:限制菜单最大高度,超出时内部滚动
          style={{ maxHeight: '380px', overflowY: 'auto' }}
        >
          <MenuList>
            <MenuItem>选项 1</MenuItem>
            <MenuItem>选项 2</MenuItem>
            <MenuItem>选项 3</MenuItem>
            <MenuItem>选项 4</MenuItem>
            <MenuItem>选项 5</MenuItem>
            <MenuItem>选项 6</MenuItem>
            <MenuItem>选项 7</MenuItem>
            <MenuItem>选项 8</MenuItem>
            <MenuItem>选项 9</MenuItem>
            <MenuItem>选项 10</MenuItem>
          </MenuList>
        </MenuPopover>
      </Menu>
      <div style={{ height: '200px' }}></div>
    </div>
  );
};

export default ScrollContainerMenu;

补充说明

如果你的菜单高度可能超过滚动容器的可视高度,建议给MenuPopover设置max-height为滚动容器的可视高度,同时开启overflow-y: auto,让菜单内部可滚动,避免内容溢出。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 12:45:04