Uma raiz de sombra é uma subárvore DOM encapsulada anexada a um elemento host: os estilos dentro dela não vazam, e os estilos de página não vazam (exceto as propriedades herdadas). Os modelos constroem a árvore de forma eficiente, e os slots projetam o conteúdo do Light DOM nela.
Como anexar uma raiz paralela
this.attachShadow({ mode: 'open', delegatesFocus: true });
delegatesFocus: true faz com que o foco no host seja movido para o primeiro elemento focalizável dentro da árvore de sombra, encaminha o estilo :focus para o host e, em elementos personalizados associados a formulários (static formAssociated = true), encaminha cliques em <label> para o controle interno. Defina-o sempre que o componente envolver controles focalizáveis. Use mode: 'open' em quase todos os casos para que as ferramentas para desenvolvedores, os scripts de página e a hidratação do servidor possam acessar element.shadowRoot (consulte Orientação sobre componentes da Web).
Modelos: construa a árvore de forma eficiente
Defina um <template> uma vez (escopo do módulo ou um campo estático) e clone-o por instância. Clonar um fragmento analisado é muito mais rápido do que analisar novamente uma string innerHTML em cada construção.
const template = document.createElement('template');
template.innerHTML = `
<div class="card">
<slot name="header"></slot>
<div class="body"><slot></slot></div>
<slot name="footer"></slot>
</div>
`;
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// MANDATORY: deep-clone the template content so each instance gets its own nodes.
this.shadowRoot.append(template.content.cloneNode(true));
}
}
customElements.define('my-card', MyCard);
Mantenha os modelos simples. A "sopa de divs" profundamente aninhada e muitos níveis de raízes de sombra aninhadas adicionam um custo mensurável de resolução de estilo e layout, um grande impacto no desempenho em escala. Prefira uma estrutura simples e elementos semânticos.
Slots: as partes que surpreendem as pessoas
Os slots padrão e nomeados funcionam conforme documentado. Estes são os comportamentos que podem causar problemas:
- Os espaços em branco contam. Os nós de texto entre elementos (incluindo espaços em branco puros) são atribuídos ao slot padrão, então um slot padrão raramente está realmente "vazio".
- A ordem do DOM é preservada, não a ordem dos slots. Os nós projetados mantêm a ordem original do Light DOM para fins de
:first-child/:last-child, independentemente do slot que preenchem. Uma regra::slotted(*:first-child)segmenta o primeiro filho do DOM, não o primeiro filho desse slot. Por isso, muitas vezes ela não corresponde ao que você espera. slotchangeé o indicador de tempo de upgrade. Ele é acionado quando os nós atribuídos mudam (incluindo a atribuição inicial) e é a maneira confiável de saber que os filhos do Light DOM chegaram após um upgrade, quando talvez não existissem duranteconnectedCallback.
this.shadowRoot.querySelector('slot[name="header"]')
.addEventListener('slotchange', (e) => {
const nodes = e.target.assignedElements();
this.toggleAttribute('has-header', nodes.length > 0);
});
Como carregar folhas de estilo em uma raiz de sombra
| Método | Prós | Contras |
|---|---|---|
<style> no modelo |
Simples, totalmente encapsulado, funciona sem JS. | Duplicado por instância, o que aumenta o DOM em escala. |
<link rel="stylesheet"> |
Arquivo externo que pode ser armazenado em cache. | Solicitação extra; corre o risco de um flash de conteúdo sem estilo. |
adoptedStyleSheets |
Um CSSStyleSheet compartilhado em todas as instâncias; mais rápido em escala. |
Requer JS. As páginas construídas não são compatíveis com @import. |
Para muitas instâncias de um componente, prefira uma folha de estilo construída compartilhada:
const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: block; }`);
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// The same sheet object is reused by every instance; no per-instance parse.
this.shadowRoot.adoptedStyleSheets = [sheet];
}
}