Funktionsweise von JS: Shadow DOM-Technologie und Webkomponenten


In der Übersetzung von 17 Teilen der Materialien, die sich mit den Funktionen von allem befassen, was irgendwie mit JavaScript verbunden ist, werden wir heute über Webkomponenten und verschiedene Standards sprechen, die darauf abzielen, mit ihnen zu arbeiten. Besonderes Augenmerk wird auf die Shadow DOM-Technologie gelegt.



Rückblick


Webkomponenten sind eine Familie von APIs, mit denen neue DOM-Elemente beschrieben werden, die zur Wiederverwendung geeignet sind. Die Funktionalität solcher Elemente ist vom Rest des Codes getrennt, sie können in Webanwendungen unseres eigenen Designs verwendet werden.

Es gibt vier Technologien für Webkomponenten:

  • Schatten-DOM (Schatten-DOM)
  • HTML-Vorlagen (HTML-Vorlagen)
  • Benutzerdefinierte Elemente
  • HTML-Importe (HTML-Import)

In diesem Artikel werden wir über die Shadow DOM-Technologie sprechen, mit der komponentenbasierte Anwendungen erstellt werden können. Es bietet Möglichkeiten zur Lösung häufig auftretender Probleme bei der Webentwicklung, auf die Sie möglicherweise bereits gestoßen sind:

  • DOM-Isolation: Die Komponente verfügt über einen isolierten DOM-Baum (dies bedeutet, dass der Befehl document.querySelector() keinen Zugriff auf den Knoten im Schatten-DOM der Komponente ermöglicht). Darüber hinaus vereinfacht es das CSS-Auswahlsystem in Webanwendungen, da die DOM-Komponenten isoliert sind, sodass der Entwickler dieselben universellen Bezeichner und Klassennamen in verschiedenen Komponenten verwenden kann, ohne sich über mögliche Namenskonflikte Gedanken machen zu müssen.
  • CSS-Isolation: Die im Schatten-DOM beschriebenen CSS-Regeln sind darauf beschränkt. Diese Stile verlassen das Element nicht und werden nicht mit anderen Seitenstilen gemischt.
  • Zusammensetzung: Entwicklung einer deklarativen API für Markup-basierte Komponenten.

Shadow DOM-Technologie


Es wird davon ausgegangen, dass Sie bereits mit dem Konzept des DOM und den zugehörigen APIs vertraut sind. Wenn dies nicht der Fall ist, können Sie dieses Material lesen.

Das Shadow-DOM ist im Grunde dasselbe wie ein normales DOM, jedoch mit zwei Unterschieden:

  • Das erste ist, wie das Schatten-DOM erstellt und verwendet wird, insbesondere geht es um die Beziehung des Schatten-DOM zum Rest der Seite.
  • Das zweite ist das Verhalten des Schatten-DOM in Bezug auf die Seite.

Bei der Arbeit mit dem DOM werden DOM-Knoten erstellt, die als untergeordnete Elemente mit anderen Elementen der Seite verknüpft werden. Bei der Shadow-DOM-Technologie wird ein isolierter DOM-Baum erstellt, der das Element verbindet, jedoch von seinen normalen untergeordneten Elementen getrennt ist.

Dieser isolierte Teilbaum wird als Schattenbaum bezeichnet. Das Element, an das ein solcher Baum angehängt ist, wird als Schattenhost bezeichnet. Alles, was dem Schatten-DOM-Teilbaum hinzugefügt wird, ist lokal für das Element, an das es angehängt ist, einschließlich der mit <style> -Tags beschriebenen <style> . Auf diese Weise wird die CSS-Isolierung durch die Shadow DOM-Technologie bereitgestellt.

Erstellen eines Schatten-DOM


Die Schattenwurzel ist ein Teil des Dokuments, das an das Hostelement angehängt wird. Ein Element erhält ein Schatten-DOM, wenn ein Schattenwurzelelement daran angehängt ist. Um ein Schatten-DOM für ein bestimmtes Element zu erstellen, müssen Sie einen Befehl des Formulars element.attachShadow() :

 var header = document.createElement('header'); var shadowRoot = header.attachShadow({mode: 'open'}); shadowRoot.appendChild(document.createElement('<p> Shadow DOM </p>'); 

Es ist zu beachten, dass in der Shadow-DOM- Spezifikation eine Liste von Elementen enthalten ist, mit denen DOM-Shadow-Teilbäume nicht verbunden werden können.

Komposition im Schatten DOM


Die Komposition ist eine der wichtigsten Funktionen des Shadow DOM. Auf diese Weise können Webanwendungen erstellt werden, die beim Schreiben von HTML-Code verwendet werden. Während dieses Vorgangs kombiniert der Programmierer die verschiedenen Bausteine ​​(Elemente), aus denen die Seite besteht, und verschachtelt sie gegebenenfalls ineinander. Dies sind beispielsweise Elemente wie <div> , <header> , <form> und andere, die zum Erstellen von Webanwendungsschnittstellen verwendet werden, einschließlich solcher, die als Container für andere Elemente dienen.

Die Komposition bestimmt die Fähigkeit von Elementen wie <select> , <form> , <video> , andere HTML-Elemente als untergeordnete Elemente einzuschließen, und die Fähigkeit, das spezielle Verhalten solcher Strukturen zu organisieren, die aus verschiedenen Elementen bestehen.

Beispielsweise verfügt das <select> -Element über Mittel zum Rendern von <option> -Elementen in Form einer Dropdown-Liste mit dem vorbestimmten Inhalt der Elemente einer solchen Liste.

Betrachten Sie einige der Funktionen des Shadow DOM, die beim Erstellen von Elementen verwendet werden.

Licht dom


Light DOM ist das vom Benutzer Ihrer Komponente erstellte Markup. Dieses DOM befindet sich außerhalb des Schatten-DOM der Komponente und ist ein untergeordnetes Element der Komponente. Stellen Sie sich vor, Sie haben eine benutzerdefinierte Komponente mit dem Namen <better-button> , die die Funktionen des Standard-HTML-Elements <button> , und der Benutzer muss diesem neuen Element ein Bild und Text hinzufügen. So sieht es aus:

 <extended-button> <!--  img  span -  Light DOM  extended-button --> <img align="center" src="boot.png" slot="image"> <span>Launch</span> </extended-button> 

Das <extended-button> -Element ist eine benutzerdefinierte Komponente, die vom Programmierer selbst beschrieben wird, und der HTML-Code in dieser Komponente ist das Light DOM - was der Benutzer dieser Komponente hinzugefügt hat.

Das Schatten-DOM in diesem Beispiel ist die Komponente <extended-button> . Dies ist ein lokales Objektmodell einer Komponente, das ihre interne Struktur beschreibt, von der Außenwelt von CSS isoliert ist und die Implementierungsdetails der Komponente kapselt.

Abgeflachter Dom


Der abgeflachte DOM-Baum zeigt an, wie der Browser die Komponente auf dem Bildschirm anzeigt, wobei das Licht-DOM und das Schatten-DOM kombiniert werden. Es ist ein solcher DOM-Baum, der in den Entwicklertools angezeigt wird und auf der Seite angezeigt wird. Es könnte ungefähr so ​​aussehen:

 <extended-button> #shadow-root <style></style> <slot name="image">   <img align="center" src="boot.png" slot="image"> </slot> <span id="container">   <slot>     <span>Launch</span>   </slot> </span> </extended-button> 

Muster


Wenn Sie im HTML-Markup von Webseiten ständig dieselben Strukturen verwenden müssen, ist es hilfreich, eine bestimmte Vorlage zu verwenden, anstatt immer wieder denselben Code zu schreiben. Dies war früher möglich, aber jetzt wurde alles dank des HTML- <template> , das moderne Browser hervorragend unterstützt, erheblich vereinfacht. Dieses Element und sein Inhalt werden nicht im DOM angezeigt, aber Sie können mit JavaScript damit arbeiten. Betrachten Sie ein einfaches Beispiel:

 <template id="my-paragraph"> <p> Paragraph content. </p> </template> 

Wenn Sie dieses Design in das HTML-Markup der Seite aufnehmen, wird der Inhalt des von ihm beschriebenen <p> -Tags erst auf dem Bildschirm angezeigt, wenn es explizit an das DOM des Dokuments angehängt wird. Zum Beispiel könnte es so aussehen:

 var template = document.getElementById('my-paragraph'); var templateContent = template.content; document.body.appendChild(templateContent); 

Es gibt andere Möglichkeiten, um den gleichen Effekt zu erzielen, aber wie bereits erwähnt, sind Vorlagen ein sehr praktisches Standardwerkzeug, das eine gute Browserunterstützung bietet.


HTML-Browser-Unterstützung für moderne Browser

Vorlagen sind an und für sich nützlich, aber ihre Funktionen werden bei Verwendung mit benutzerdefinierten Elementen vollständig offengelegt. Benutzerdefinierte Elemente sind ein Thema für ein separates Material. customElement zu verstehen, was gerade passiert, muss berücksichtigt werden, dass der Programmierer mit der customElement Browser- customElement seine eigenen HTML-Tags beschreiben und angeben kann, wie die mit diesen Tags erstellten Elemente auf dem Bildschirm aussehen sollen.

Definieren Sie eine Webkomponente, die unsere Vorlage als Inhalt für ihr Schatten-DOM verwendet. Nennen Sie dieses neue Element <my-paragraph> :

 customElements.define('my-paragraph', class extends HTMLElement {  constructor() {    super();    let template = document.getElementById('my-paragraph');    let templateContent = template.content;    const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true)); } }); 

Das Wichtigste, worauf Sie achten sollten, ist, dass wir einen Klon des Vorlageninhalts, der mit der Node.cloneNode () -Methode erstellt wurde, an die Schattenwurzel angehängt haben .

Da wir den Inhalt der Vorlage an das Schatten-DOM anhängen, können wir einige Stilinformationen in die Vorlage im <style> -Element aufnehmen, die dann im Benutzerelement gekapselt werden. Dieses gesamte Schema funktioniert nicht wie erwartet, wenn Sie mit dem regulären DOM anstelle des Schatten-DOM arbeiten.

Beispielsweise kann eine Vorlage wie folgt geändert werden, indem Stilinformationen hinzugefügt werden:

 <template id="my-paragraph"> <style>   p {     color: white;     background-color: #666;     padding: 5px;   } </style> <p>Paragraph content. </p> </template> 

Jetzt kann das von uns beschriebene Benutzerelement auf normalen Webseiten wie folgt verwendet werden:

 <my-paragraph></my-paragraph> 

Slots


HTML-Vorlagen weisen mehrere Nachteile auf. Der Hauptnachteil besteht darin, dass die Vorlagen statische Markups enthalten, die es beispielsweise nicht ermöglichen, den Inhalt bestimmter Variablen mit ihrer Hilfe anzuzeigen, um mit ihnen auf die gleiche Weise wie mit Standard-HTML zu arbeiten Muster. Hier kommt das <slot> -Tag ins Spiel.

Slots können als Platzhalter wahrgenommen werden, mit denen Sie Ihren eigenen HTML-Code in die Vorlage aufnehmen können. Auf diese Weise können Sie universelle HTML-Vorlagen erstellen und diese dann anpassbar machen, indem Sie ihnen Slots hinzufügen.

Sehen Sie sich mit dem <slot> an, wie die obige Vorlage aussehen wird:

 <template id="my-paragraph"> <p>   <slot name="my-text">Default text</slot> </p> </template> 

Wenn der Inhalt des Slots nicht angegeben wird, wenn das Element im Markup enthalten ist, oder wenn der Browser die Arbeit mit Slots nicht unterstützt, enthält das Element <my-paragraph> nur den Standardinhalt des Default text .

Um den Inhalt des Slots festzulegen, müssen Sie HTML-Code mit dem slot Attribut in das Element <my-paragraph> einfügen, dessen Wert dem Namen des Slots entspricht, in dem Sie diesen Code platzieren möchten.

Nach wie vor kann es alles geben. Zum Beispiel:

 <my-paragraph> <span slot="my-text">Let's have some different text!</span> </my-paragraph> 

Elemente, die in Slots platziert werden können, werden als Slotable- Elemente bezeichnet.

Bitte beachten Sie, dass wir im vorherigen Beispiel das <span> -Element zum Steckplatz hinzugefügt haben, es ist das sogenannte Schlitzelement. Es hat ein slot Attribut, dem der Wert my-text zugewiesen ist, dh derselbe Wert, der im name Attribut des in der Vorlage beschriebenen Slots verwendet wird.

Nach der Verarbeitung des obigen Markups erstellt der Browser den folgenden abgeflachten DOM-Baum:

 <my-paragraph> #shadow-root <p>   <slot name="my-text">     <span slot="my-text">Let's have some different text!</span>   </slot> </p> </my-paragraph> 

Achten Sie auf das Element #shadow-root . Dies ist nur ein Indikator für die Existenz des Schatten-DOM.

Stilisierung


Komponenten, die die Shadow DOM-Technologie verwenden, können auf einer gemeinsamen Basis gestaltet werden, sie können ihre eigenen Stile definieren oder Hooks in Form von benutzerdefinierten CSS-Eigenschaften bereitstellen, mit denen Komponentenbenutzer die Standardstile überschreiben können.

▍ In Komponenten beschriebene Stile


Die CSS-Isolierung ist eines der bemerkenswertesten Merkmale der Shadow DOM-Technologie. Wir sprechen nämlich über Folgendes:

  • CSS-Selektoren der Seite, auf der die entsprechende Komponente platziert ist, haben keinen Einfluss darauf, was sie enthält.
  • Die in der Komponente beschriebenen Stile wirken sich nicht auf die Seite aus. Sie sind im Host-Element isoliert.

CSS-Selektoren, die im Schatten-DOM verwendet werden, gelten lokal für den Komponenteninhalt. In der Praxis bedeutet dies die Möglichkeit, dieselben Bezeichner und Klassennamen in verschiedenen Komponenten wiederzuverwenden, ohne sich um Namenskonflikte sorgen zu müssen. Einfache CSS-Selektoren bedeuten auch eine bessere Leistung für die Lösungen, in denen sie verwendet werden.

Schauen Sie sich das Element #shadow-root , das einige Stile definiert:

 #shadow-root <style> #container {   background: white; } #container-items {   display: inline-flex; } </style> <div id="container"></div> <div id="container-items"></div> 

Alle oben genannten Stile sind lokal für #shadow-root .

Darüber hinaus können Sie das <link> -Tag verwenden, um externe Stylesheets in #shadow-root . Solche Stile werden auch lokal sein.

▍Pseudoklasse: Host


Mit der Pseudoklasse :host können Sie auf ein Element zugreifen, das einen Schatten-DOM-Baum enthält, und dieses Element formatieren:

 <style> :host {   display: block; /*       display: inline */ } </style> 

Denken Sie bei der Verwendung der Pseudoklasse :host daran, dass die Regeln der übergeordneten Seite eine höhere Priorität haben als diejenigen, die im Element unter Verwendung dieser Pseudoklasse angegeben sind. Auf diese Weise können Benutzer die darin definierten Hostkomponentenstile von außen überschreiben. Darüber hinaus funktioniert die Pseudoklasse :host nur im Kontext des Schattenstammelements, Sie können sie jedoch nicht außerhalb des Schatten-DOM-Baums verwenden.

Mit der Funktionsform der Pseudoklasse: :host(<selector>) können Sie auf das Host-Element zugreifen, wenn es mit dem angegebenen <selector> -Element übereinstimmt. Auf diese Weise können Komponenten Verhalten kapseln, das auf Benutzeraktionen oder Änderungen im Status einer Komponente reagiert, und interne Knoten basierend auf der Hostkomponente formatieren:

 <style> :host {   opacity: 0.4; } :host(:hover) {   opacity: 1; } :host([disabled]) { /*      -  disabled. */   background: grey;   pointer-events: none;   opacity: 0.4; } :host(.pink) > #tabs {   color: pink; /*     #tabs   -  class="pink". */ } </style> 

▍Themen und Elemente mit einer Pseudoklasse: Host-Kontext (<Selektor>)


Die :host-context(<selector>) mit dem Host-Element :host-context(<selector>) , wenn es oder einer seiner Vorfahren mit dem angegebenen <selector> -Element übereinstimmt.

Ein häufiger Anwendungsfall für diese Funktion ist das Stylen von Elementen mit Themen. Beispielsweise werden Themen häufig verwendet, indem den Tags <html> oder <body> die entsprechende Klasse zugewiesen wird:

 <body class="lightheme"> <custom-container></custom-container> </body> 

Die :host-context(.lightheme) wird auf <fancy-tabs> angewendet, wenn dieses Element ein Nachkomme von .lightteme :

 :host-context(.lightheme) { color: black; background: white; } 

Das Konstrukt :host-context() kann nützlich sein, um Themen anzuwenden. Zu diesem Zweck ist es jedoch besser, Hooks mit benutzerdefinierten CSS-Eigenschaften zu verwenden .

▍ Gestalten Sie das Host-Element der Komponente von außen


Das Host-Element der Komponente kann extern mit dem Namen des Tags als Selektor gestaltet werden:

 custom-container { color: red; } 

Externe Stile haben Vorrang vor im Schatten-DOM definierten Stilen.
Angenommen, ein Benutzer erstellt den folgenden Selektor:

 custom-container { width: 500px; } 

Die in der Komponente selbst definierte Regel wird überschrieben:

 :host { width: 300px; } 

Mit diesem Ansatz können Sie nur die Komponente selbst stilisieren. Wie kann die interne Struktur einer Komponente stilisiert werden? Zu diesem Zweck werden benutzerdefinierte CSS-Eigenschaften verwendet.

▍Erstellen von Style-Hooks mithilfe benutzerdefinierter CSS-Eigenschaften


Benutzer können die Stile der internen Strukturen von Komponenten anpassen, wenn der Autor der Komponente ihnen mithilfe von benutzerdefinierten CSS-Eigenschaften Stil-Hooks zur Verfügung stellt.

Dieser Ansatz basiert auf einem ähnlichen Mechanismus wie bei der Arbeit mit <slot> -Tags, gilt jedoch in diesem Fall für Stile.

Betrachten Sie ein Beispiel:

 <!-- main page --> <style> custom-container {   margin-bottom: 60px;    - custom-container-bg: black; } </style> <custom-container background></custom-container> 

Folgendes befindet sich im Schatten-DOM-Baum:

 :host([background]) { background: var( - custom-container-bg, #CECECE); border-radius: 10px; padding: 10px; } 

In diesem Fall verwendet die Komponente Schwarz als Hintergrundfarbe, da der Benutzer dies angegeben hat. Andernfalls #CECECE die Hintergrundfarbe #CECECE .

Als Autor der Komponente sind Sie dafür verantwortlich, den Benutzern mitzuteilen, welche spezifischen CSS-Eigenschaften sie verwenden können. Betrachten Sie diesen Teil der offenen Oberfläche Ihrer Komponente.

JavaScript-API für die Arbeit mit Slots


Die Shadow DOM-API bietet die Möglichkeit, mit Slots zu arbeiten.

▍Event Slotchange


Das slotchange Ereignis slotchange , wenn sich die im Slot platzierten Knoten ändern. Wenn ein Benutzer beispielsweise untergeordnete Knoten im Light DOM hinzufügt oder entfernt:

 var slot = this.shadowRoot.querySelector('#some_slot'); slot.addEventListener('slotchange', function(e) { console.log('Light DOM change'); }); 

Um andere Arten von Änderungen im Light DOM zu verfolgen, können Sie MutationObserver im Konstruktor des Elements verwenden. Lesen Sie hier mehr darüber.

▍ Methode zugewiesenNodes ()


Die Methode assignedNodes() kann hilfreich sein, wenn Sie wissen müssen, welche Elemente dem Steckplatz zugeordnet sind. Durch Aufrufen der Methode slot.assignedNodes() können Sie genau herausfinden, welche Elemente vom Steckplatz angezeigt werden. Mit der Option {flatten: true} können Sie den Standardinhalt des Steckplatzes abrufen (angezeigt, wenn keine Knoten daran angeschlossen waren).

Betrachten Sie ein Beispiel:

 <slot name='slot1'><p>Default content</p></slot> 

Stellen Sie sich vor, dieser Steckplatz befindet sich in der Komponente <my-container> .

Werfen wir einen Blick auf die verschiedenen Verwendungszwecke dieser Komponente und darauf, was zurückgegeben wird, wenn die Methode assignedNodes() aufgerufen wird.

Im ersten Fall fügen wir dem Slot unseren eigenen Inhalt hinzu:

 <my-container> <span slot="slot1"> container text </span> </my-container> 

In diesem Fall gibt der Aufruf von assignedNodes() [ container text ] . Beachten Sie, dass dieser Wert ein Array von Knoten ist.

Im zweiten Fall füllen wir den Slot nicht mit unserem eigenen Inhalt:

 <my-container> </my-container> 

Der Aufruf von assignedNodes() gibt ein leeres Array zurück - [] .

Wenn Sie jedoch den Parameter {flatten: true} an diese Methode übergeben, gibt der Aufruf für dasselbe Element den Standardinhalt zurück: [ Default content ]

[ Default content ]

[ Default content ] .

Um auf ein Element innerhalb des Steckplatzes zuzugreifen, können Sie außerdem assignedNodes() aufrufen, um zu erfahren, welchem ​​Komponentensteckplatz Ihr Element zugewiesen ist.

Ereignismodell


Lassen Sie uns darüber sprechen, was passiert, wenn ein Ereignis im Schatten-DOM-Baum angezeigt wird. Der Zweck des Ereignisses wird unter Berücksichtigung der von der Shadow DOM-Technologie unterstützten Kapselung festgelegt. Wenn ein Ereignis umgeleitet wird, sieht es so aus, als stamme es von der Komponente selbst und nicht von ihrem internen Element, das sich im Schatten-DOM-Baum befindet und Teil dieser Komponente ist.

Hier ist eine Liste von Ereignissen, die vom DOM-Schattenbaum übergeben werden (dieses Verhalten ist für einige Ereignisse nicht charakteristisch):

  • focusin : blur , focus , focusin , focusout .
  • dblclick s: click , dblclick , mousedown , mouseenter , mousemove und andere.
  • Rad Ereignisse: wheel .
  • beforeinput : vor beforeinput , input .
  • Tastaturereignisse: keydown , keyup .
  • Kompositionsereignisse: compositionstart , compositionupdate , compositionend .
  • Drag Events: dragstart , drag , dragend , drop und so weiter.

Benutzerdefinierte Ereignisse


Benutzerereignisse verlassen standardmäßig nicht den DOM-Schattenbaum. Wenn Sie ein Ereignis auslösen möchten und möchten, dass es das Schatten-DOM verlässt, müssen Sie ihm die Parameter bubbles: true bereitstellen bubbles: true und composed: true . So sieht die Herausforderung einer ähnlichen Veranstaltung aus:

 var container = this.shadowRoot.querySelector('#container'); container.dispatchEvent(new Event('containerchanged', {bubbles: true, composed: true})); 

Unterstützung für Shadow DOM-Browser


Um herauszufinden, ob der Browser die Shadow DOM-Technologie unterstützt, können Sie das Vorhandensein von attachShadow :

 const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow; 

Hier finden Sie Informationen darüber, wie verschiedene Browser diese Technologie unterstützen.


Unterstützung für die Shadow DOM-Technologie in Browsern

Zusammenfassung


Der Schatten-DOM-Baum verhält sich nicht wie ein normaler DOM-Baum. Laut dem Autor dieses Materials drückt sich dies in der SessionStack- Bibliothek insbesondere in der Komplikation des Verfahrens zum Verfolgen von DOM-Änderungen aus, deren Informationen erforderlich sind, um zu reproduzieren, was mit der Seite passiert ist. MutationObserver verwendet, um Änderungen zu verfolgen. In diesem Fall löst der DOM-Schattenbaum das MutationObserver Ereignis nicht im globalen Bereich aus, was dazu führt, dass spezielle Ansätze für die Arbeit mit Komponenten verwendet werden müssen, die das Schatten-DOM verwenden.

, - Shadow DOM, , , , .

Liebe Leser! -, Shadow DOM?

Source: https://habr.com/ru/post/de415881/


All Articles