Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scrollable div with absolute content

Tags:

html

css

reactjs

I have a Card div that is supposed to show the scroll if the content exceeds it's height. I've used overflow-y: auto to do that. I'm trying to use a Select inside it, and the select menu is supposed to show in front of the card. The menu position is absolute.

The problem is, even with position: absolute, the menu is taking space inside the card. making it scrollable.

image

If I remove the overflow from the card, it works fine, but the content exceeds it. I've created a sandbox for it:

https://codesandbox.io/s/position-absolute-inside-overflow-y-9kppcy?file=/src/App.js

Other things I have tried

  • Show the SelectMenu inside a portal.

    • Better results I got so far, but when the window is scrolled, the menu got fixed in the screen.
  • Remove the overflow from the card, add it to a CardBody element, and keep the select outside it.

    • Did the magic, but with deep nested divs, is really hard to keep it outside every element with overflow.
    • Did not work if the Select is inside a modal (because modals should have a scroll).

More details

  • I'm using react-select to create my select, but the problem is strictly css and html.

This might be a common question, but I could not find a solution.

like image 872
Alexandre Aragão Avatar asked Aug 15 '26 23:08

Alexandre Aragão


1 Answers

I think your solution of using a React Portal actually makes a lot of sense, but you just need a little more trick to position the element.

{showMenu &&
  createPortal(
    <div className="SelectMenu" style={menuStyle}>
      I'm the menu.
    </div>,
    PORTAL_HOST_NODE
  )}

The menuStyle is simply a computed value returned by useMemo, which is simply the CSS styles we need to apply to position your menu. What you want is the menu to use position: fixed, and then simply use the top, left, width and height attributes of the trigger element (in this case, your "Open Select" button) to position the element.

In order to ensure that the position is updated when the viewport is scrolled, you will also need to track the window.scrollY value:

const [scrollY, setScrollY] = useState(window.scrollY);

useEffect(() => {
  const onScroll = () => setScrollY(window.scrollY);
  window.addEventListener("scroll", onScroll);

  return () => {
    window.removeEventListener("scroll", onScroll);
  };
}, []);

I see that you have a manual 0.5rem offset you want for the select menu to be positioned away from its trigger element. We can store in as a CSS variable:

.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;
}

Then, it is matter of setting the other code up as mentioned before. Some notes:

  • Use CSS transform so that we can handle sub pixel placement (instead of top/left)
  • Set the width so that your select menu follows the width of its trigger element
  • Use the CSS custom property set above to calculate the y-axis value of CSS transform
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]);

See your updated CodeSandbox example here:

Edit Position absolute inside overflow Y (forked)

like image 67
Terry Avatar answered Aug 17 '26 13:08

Terry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!