Signatur
Beschreibung
document.elementsFromPoint(x, y) ermittelt alle Elemente, die sich an einem bestimmten Punkt im Viewport überlagern. Anders als elementFromPoint(), das nur das oberste Element liefert, erhältst du hier den gesamten „Stapel“, also die komplette Treffer-Hierarchie in Stapelreihenfolge (z-Order): Index 0 ist das vorderste Element, am Ende stehen typischerweise <body> und <html>.
Das ist besonders nützlich für Drag-and-drop (Welche Drop-Zone liegt unter dem gezogenen Element?), für Overlay- und Tooltip-Logik, für Debugging-Werkzeuge (Welche Elemente verdecken einen Button?) sowie für eigene Hit-Tests in Canvas-ähnlichen Oberflächen, die aus DOM-Elementen bestehen.
Die Koordinaten beziehen sich auf den Viewport (wie clientX/clientY eines Mausereignisses), nicht auf das Dokument. Elemente mit pointer-events: none werden ignoriert, ebenso Elemente mit visibility: hidden. Liegt der Punkt außerhalb des Viewports, ist das Ergebnis ein leeres Array.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $x Pflicht | number | Horizontale Koordinate in CSS-Pixeln, relativ zur linken Kante des Viewports. | |
| $y Pflicht | number | Vertikale Koordinate in CSS-Pixeln, relativ zur oberen Kante des Viewports. |
Rückgabewert
Array (keine NodeList) mit allen getroffenen Elementen, sortiert vom obersten zum untersten. Leeres Array, wenn der Punkt außerhalb des Viewports liegt.Beispiele
Alle Elemente unter dem Mauszeiger protokollieren
document.addEventListener('click', (event) => {
const stack = document.elementsFromPoint(event.clientX, event.clientY);
// Kompakte Darstellung: tag#id.klasse
const labels = stack.map((el) => {
const id = el.id ? `#${el.id}` : '';
const cls = el.classList.length ? `.${[...el.classList].join('.')}` : '';
return `${el.tagName.toLowerCase()}${id}${cls}`;
});
console.log(labels.join(' > '));
});
Drop-Zone beim Ziehen ermitteln (gezogenes Element verdeckt das Ziel)
const dragged = document.querySelector('#draggable');
document.addEventListener('pointermove', (event) => {
if (!dragged.hasPointerCapture?.(event.pointerId) && event.buttons !== 1) return;
// Das gezogene Element liegt oben – daher den ganzen Stapel durchsuchen
const dropZone = document
.elementsFromPoint(event.clientX, event.clientY)
.find((el) => el !== dragged && el.matches('.drop-zone'));
document.querySelectorAll('.drop-zone').forEach((zone) => {
zone.classList.toggle('is-over', zone === dropZone);
});
});
Prüfen, ob ein Button von einem Overlay verdeckt wird
const isCovered = (element) => {
const rect = element.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const [topMost] = document.elementsFromPoint(x, y);
// Verdeckt, wenn das oberste Element weder das Element selbst noch ein Kind davon ist
return topMost !== undefined && !element.contains(topMost);
};
const submit = document.querySelector('#submit');
console.log('Verdeckt:', isCovered(submit));
// Wichtig · Fallstricke
- Viewport-Koordinaten: Verwende
clientX/clientY, nichtpageX/pageY. Bei gescrollter Seite liefern Seitenkoordinaten sonst falsche Treffer. pointer-events: noneundvisibility: hiddenschließen Elemente aus – praktisch, um ein gezogenes Element „durchlässig“ zu machen, aber auch eine häufige Ursache für scheinbar fehlende Treffer.- Shadow DOM: Auf
documentaufgerufen, werden Elemente in Shadow Roots auf ihren Host „retargetet“. Für Treffer innerhalb eines Shadow TreesshadowRoot.elementsFromPoint()verwenden. - iframes: Inhalte eines
<iframe>werden nicht durchsucht; nur das<iframe>-Element selbst erscheint im Ergebnis. - Performance: Der Aufruf erzwingt ggf. ein synchrones Layout (Reflow). In
pointermove-Handlern sparsam einsetzen oder perrequestAnimationFramedrosseln. - Kompatibilität: In allen aktuellen Browsern verfügbar; ältere Edge/IE-Versionen kannten nur
msElementsFromPoint()(liefert eineNodeList).