Start · Sprachen · JavaScript · Referenz · Event.composedPath()

Event.composedPath()

Methode

Gibt ein Array der Objekte zurück, die das Ereignis durchläuft – vom eigentlichen Ziel bis hinauf zu <code>Window</code>, inklusive offener Shadow-DOM-Knoten.

seit JavaScript Baseline: alle modernen Browser (seit 2020) Kategorie: webapi

Signatur

Event.prototype.composedPath()

Beschreibung

composedPath() liefert den Ereignispfad: ein Array aller EventTarget-Objekte, an denen der Listener aufgerufen wird. Das erste Element ist das innerste Ziel (z. B. ein <span> in einem Button), danach folgen die Vorfahren über document bis zu window.

Richtig nützlich wird die Methode beim Shadow DOM. Sobald ein Ereignis eine Shadow-Grenze verlässt, wird event.target auf den Host umgebogen (Retargeting). Außerhalb der Komponente sieht man also nur das Custom Element, nicht das Element im Inneren, das angeklickt wurde. Bei offenen Shadow Roots (mode: "open") enthält composedPath() trotzdem die inneren Knoten. Bei geschlossenen Shadow Roots (mode: "closed") tauchen sie nur auf, wenn der Listener selbst innerhalb dieses Shadow-Baums sitzt.

Typische Einsätze sind Klick-außerhalb-Erkennung bei Dropdowns und Modals, Event-Delegation über Komponentengrenzen hinweg und die Fehlersuche bei Bubbling-Problemen. Ob ein Ereignis die Shadow-Grenze überhaupt verlässt, hängt von der Eigenschaft composed ab. Native UI-Ereignisse wie click haben sie auf true, bei eigenen CustomEvents muss man composed: true selbst setzen.

Rückgabewert

Typ
EventTarget[]
Beschreibung
Ein Array von EventTarget-Objekten, vom innersten Ziel bis zum äußersten (meist Window). Wird die Methode nicht während der Auslieferung aufgerufen (also nach Ende des Dispatch), ist das Array leer.

Beispiele

Ereignispfad eines Klicks ausgeben

// HTML: <main><button id="save"><span>Speichern</span></button></main>
document.addEventListener('click', (event) => {
  const path = event.composedPath().map((node) =>
    node === window ? 'window'
      : node === document ? 'document'
      : node.nodeName.toLowerCase()
  );
  console.log(path.join(' → '));
});
span → button → main → body → html → document → window

Klick außerhalb erkennen – funktioniert auch mit Web Components

const dropdown = document.querySelector('my-dropdown');

const onOutsideClick = (event) => {
  // contains() versagt bei Shadow DOM, composedPath() nicht
  if (!event.composedPath().includes(dropdown)) {
    dropdown.removeAttribute('open');
    console.log('Dropdown geschlossen');
  }
};

document.addEventListener('pointerdown', onOutsideClick);
Dropdown geschlossen // bei Klick außerhalb der Komponente

Retargeting im offenen Shadow DOM umgehen

class FancyCard extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: 'open' });
    root.innerHTML = '<button class="like">Gefällt mir</button>';
  }
}
customElements.define('fancy-card', FancyCard);

const card = document.createElement('fancy-card');
document.body.append(card);

document.addEventListener('click', (event) => {
  console.log('target:', event.target.localName);
  const inner = event.composedPath()[0];
  console.log('wirklich geklickt:', inner.className);
});

card.shadowRoot.querySelector('.like').click();
target: fancy-card wirklich geklickt: like

CustomEvent mit composed über die Shadow-Grenze schicken

const host = document.createElement('div');
document.body.append(host);
const root = host.attachShadow({ mode: 'open' });
root.innerHTML = '<input id="q">';

document.addEventListener('search', (event) => {
  console.log(event.composedPath().length > 0, event.detail);
});

// Ohne composed: true käme das Event nie bei document an
root.getElementById('q').dispatchEvent(
  new CustomEvent('search', { detail: 'js', bubbles: true, composed: true })
);
true js

// Wichtig · Fallstricke

  • Nur während des Dispatch gültig: Wird composedPath() später aufgerufen, etwa in einem setTimeout, nach einem await oder für ein gespeichertes Event-Objekt, kommt ein leeres Array zurück. Wenn man den Pfad später braucht, sollte man ihn direkt im Listener in eine Variable kopieren.
  • Geschlossene Shadow Roots (mode: "closed") werden außerhalb nicht offengelegt. Der Pfad endet für äußere Listener dann beim Host.
  • Ohne composed: true bleibt ein Ereignis im eigenen Shadow-Baum. Außen kommt es gar nicht an, dort gibt es also auch keinen Pfad.
  • Bei Knoten, die nicht im Dokument hängen, endet der Pfad bei der Wurzel des losgelösten Teilbaums, document und window fehlen dann.
  • Node.contains() ist für Klick-außerhalb-Prüfungen mit Web Components unzuverlässig, weil es keine Shadow-Grenzen überschreitet. Dafür ist composedPath().includes() die robustere Lösung.
  • Kompatibilität: Chrome 53+, Firefox 52+, Safari 10+, Edge 79+. Die veraltete, nicht standardisierte Eigenschaft event.path (nur Chrome) wurde entfernt, stattdessen composedPath() verwenden.