Table Des Matières
Shadow DOM: styles rapides et encapsulés
Shadow DOM supprime la fragilité de la création d’applications Web. La fragilité vient de la nature globale de HTML, CSS et JS. Au cours des années, nous avons inventé un nombre exorbitant d’outils pour contourner les problèmes. Par exemple, lorsque vous utilisez un nouvel id/class HTML, rien ne dit s’il entrera en conflit avec un nom existant utilisé par la page. des bugs subtils rampent, la spécificité CSS devient un problème énorme (!important all the things!), les sélecteurs de style deviennent hors de contrôle et les performances peuvent être affectées. La liste continue.
Shadow DOM corrige les CSS et les DOM. Il introduit des styles de portée sur la plate-forme Web. Sans outils ou conventions de dénomination, vous pouvez regrouper CSS avec balisage, masquer les détails d’implémentation et créer des composants autonomes dans “vanilla JavaScript”
Introduction
Remarque: Vous connaissez déjà Shadow DOM? Cet article décrit la nouvelle spécification Shadow DOM v1. Si vous utilisez le Shadow DOM, il est probable que vous connaissiez la version v0 qui a été livrée avec Chrome 35 et les webcomponents.js polyfills. Les concepts sont les mêmes, mais la spécification v1 a des différences importantes de l’API. C’est aussi la version que tous les principaux navigateurs ont accepté de mettre en œuvre, avec des mises en œuvre déjà dans Safari Tech Preview et Chrome Canary. Continuez à lire pour voir les nouveautés.
Shadow DOM est l’une des quatre normes Web Component: les modèles HTML, les Shadow DOM, les éléments personnalisés et les importations HTML.
Vous n’avez pas à autoriser les composants Web (web components) qui utilisent shadow DOM. Mais lorsque vous le faites, vous profitez de ses avantages (CSS scoping, DOM encapsulation, composition) et construire des éléments personnalisés, résilients, hautement configurables et extrêmement réutilisables. Si vous pouvez créer un nouveau html grâce aux éléments personnalisés (avec une API JS), shadow DOM est la manière dont vous fournissez son HTML et son CSS. Les deux API se combinent pour créer un composant avec HTML, CSS et JavaScript compatibles.
Shadow DOM est conçu tel un outil pour créer des applications basées sur des composants. Par conséquent, il apporte des solutions pour les problèmes communs en terme de développement web:
DOM isolé: le DOM d’un composant est autonome (par exemple, document.querySelector () ne renverra pas les noeuds dans le shadow DOM des composants).
CSS partagé: CSS défini à l’intérieur de shadow DOM est étendu à celui-ci. Les règles de style ne fuient pas et les styles de page ne saignent pas.
Composition: Concevez une API déclarative et basée sur les balises pour votre composant.
Simplifie CSS – DOM partagé signifie que vous pouvez utiliser des sélecteurs CSS simples, des noms d’id/class plus génériques et pas de risque des conflits liés au nommage.
Productivité – Pensez aux applications en morceaux de DOM plutôt qu’une grande page (globale).
Remarque: Bien que vous puissiez utiliser l’API shadow DOM et ses avantages en dehors des composants Web, je me concentrerai seulement sur des exemples basés sur des éléments personnalisés. J’utiliserai les éléments personnalisés v1 API dans tous les exemples.
Démo Des <fancy-tabs> Onglets Fantaisie
Tout au long de cet article, je vais me référer à un composant de démonstration <fancy-tabs> (Onglets Fantaisie) et à référencer des extraits de code de celui-ci. Si votre navigateur prend en charge les API, vous devriez voir une démonstration en direct juste ci-dessous. Sinon, consultez la source complète juste au dessous du démo
Si votre navigateur ne prend pas en charge les API, vous pouvez visualiser le code source complet du démo par içi
Qu’est Ce Que Shadow Dom?
Informations Générales Sur DOM
HTML alimente le web car il est facile à manipuler. En déclarant quelques balises, vous pouvez autoriser une page en quelques secondes qui expose à la fois la présentation et la structure. Cependant, par lui-même, HTML n’est pas si utile. Il est facile pour les humains de comprendre un langage textuel, mais les machines ont besoin de quelque chose de plus. C’est là qu’intervient le Document Object Model, ou DOM.
Lorsque le navigateur charge une page Web, il fait beaucoup de choses intéressantes. Une des choses qu’il fasse est de transformer le HTML de l’auteur en un document direct. Généralement, pour comprendre la structure de la page, le navigateur analyse HTML (chaînes statiques de texte) dans un modèle de données (objets / nœuds). Le navigateur conserve la hiérarchie HTML en créant un arbre de ces nœuds: le DOM. La bonne chose à propos de DOM est que c’est une représentation direct de votre page. Contrairement au code HTML statique, les nœuds produits par le navigateur contiennent des propriétés, des méthodes et le meilleur de tous, peuvent être manipulés par des programmes! C’est pourquoi nous sommes en mesure de créer des éléments DOM directement en utilisant le JavaScript:
const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello world!';
header.appendChild(h1);
document.body.appendChild(header);
Produit le balisage HTML suivant:
<body>
<header>
<h1>Hello DOM</h1>
</header>
</body>
Tout cela semble bien et bon. Ok alors, qu’est-ce que le shadow DOM?
DOM … dans le Shadow
Shadow DOM est juste un DOM normal avec deux différences:
- comment il est créé / utilisé.
- comment il se comporte par rapport au reste de la page.
Normalement, vous créez des nœuds DOM et les ajouter comme enfants d’un autre élément. Avec shadow DOM, vous créez un arbre DOM scopé qui est attaché à l’élément, Mais séparé de ses enfants réels. Ce sous-arbre est appelé arbre de shadow. L’élément auquel elle est attachés est son hôte shadow. Tout ce que vous ajoutez dans les shadow DOM devient local à l’élément d’hébergement, y compris <style>. C’est ainsi que shadow DOM atteint la portée de style CSS.
Comment Créer Le Shadow DOM?
Une racine shadow est un fragment de document qui s’attache à un élément “hôte”. L’attachement d’une racine shadow est la façon dont l’élément gagne son shadow DOM. Pour créer un shadow DOM pour un élément, appelez element.attachShadow ():
const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().
// header.shadowRoot === shadowRoot
// shadowRoot.host === header
J’utilise .innerHTML pour remplir la racine shadow, mais vous pouvez également utiliser d’autres API de DOM. C’est le Web. Vous avez le choix.
La spécification définit une liste d’éléments qui ne peuvent pas héberger un arbre shadow. Il existe plusieurs raisons pour lesquelles un élément peut se trouver sur la liste:
- Le navigateur héberge déjà son propre shadow interne DOM pour l’élément (<textarea>, <input>).
- Ça n’a pas de sens que l’élément héberge un shadow DOM (<img>).
Par exemple, cela ne fonctionne pas:
document.createElement('input').attachShadow({mode: 'open'});
// Error. `<input>` cannot host shadow dom.
Créer Un Shadow DOM Pour Un Élément Personnalisé
Shadow DOM est particulièrement utile lors de la création d’éléments personnalisés. Utilisez shadow DOM pour compartimenter les éléments HTML, CSS et JS, produisant ainsi un “composant Web”.
Exemple: un élément personnalisé attache shadow DOM à lui-même, encapsulant son DOM / CSS:
// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
constructor() {
super(); // always call super() first in the ctor.
// Attach a shadow root to <fancy-tabs>.
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
<div id="tabs">...</div>
<div id="panels">...</div>
`;
}
...
});
Il y a quelques choses intéressantes qui se passent ici. Premièrement, l’élément personnalisé crée son propre shadow DOM lorsqu’une instance de est créée. C’est fait dans le constructeur (). Deuxièmement, parce que nous créons une racine shadow, les règles CSS dans
Remarque: Lorsque vous essayez d’exécuter cet exemple, vous remarquerez probablement que rien ne se produit. Le balisage de l’utilisateur semble disparaître! C’est parce que le shadow DOM de l’élément est rendu au lieu de son enfant. Si vous souhaitez afficher l’enfants, vous devez indiquer au navigateur où le rendre en plaçant un élément <slot> dans votre shadow DOM.
La Composition & L’Élément Slot
La composition est l’une des caractéristiques les moins comprises de shadow DOM, mais c’est certainement la plus importante.
Dans le monde du développement web, la composition est la façon dont nous construisons les applications, déclarativement hors HTML. Les différents blocs de construction (<div> s, <header> s, <form> s, <input> s) se conjuguent pour former des applications. Certaines de ces balises fonctionnent même les unes avec les autres. La composition est la cause de la fléxibilité des éléments natifs comme <select>, <details>, <form> et <video>. Chacune de ces balises accepte certains HTML en tant qu’enfant et fait quelque chose de spécial avec eux. Par exemple, <select> sait comment rendre <option> et <optgroup> dans les widgets déroulants et multi-sélection. L’élément <details> rend <summary> comme une flèche extensible. Même <video> sait comment traiter certains enfants: les éléments <source> ne sont pas rendus, mais ils affectent le comportement de la vidéo. Quelle magie!
Terminologie: Light DOM vs. Shadow DOM
Shadow DOM introduit un ensemble de nouveaux fondamentaux dans le développement web. Avant d’entrer dans les mauvaises herbes, laissez-moi standardiser une certaine terminologie, pour qu’on puisse parler le même jargon.
Light DOM
C’est le langage de balisage (markup ) que l’utilisateur de votre composant écrit. Ce DOM vit en dehors du composant du shadow DOM. C’est l’enfant réel de l’élément.
<better-button>
<!-- the image and span are better-button's light DOM -->
<img src="gear.svg" slot="icon">
<span>Settings</span>
</better-button>
Shadow DOM
C’est le DOM que l’auteur du composant écrit. Pour le composant, shadow DOM est local, il définit sa structure interne, scope les CSS et encapsule vos détails d’implémentation. Il peut également définir comment rendre le balisage qui est rédigé par le consommateur de votre composant.
#shadow-root
<style>...</style>
<slot name="icon"></slot>
<span id="wrapper">
<slot>Button</slot>
</span>
Arbre Du “Flattened DOM”
Le résultat du navigateur distribuant le light DOM léger de l’utilisateur dans votre shadow DOM, rendant le produit final. L’arbre aplati est ce que vous voyez finalement dans les DevTools et ce qui est aussi rendu sur la page.
<better-button>
#shadow-root
<style>...</style>
<slot name="icon">
<img src="gear.svg" slot="icon">
</slot>
<span id="wrapper">
<slot>
<span>Settings</span>
</slot>
</span>
</better-button>
L’élément <slot>
Shadow DOM compose différents arbre DOM ensembles en utilisant l’élément <slot>. Les slots sont des espaces réservés à l’intérieur de votre composant que les utilisateurs peuvent remplir avec leur propre balisage. En définissant un ou plusieurs emplacements, vous invitez le balisage externe à rendre dans le shadow DOM de votre composant. Essentiellement, vous dites “Rendre le balisage de l’utilisateur ici”.
Remarque: Les slots sont un moyen de créer une “API déclarative” pour un composant Web. Ils mélangent les DOMs de l’utilisateur pour aider à rendre le composant global, donc, en composant différents arbres shadow DOM ensembles.
Les éléments sont autorisés à “traverser” la frontière du shadow DOM lorsqu’un <slot> les invite. Ces éléments sont appelés noeuds distribués. Conceptuellement, les noeuds distribués peuvent sembler un peu bizarres. Les slots ne déplacent pas physiquement les DOMs; Ils le rendent à un autre endroit à l’intérieur du shadow DOM.
Un composant peut définir zéro ou plusieurs slots dans son shadow DOM. Les slots peuvent être vides ou fournir un emplacement pour nouveau contenu. Si l’utilisateur ne fournit pas de contenu léger de DOM, le slot rend son contenu de reserves (de secours par exemple).
<!-- Default slot. If there's more than one default slot, the first is used. -->
<slot></slot>
<slot>fallback content</slot> <!-- default slot with fallback content -->
<slot> <!-- default slot entire DOM tree as fallback -->
<h2>Title</h2>
<summary>Description text</summary>
</slot>
Vous pouvez également créer des emplacements nommés. Les emplacements nommés sont des trous spécifiques dans votre shadow DOM que les utilisateurs référencent par leur nom.
#shadow-root
<div id="tabs">
<slot id="tabsSlot" name="title"></slot> <!-- named slot -->
</div>
<div id="panels">
<slot id="panelsSlot"></slot>
</div>
Les utilisateurs du composant déclarent <fancy-tabs> comme ceci:
<fancy-tabs>
<button slot="title">Title</button>
<button slot="title" selected>Title 2</button>
<button slot="title">Title 3</button>
<section>content panel 1</section>
<section>content panel 2</section>
<section>content panel 3</section>
</fancy-tabs>
<!-- Using <h2>'s and changing the ordering would also work! -->
<fancy-tabs>
<h2 slot="title">Title</h2>
<section>content panel 1</section>
<h2 slot="title" selected>Title 2</h2>
<section>content panel 2</section>
<h2 slot="title">Title 3</h2>
<section>content panel 3</section>
</fancy-tabs>
Et si vous vous demandez, l’arbre Flattened DOM ressemble à quelque chose comme ceci:
<fancy-tabs>
#shadow-root
<div id="tabs">
<slot id="tabsSlot" name="title">
<button slot="title">Title</button>
<button slot="title" selected>Title 2</button>
<button slot="title">Title 3</button>
</slot>
</div>
<div id="panels">
<slot id="panelsSlot">
<section>content panel 1</section>
<section>content panel 2</section>
<section>content panel 3</section>
</slot>
</div>
</fancy-tabs>
Notez que notre composant est capable de gérer différentes configurations, mais l’arbre flattened DOM est la même. Nous pouvons également passer de <bouton> à <h2>. Ce composant a été conçu pour gérer différents types d’enfants … tout comme le fait <select>.
Style
Il existe de nombreuses options pour styliser les composants Web. Un composant qui utilise shadow DOM peut être stylisé par la page principale, définir ses propres styles ou fournir des crochets (sous la forme de propriétés personnalisées CSS) pour que les utilisateurs remplacent les valeurs par défaut.
Styles définis par un composant
La fonctionnalité la plus utile de shadow DOM est CSS scopé:
- Les sélecteurs CSS de la page externe ne s’appliquent pas à l’intérieur de votre composant.
- Les styles définis à l’intérieur ne saignent pas. Ils sont scopés vers l’élément hôte.
Les sélecteurs CSS utilisés à l’intérieur de Shadow DOM s’appliquent localement à votre composant. Dans la pratique, cela signifie que nous pouvons utiliser à nouveau les noms d’ID / classe, sans se soucier des conflits ailleurs sur la page. Les sélecteurs CSS plus simples sont une pratique exemplaire dans Shadow DOM. Ils sont également bons pour la performance.
Exemple – les styles définis dans une shadow racine sont locaux
#shadow-root
<style>
#panels {
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
background: white;
...
}
#tabs {
display: inline-flex;
...
}
</style>
<div id="tabs">
...
</div>
<div id="panels">
...
</div>
Les feuilles de style sont également regroupées dans l’arbre des shadows:
#shadow-root
<!-- Available in Chrome 54+ -->
<!-- WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=160683 -->
<link rel="stylesheet" href="styles.css">
<div id="tabs">
...
</div>
<div id="panels">
...
</div>
Vous vous êtes déjà demandé comment l’élément <select> rend un widget multi-sélection (au lieu d’un menu déroulant) lorsque vous ajoutez l’attribut multiple:
See the Pen qXyoZj by Med Mus (@MedMus) on CodePen.
<Select> est capable de se styliser différemment en fonction des attributs que vous déclarez sur lui. Les composants Web peuvent également se styliser en utilisant :host selector.
Exemple – un composant se stylise lui-même
<style>
:host {
display: block; /* by default, custom elements are display: inline */
contain: content; /* CSS containment FTW. */
}
</style>
Avec :host, les règles dans la page parent ont une spécificité plus élevée que: les règles de :host définies dans l’élément. C’est-à-dire que les styles extérieurs gagnent. Cela permet aux utilisateurs de remplacer votre style de haut niveau de l’extérieur. En plus, :host fonctionne uniquement dans le contexte d’une racine shadow, de sorte que vous ne pouvez pas l’utiliser en dehors du Shadow DOM.
La forme fonctionnelle de :host(<selector>) vous permet de cibler l’hôte s’il correspond au <sélecteur>. C’est un bon moyen pour votre composant d’encapsuler les comportements qui réagissent à l’interaction de l’utilisateur ou à l’état ou au style des nœuds internes en fonction de l’hôte.
<style>
:host {
opacity: 0.4;
will-change: opacity;
transition: opacity 300ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host([disabled]) { /* style when host has disabled attribute. */
background: grey;
pointer-events: none;
opacity: 0.4;
}
:host(.blue) {
color: blue; /* color host when it has class="blue" */
}
:host(.pink) > #tabs {
color: pink; /* color internal #tabs node when host has class="pink". */
}
</style>
Style basé sur le contexte
:host-context(<selector>) Correspond au composant si celui -ci ou l’un de ses ancêtres correspond à <selector>. Un usage courant comme cela est un thème basé sur l’environnement d’un composant. Par exemple, beaucoup de gens créent des themes en appliquant class à <html> ou <body>:
<body class="darktheme">
<fancy-tabs>
...
</fancy-tabs>
</body>
:host-context (.darktheme)Style <fancy-tabs> quand c’est un descendant de .darktheme:
:host-context(.darktheme) {
color: white;
background: black;
}
:host-context () peut être utile pour les thèmes, mais il existe une approche meilleure qui consiste à créer des crochets de style à l’aide des propriétés personnalisées CSS.
Style Des Nœuds Distribués
::slotted(<compound-selector>) Correspond aux noeuds qui sont distribués dans un <slot>.
Disons que nous avons créé un composant de badge nommé:
<name-badge>
<h2>Eric Bidelman</h2>
<span class="title">
Digital Jedi, <span class="company">Google</span>
</span>
</name-badge>
Shadow DOM peut styliser <h2> et .title:
<style>
::slotted(h2) {
margin: 0;
font-weight: 300;
color: red;
}
::slotted(.title) {
color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
text-transform: uppercase;
}
*/
</style>
<slot></slot>
Si vous vous rappelez, <slot> ne déplace pas light DOM de l’utilisateur. Lorsque les noeuds sont distribués dans un <slot>, le <slot> rend leur DOM mais les noeuds restent physiquement. Les styles appliqués avant la distribution continueront à s’appliquer après la distribution. Toutefois, lorsque light DOM est distribué, il peut prendre des styles supplémentaires (définis par le shadow DOM).
Un autre exemple plus approfondi de <fancy-tabs>:
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
#panels {
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
background: white;
border-radius: 3px;
padding: 16px;
height: 250px;
overflow: auto;
}
#tabs {
display: inline-flex;
-webkit-user-select: none;
user-select: none;
}
#tabsSlot::slotted(*) {
font: 400 16px/22px 'Roboto';
padding: 16px 8px;
...
}
#tabsSlot::slotted([aria-selected="true"]) {
font-weight: 600;
background: white;
box-shadow: none;
}
#panelsSlot::slotted([aria-hidden="true"]) {
display: none;
}
</style>
<div id="tabs">
<slot id="tabsSlot" name="title"></slot>
</div>
<div id="panels">
<slot id="panelsSlot"></slot>
</div>
`;
Dans cet exemple, il existe deux slots: un slot nommé pour les titres des onglets et un slot pour le contenu du panneau d’onglets. Lorsque l’utilisateur sélectionne un onglet, nous sommes attentifs à leur sélection et révélons son panneau. Cela se fait en sélectionnant les nœuds distribués qui ont l’attribut sélectionné. Le JS de l’élément personnalisé (non illustré ici) ajoute cet attribut au bon moment.
Styliser Un Composant De L’Extérieur
Il existe plusieurs façons de styliser un composant de l’extérieur. La manière la plus simple est d’utiliser le nom de la balise comme sélecteur:
fancy-tabs {
width: 500px;
color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
box-shadow: 0 3px 3px #ccc;
}
Les styles extérieurs gagnent toujours les styles définis dans les shadow DOM. Par exemple, si l’utilisateur écrit fancy-tabs du sélecteur {largeur: 500px; }, Il atténuera la règle du composant :: host {width: 650px;}.
Styliser le composant lui même ne fait que vous éloigner. Mais que se passe-t-il si vous voulez styliser les éléments internes d’un composant? Pour cela, nous avons besoin de propriétés personnalisées CSS.
Créer des crochets de style à l’aide des propriétés personnalisées CSS
Les utilisateurs peuvent modifier les styles internes si l’auteur du composant fournit des crochets de style à l’aide de propriétés personnalisées CSS. Conceptuellement, l’idée est similaire à <slot>. Vous créez des “espaces réservés de style” pour que les utilisateurs les remplacent.
Exemple – <fancy-tabs> permet aux utilisateurs de remplacer la couleur d’arrière-plan:
<!-- main page -->
<style>
fancy-tabs {
margin-bottom: 32px;
--fancy-tabs-bg: black;
}
</style>
<fancy-tabs background>...</fancy-tabs>
À l’intérieur de son shadow DOM:
:host([background]) {
background: var(--fancy-tabs-bg, #9E9E9E);
border-radius: 10px;
padding: 10px;
}
Dans ce cas, le composant utilisera le noir comme valeur de fond puisque l’utilisateur l’a fourni. Sinon, il serait par défaut # 9E9E9E.
Remarque: En tant qu’auteur du composant, vous êtes responsable de permettre aux développeurs de connaître les propriétés personnalisées CSS qu’ils peuvent utiliser. Considérez la partie de l’interface publique de votre composant. Assurez-vous de documenter les crochets de style!
Sujets Avancés
Créer Des Racines Shadow fermées (à éviter)
Il existe un autre goût de shadow DOM appelée “fermé”. Lorsque vous créez un arbre shadow fermé, le JavaScript externe ne pourra pas accéder au DOM interne de votre composant. Cela ressemble à la façon dont les éléments natifs comme <vidéo> fonctionnent. JavaScript ne peut pas accéder au shadow DOM de <video> car le navigateur l’implémente en utilisant une racine shadow en mode fermé.
Exemple – créer un arbre shadow fermé:
const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'closed'}); // close shadow tree
// div.shadowRoot === null
// shadowRoot.host === div
D’autres API sont également affectées par le mode fermé:
- Element.assignedSlot / TextNode.assignedSlot renvoie null
- Event.composedPath() Pour les événements associés aux éléments dans l’ombre DOM, renvoie []
Remarque: Les racines shadow fermées ne sont pas très utiles. Certains développeurs verront le mode fermé comme une fonction de sécurité artificielle. Mais soyons clairs, ce n’est pas une fonctionnalité de sécurité. Le mode fermé empêche simplement JS extérieurs de se forger dans le DOM interne d’un élément.
Voici mon résumé de pourquoi vous ne devriez jamais créer des composants Web avec {mode: ‘closed’}:
1-Sens artificiel de la sécurité. Rien n’empêche un attaquant de pirater Element.prototype.attachShadow
.
2-Le mode fermé empêche votre code d’élément personnalisé d’accéder à son propre shadow DOM. C’est complètement échoué. Au lieu de cela, vous devrez déposer une référence pour plus tard si vous souhaitez utiliser des objets comme querySelector (). Cela détruit complètement le but initial du mode fermé!
customElements.define('x-element', class extends HTMLElement {
constructor() {
super(); // always call super() first in the ctor.
this._shadowRoot = this.attachShadow({mode: 'closed'});
this._shadowRoot.innerHTML = '<div class="wrapper"></div>';
}
connectedCallback() {
// When creating closed shadow trees, you'll need to stash the shadow root
// for later if you want to use it again. Kinda pointless.
const wrapper = this._shadowRoot.querySelector('.wrapper');
}
...
});
3-Le mode fermé rend votre composant moins flexible pour les utilisateurs finaux. Au fur et à mesure que vous construisez des composants Web, vous aurez l’opportunité d’ajouter une fonctionnalité. Une option de configuration. Un cas d’utilisation que l’utilisateur souhaite. Un exemple commun, c’est oublie d’inclure des crochets de style adéquats pour les noeuds internes. Avec le mode fermé, il n’y a aucun moyen pour les utilisateurs d’annuler les valeurs par défaut et de modifier les styles. Être capable d’accéder aux composants internes du composant est super utile. En fin de compte, les utilisateurs vont fourcher votre composant, trouver un autre, ou créer leur propre si cela ne fait pas ce qu’ils souhaitaient 🙁
Travailler Avec Les Slots Dans JS
L’API shadow DOM fournit des utilitaires pour travailler avec des slots et les nœuds distribués. Ceux-ci sont utiles lorsque vous créez un élément personnalisé.
Événement slotchange
L’élément slotchange event se déclenche lorsque les nœuds distribués d’un slot changent. Par exemple, si l’utilisateur ajoute / supprime les enfants du light DOM.
const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
console.log('light dom children changed!');
});
Remarque: Slotchangene déclenche pas lorsqu’une instance du composant est initialisée.
Pour controler d’autres types de modifications sur les light DOMs, vous pouvez configurer MutationObserver dans le constructeur de votre élément.
Quels Sont Les Éléments Rendu Dans Un Slot?
Parfois, il est utile de savoir quels éléments sont associés à un slot (emplacement). appelles slot.assignedNodes () pour trouver quels éléments le slot est en train de rendre. L’option {flatten: true} renverra également le contenu de repli (fallback content) d’un slot (si aucun noeud n’est distribué).
Exemple, disons que votre DOM d’ombre ressemble à ceci:
Usage | appel | Resultat |
---|---|---|
<my-component>component text</my-component> | slot. | [component text] |
<my-component></my-component> | slot. | [] |
<my-component></my-component> | slot. | [<b>fallback content</b>] |
Dans quel emplacement est affecté un élément?
Répondre à la question inverse est également possible. Element.assignedSlot vous indique lequel des emplacements des composants auxquels votre élément est affecté.
Le Modèle Des Événements De Shadow DOM
Lorsqu’un événement se dégage du shadow DOM, sa cible est ajustée pour maintenir l’encapsulation que le shadow DOM fournit. C’est-à-dire que les événements sont re-ciblés pour qu’ils ressemblent à ceux issus du composant plutôt qu’aux éléments internes de votre shadow DOM. Certains événements ne propagent même pas les shadow DOMs.
Les événements qui traversent la limite du shadow sont:
- Événements de concentration (Focus Events): flou, focus (concentration), focusin (se concentrer), focusout (mettre au point).
- Événements de la souris (Mouse Events): cliquez, dblclick, mousedown, mouseenter, mousemove, etc.
- Événements de la roue (Wheel Events): roue (Wheel).
- Événements d’entrée (Input Events): avant entrée (beforeinput), entrée (input).
- Événements clavier (Keyboard Events): keydown, keyup.
- Événements de composition: compositionstart, composition update, compositionend.
- Événements (DragEvent): dragstart, glisser, glisser, déposer, etc.
Conseil : Si l’arbre des shadows est ouverte, appeler event.composedPath () renverra un ensemble de nœuds sur lesquels l’événement a parcouru.
Utilisation des événements personnalisés
Les événements DOM personnalisés qui sont déclenchés sur des noeuds internes dans un arbre de shadow ne font pas exploser la limite du shadow à moins que l’événement ne soit créé en utilisant true flag:
// Inside <fancy-tab> custom element class definition:
selectTab() {
const tabs = this.shadowRoot.querySelector('#tabs');
tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
}
Si composé: false (par défaut), les consommateurs ne pourront pas écouter l’événement en dehors de votre racine ombre.
<fancy-tabs></fancy-tabs>
<script>
const tabs = document.querySelector('fancy-tabs');
tabs.addEventListener('tab-select', e => {
// won't fire if `tab-select` wasn't created with `composed: true`.
});
</script>
Manipuler L’Événement Focus
Si vous vous souvenez du modèle d’événement de shadow DOM, les événements qui sont déclenchés à l’intérieur de shadow DOM sont ajustés pour qu’ils ressemblent à ceux issus de l’élément d’hébergement. Par exemple, disons que vous cliquez sur <input> dans une racine shadow:
<x-focus>
#shadow-root
<input type="text" placeholder="Input inside shadow dom">
L’événement focus semblera être venu de <x-focus>, et non de <input>. De même, document.activeElement sera <x-focus>. Si la racine du shadow a été créée avec le mode: ‘ouvrir’ (voir mode fermé), vous pourrez également accéder au noeud interne qui a obtenu focus:
document.activeElement.shadowRoot.activeElement // only works with open mode.
S’il y a plusieurs niveaux de shadow DOM en jeu (par exemple, un élément personnalisé dans un autre élément personnalisé), vous devez effectuer une exploration récurrente dans les racines du shadow pour trouver ActiveElement:
function deepActiveElement() {
let a = document.activeElement;
while (a && a.shadowRoot && a.shadowRoot.activeElement) {
a = a.shadowRoot.activeElement;
}
return a;
}
Une autre option pour la mise au point est l’option delegatesFocus: true, qui élargit le comportement de la mise au point des éléments dans une arbre du shadow:
- Si vous cliquez sur un noeud à l’intérieur du shadow DOM et que le nœud n’est pas une zone focalisable, la première zone focalisable devient concentrée.
- Lorsqu’un noeud à l’intérieur du shadow DOM gagne du focus, :focus s’applique à l’hôte (host) en plus de l’élément ciblé.
Exemple – comment déléguésFocus: true change le comportement de focus
<style>
:focus {
outline: 2px solid red;
}
</style>
<x-focus></x-focus>
<script>
customElements.define('x-focus', class extends HTMLElement {
constructor() {
super(); // always call super() first in the ctor.
const root = this.attachShadow({mode: 'open', delegatesFocus: true});
root.innerHTML = `
<style>
:host {
display: flex;
border: 1px dotted black;
padding: 16px;
}
:focus {
outline: 2px solid blue;
}
</style>
<div>Clickable Shadow DOM text</div>
<input type="text" placeholder="Input inside shadow dom">`;
// Know the focused element inside shadow DOM:
this.addEventListener('focus', function(e) {
console.log('Active element (inside shadow dom):',
this.shadowRoot.activeElement);
});
}
});
</script>
Compatibilité
Chrome 53 (état), Opera 40 et Safari 10 supportent shadow DOM v1. Edge est à l’étude avec priorité élevée. Mozilla a un bug ouvert à mettre en œuvre.
Pour détecter shadow DOM, vérifiez l’existence de attachShadow:
const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;
Polyfill
Jusqu’à ce que le support du navigateur soit largement disponible, les polyfills shadydom et shadycss vous offrent une fonctionnalité v1. Shady DOM imite la portée de DOM de Shadow DOM et les propriétés personnalisées CSS de shadycss et la portée du style que l’API native fournisse.
Installer les polyfills:
bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss
Utiliser les polyfills:
function loadScript(src) {
return new Promise(function(resolve, reject) {
const script = document.createElement('script');
script.async = true;
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// Lazy load the polyfill if necessary.
if (!supportsShadowDOMV1) {
loadScript('/bower_components/shadydom/shadydom.min.js')
.then(e => loadScript('/bower_components/shadycss/shadycss.min.js'))
.then(e => {
// Polyfills loaded.
});
} else {
// Native shadow dom v1 support. Go to go!
}
Conclusion
Pour la première fois, nous avons une primitive API qui utilise la portée de CSS, la portée de DOM et possède une vraie composition. Combiné avec d’autres API de composants Web comme des éléments personnalisés, shadow DOM offre un moyen d’autoriser des composants véritablement encapsulés sans hacks ou bagages plus anciens comme les <iframe> s.
Shadow DOM est certainement une bête complexe! Mais c’est une bête qui vaut la peine d’être appris. Passez du temps avec elle. Apprenez-le.
Source Originale en Anglais par Eric Bidelman