initial commit
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# Dropdown Portal / Teleport Pattern
|
||||
|
||||
When a dropdown is clipped by a parent with `overflow: hidden` or trapped inside a stacking context, CSS z-index alone cannot fix it. The solution is to move the dropdown to a portal root at the end of `<body>` and position it with `position: fixed` + `getBoundingClientRect()`.
|
||||
|
||||
## Setup
|
||||
|
||||
Every page needs `<div id="dropdown-root"></div>` right before `</body>`. This is a portal target — a direct child of `<body>` with no clipping ancestors.
|
||||
|
||||
## Vanilla JS Implementation
|
||||
|
||||
```javascript
|
||||
function showDropdown(dropdown, anchorInput) {
|
||||
// 1. Move to portal
|
||||
const root = document.getElementById('dropdown-root');
|
||||
if (root && dropdown.parentElement !== root) {
|
||||
root.appendChild(dropdown);
|
||||
}
|
||||
|
||||
// 2. Position relative to anchor
|
||||
const rect = anchorInput.getBoundingClientRect();
|
||||
dropdown.style.position = 'fixed';
|
||||
dropdown.style.top = (rect.bottom + 4) + 'px';
|
||||
dropdown.style.left = rect.left + 'px';
|
||||
dropdown.style.width = rect.width + 'px';
|
||||
dropdown.style.zIndex = '99999';
|
||||
dropdown.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 3. Reposition on scroll/resize
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!dropdown.classList.contains('hidden')) {
|
||||
const rect = anchorInput.getBoundingClientRect();
|
||||
dropdown.style.top = (rect.bottom + 4) + 'px';
|
||||
dropdown.style.left = rect.left + 'px';
|
||||
dropdown.style.width = rect.width + 'px';
|
||||
}
|
||||
}, { passive: true });
|
||||
```
|
||||
|
||||
## React Implementation
|
||||
|
||||
```tsx
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
function Dropdown({ open, anchorRect, children }) {
|
||||
if (!open) return null;
|
||||
return createPortal(
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: anchorRect.bottom + 4,
|
||||
left: anchorRect.left,
|
||||
width: anchorRect.width,
|
||||
zIndex: 99999,
|
||||
}}>
|
||||
{children}
|
||||
</div>,
|
||||
document.getElementById('dropdown-root')!
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Why z-index alone fails
|
||||
|
||||
A parent with `overflow: hidden`, `transform`, `will-change`, or `position: relative` creates a new stacking context. The dropdown's z-index is scoped within that parent — it can never escape to be above a sibling element that comes later in the DOM.
|
||||
|
||||
## Diagnosis
|
||||
|
||||
If `z-index: 99999 !important` on the dropdown doesn't fix clipping, and the dropdown is inside any ancestor with `overflow: hidden` or `position: relative`, this is the fix.
|
||||
Reference in New Issue
Block a user