Signatur
Beschreibung
getComposedRanges() ist die Shadow-DOM-taugliche Alternative zu Selection.getRangeAt(). Wenn der Nutzer Text markiert, der teilweise oder ganz innerhalb eines Shadow Trees liegt, liefert getRangeAt() meist nur einen Bereich, der auf den Baum des Dokuments beschränkt ist. getComposedRanges() dagegen kann Start- und Endpunkte zurückgeben, die direkt in Knoten eines Shadow Trees liegen. Voraussetzung ist, dass Sie die betreffenden ShadowRoot-Objekte ausdrücklich übergeben.
Die übergebenen Shadow Roots wirken wie eine Freigabeliste. Liegt ein Endpunkt der Auswahl in einem Shadow Tree, der nicht in shadowRoots steht, verschiebt der Browser diesen Endpunkt nach oben auf den zugehörigen Shadow Host. So werden gekapselte, besonders geschlossene (mode: "closed") Shadow Trees nicht versehentlich offengelegt. Die Kapselung bleibt also erhalten: Nur wer die Shadow Root bereits kennt, bekommt Zugriff auf ihre inneren Knoten.
Der Rückgabewert besteht aus StaticRange-Objekten und nicht aus lebendigen Range-Objekten. Sie werden also nicht aktualisiert, wenn sich das DOM ändert, und haben keine Methoden wie cloneContents() oder getBoundingClientRect(). Sie bieten nur startContainer, startOffset, endContainer, endOffset und collapsed. Brauchen Sie mehr, erzeugen Sie aus diesen Werten mit document.createRange() einen echten Range.
Typische Einsatzfälle sind Rich-Text-Editoren, Werkzeuge zum Markieren und Kommentieren sowie Kontextmenüs innerhalb von Web Components, bei denen die markierte Stelle genau bekannt sein muss.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $options | object | {} | Optionales Einstellungsobjekt. |
| $options.shadowRoots | ShadowRoot[] | [] | Liste der ShadowRoot-Objekte, in deren Bäumen Endpunkte liegen dürfen. Endpunkte in nicht aufgeführten Shadow Trees werden auf den Shadow Host verschoben. |
Rückgabewert
StaticRange-Objekten. Gibt es keine Auswahl (rangeCount === 0), ist das Array leer. In heutigen Browsern enthält es höchstens einen Eintrag.Beispiele
Einfache Auswahl im Dokument auslesen
const selection = document.getSelection();
const ranges = selection.getComposedRanges();
if (ranges.length === 0) {
console.log('Nichts markiert');
} else {
const [range] = ranges;
console.log(range.startContainer.nodeName, range.startOffset);
console.log(range.endContainer.nodeName, range.endOffset);
console.log('Leer (collapsed)?', range.collapsed);
}
Auswahl innerhalb einer Web Component (Shadow DOM)
class NoteBox extends HTMLElement {
#root = this.attachShadow({ mode: 'closed' });
constructor() {
super();
this.#root.innerHTML = '<p>Markiere diesen Text im Shadow DOM.</p>';
document.addEventListener('selectionchange', () => this.#report());
}
#report() {
const sel = document.getSelection();
// Nur mit übergebener Shadow Root liegen die Endpunkte im Shadow Tree
const [range] = sel.getComposedRanges({ shadowRoots: [this.#root] });
if (!range || range.collapsed) return;
const inside = this.#root.contains(range.startContainer);
console.log('Start im Shadow Tree:', inside);
}
}
customElements.define('note-box', NoteBox);
document.body.append(document.createElement('note-box'));
Vergleich: ohne shadowRoots wird auf den Host verschoben
const host = document.querySelector('note-box');
const sel = document.getSelection();
const [scoped] = sel.getComposedRanges();
// Endpunkte liegen jetzt beim Elternknoten des Hosts
console.log(scoped.startContainer === host.parentNode);
StaticRange in echten Range umwandeln und markierten Text lesen
const toLiveRange = (staticRange) => {
const range = document.createRange();
range.setStart(staticRange.startContainer, staticRange.startOffset);
range.setEnd(staticRange.endContainer, staticRange.endOffset);
return range;
};
const shadowRoot = document.querySelector('#editor').shadowRoot;
const [composed] = document.getSelection().getComposedRanges({ shadowRoots: [shadowRoot] });
if (composed) {
const live = toLiveRange(composed);
console.log('Markiert:', live.toString());
console.log('Position:', live.getBoundingClientRect().top);
}
// Wichtig · Fallstricke
- Signatur hat sich geändert: Ältere Umsetzungen, etwa Safari 17, erwarten die Shadow Roots als einzelne Argumente:
getComposedRanges(root1, root2). Die aktuelle Spezifikation verlangt dagegen ein Objekt:getComposedRanges({ shadowRoots: [root1] }). Wer beide Formen unterstützen muss, sollte das Verhalten prüfen und bei Bedarf auf die alte Form ausweichen. - Browser-Unterstützung: Die Methode ist noch recht neu. Prüfen Sie vorher mit
typeof Selection.prototype.getComposedRanges === 'function', ob sie vorhanden ist. Andernfalls greifen Sie aufgetRangeAt(0)zurück. - Ohne
shadowRootskeine Shadow-Knoten: Wer die Shadow Roots nicht übergibt, bekommt Endpunkte auf dem Shadow Host bzw. dessen Elternknoten. Das ist gewollt und schützt die Kapselung, wird aber oft für einen Fehler gehalten. - Statisch, nicht lebendig:
StaticRangewird bei DOM-Änderungen nicht angepasst und kann danach auf ungültige Offsets zeigen. Werten Sie ihn also sofort aus oder wandeln Sie ihn in einenRangeum. - Richtung: Wie bei
getRangeAt()steht der Start immer vor dem Ende im Dokument. Ob der Nutzer rückwärts markiert hat, verrätSelection.direction. - Gibt es keine Auswahl, kommt ein leeres Array zurück. Es wird keine Ausnahme ausgelöst. Greifen Sie deshalb nicht ungeprüft auf
ranges[0]zu.