Signatur
Beschreibung
elementFromPoint() auf einem ShadowRoot funktioniert wie document.elementFromPoint(). Der Unterschied: Das Ergebnis wird aus Sicht dieses Shadow-Baums bestimmt. Die Methode sucht das oberste Element, das an den Koordinaten (x, y) relativ zum Viewport gerendert wird. Liegt dieses Element im Shadow-DOM, bekommst du das echte innere Element und nicht den Host.
Hintergrund: document.elementFromPoint() wendet Retargeting an. Es liefert für Elemente in einem Shadow-Baum nur das Host-Element, damit die Kapselung erhalten bleibt. Wenn du innerhalb einer Web Component wissen willst, welches interne Element unter dem Mauszeiger oder unter einem Touch-Punkt liegt, rufst du die Methode direkt auf dem eigenen shadowRoot auf.
Typische Einsätze sind eigene Drag-and-Drop-Logik in Komponenten, Trefferprüfung (Hit-Testing) bei Touch-Gesten ohne Ereignisziel, Tooltips und Barrierefreiheits- oder Test-Werkzeuge. Die Methode stammt aus dem Mixin DocumentOrShadowRoot. Für alle übereinanderliegenden Elemente an einem Punkt gibt es elementsFromPoint().
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $x Pflicht | number | Horizontale Koordinate in CSS-Pixeln relativ zur linken Kante des Viewports, also nicht relativ zum Dokument oder zum Host-Element. | |
| $y Pflicht | number | Vertikale Koordinate in CSS-Pixeln relativ zur oberen Kante des Viewports. |
Rückgabewert
null, wenn der Punkt außerhalb des Viewports liegt oder dort nichts gerendert wird.Beispiele
Inneres Element unter dem Mauszeiger ermitteln
class HoverInfo extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
root.innerHTML = `
<style>button { margin: 4px; }</style>
<button id="a">Speichern</button>
<button id="b">Abbrechen</button>`;
}
connectedCallback() {
document.addEventListener('pointermove', (e) => {
const inner = this.shadowRoot.elementFromPoint(e.clientX, e.clientY);
const outer = document.elementFromPoint(e.clientX, e.clientY);
if (inner?.tagName === 'BUTTON') {
console.log('Shadow:', inner.id, '| Dokument:', outer.tagName);
}
});
}
}
customElements.define('hover-info', HoverInfo);
document.body.append(document.createElement('hover-info'));
Drop-Ziel beim Ziehen per Touch bestimmen
const host = document.createElement('div');
document.body.append(host);
const root = host.attachShadow({ mode: 'open' });
root.innerHTML = `
<style>.zone { width: 150px; height: 80px; border: 2px dashed; margin: 8px; }</style>
<div class="zone" data-name="Eingang"></div>
<div class="zone" data-name="Archiv"></div>`;
// Bei touchmove ist e.target immer das Startelement,
// daher wird das Ziel über die Koordinaten ermittelt
document.addEventListener('touchend', (e) => {
const { clientX, clientY } = e.changedTouches[0];
const target = root.elementFromPoint(clientX, clientY);
const zone = target?.closest('.zone');
console.log(zone ? `Abgelegt in: ${zone.dataset.name}` : 'Kein Drop-Ziel');
});
Trefferprüfung an der Mitte eines Elements
const host = document.createElement('div');
document.body.append(host);
const root = host.attachShadow({ mode: 'open' });
root.innerHTML = '<p id="text">Hallo</p><div id="overlay"></div>';
const isVisibleAtCenter = (el) => {
const r = el.getBoundingClientRect();
const hit = el.getRootNode().elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
return hit === el || el.contains(hit);
};
console.log(isVisibleAtCenter(root.getElementById('text')));
// Wichtig · Fallstricke
- Koordinaten beziehen sich auf den Viewport: Verwende
clientX/clientYund nichtpageX/pageY. Punkte außerhalb des sichtbaren Bereichs liefernnull. - Nicht standardisiert: Die Methode steht nicht in der DOM-Spezifikation, sondern gilt als Erweiterung aus CSSOM View bzw.
DocumentOrShadowRoot. Alle modernen Browser unterstützen sie, im Detail kann sich das Verhalten aber unterscheiden, etwa bei verschachtelten Shadow-Roots oder bei Elementen außerhalb des Baums. - Geschlossene Shadow-Roots: Bei
mode: 'closed'isthost.shadowRootgleichnull. Du brauchst dann die Referenz, dieattachShadow()zurückgegeben hat. - Elemente, die Treffer ignorieren: Elemente mit
pointer-events: noneodervisibility: hiddenwerden übersprungen. Das Ergebnis kann dann ein darunterliegendes Element sein. - Performance: Jeder Aufruf kann ein Layout erzwingen. Bei häufigen Ereignissen wie
pointermovedie Aufrufe drosseln oder mitrequestAnimationFramebündeln. - Verschachtelte Komponenten: Liegt der Punkt über dem Host einer weiteren, verschachtelten Komponente, bekommst du nur diesen Host und nicht deren Innenleben. Für das Innenleben rufst du die Methode auf dem jeweiligen
shadowRooterneut auf.