Como o JS funciona: tecnologia Shadow DOM e componentes da Web


Hoje, na tradução de 17 partes dos materiais dedicados aos recursos de tudo que está de alguma forma conectado ao JavaScript, falaremos sobre componentes da Web e vários padrões que visam trabalhar com eles. Atenção especial será dada à tecnologia Shadow DOM.



Revisão


Os componentes da Web são uma família de APIs projetadas para descrever novos elementos DOM adequados para reutilização. A funcionalidade desses elementos é separada do restante do código; eles podem ser usados ​​em aplicativos da web de nosso próprio design.

Existem quatro tecnologias relacionadas aos componentes da web:

  • DOM da sombra (DOM da sombra)
  • Modelos HTML (Modelos HTML)
  • Elementos personalizados
  • Importações HTML (Importação HTML)

Neste artigo, falaremos sobre a tecnologia Shadow DOM, projetada para criar aplicativos baseados em componentes. Ele oferece maneiras de resolver problemas comuns de desenvolvimento da web que você já pode ter encontrado:

  • Isolamento do DOM: o componente possui uma árvore DOM isolada (isso significa que o comando document.querySelector() não permitirá acesso ao nó no DOM sombra do componente). Além disso, simplifica o sistema seletor de CSS em aplicativos da Web, uma vez que os componentes do DOM são isolados, o que permite ao desenvolvedor usar os mesmos identificadores universais e nomes de classes em componentes diferentes sem se preocupar com possíveis conflitos de nomes.
  • Isolamento CSS: as regras CSS descritas dentro do DOM sombra são limitadas a ele. Esses estilos não deixam o elemento, eles não se misturam com outros estilos de página.
  • Composição: desenvolvendo uma API declarativa para componentes baseados em marcação.

Tecnologia Shadow DOM


Pressupõe que você já esteja familiarizado com o conceito do DOM e as APIs associadas. Caso contrário, você pode ler este material.

O DOM Shadow é basicamente o mesmo que um DOM normal, mas com duas diferenças:

  • A primeira é como o Shadow DOM é criado e usado, em particular, é sobre o relacionamento do Shadow DOM com o restante da página.
  • O segundo é o comportamento do DOM da sombra em relação à página.

Ao trabalhar com o DOM, são criados nós DOM que se associam, como filhos, a outros elementos da página. No caso da tecnologia Shadow DOM, é criada uma árvore DOM isolada que une o elemento, mas é separada de seus elementos filhos normais.

Essa subárvore isolada é chamada de árvore das sombras. O elemento ao qual essa árvore está anexada é chamado host de sombra. Tudo o que é adicionado à subárvore DOM da sombra acaba sendo local para o elemento ao qual está anexado, incluindo os estilos descritos usando as tags <style> . É assim que o isolamento CSS é fornecido por meio da tecnologia Shadow DOM.

Criando um DOM Shadow


A raiz da sombra é uma parte do documento anexada ao elemento host. Um elemento adquire um DOM de sombra quando um elemento raiz de sombra é anexado a ele. Para criar um DOM de sombra para um determinado elemento, você precisa usar um comando do formulário element.attachShadow() :

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

Deve-se notar que, na especificação Shadow DOM, há uma lista de elementos aos quais as subárvores DOM shadow não podem ser conectadas.

Composição no Shadow DOM


A composição é um dos recursos mais importantes do Shadow DOM, é uma maneira de criar aplicativos da web, usados ​​no processo de escrever código HTML. Durante esse processo, o programador combina os vários blocos de construção (elementos) que compõem a página, aninhando-os, se necessário, um no outro. Por exemplo, são elementos como <div> , <header> , <form> e outros usados ​​para criar interfaces de aplicativos da web, incluindo aqueles que atuam como contêineres para outros elementos.

A composição determina os recursos de elementos, como <select> , <form> , <video> , para incluir outros elementos HTML como filhos e a capacidade de organizar o comportamento especial de tais estruturas que consistem em diferentes elementos.

Por exemplo, o elemento <select> possui meios para renderizar elementos <option> na forma de uma lista suspensa com o conteúdo predeterminado dos elementos dessa lista.

Considere alguns dos recursos do Shadow DOM usados ​​na composição de elementos.

Dom luz


Light DOM é a marcação criada pelo usuário do seu componente. Este DOM está fora do DOM de sombra do componente e é filho do componente. Imagine que você criou um componente personalizado chamado <better-button> que estende os recursos do elemento HTML <button> padrão, e o usuário precisa adicionar uma imagem e algum texto a esse novo elemento. Aqui está o que parece:

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

O elemento <extended-button> é um componente personalizado descrito pelo programador por conta própria, e o código HTML dentro deste componente é seu Light DOM - o que o usuário deste componente adicionou a ele.

O DOM de sombra neste exemplo é o componente <extended-button> . Este é um modelo de objeto local de um componente que descreve sua estrutura interna, isolada do mundo externo do CSS, e encapsula os detalhes de implementação do componente.

Dom achatado


A árvore DOM achatada representa como o navegador exibe o componente na tela, combinando o Light DOM e o Shadow DOM. É uma árvore DOM que pode ser vista nas ferramentas do desenvolvedor e é exibida na página. Pode ser algo como isto:

 <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> 

Padrões


Se você precisar usar constantemente as mesmas estruturas na marcação HTML das páginas da Web, será útil usar um determinado modelo em vez de escrever o mesmo código repetidamente. Isso já era possível antes, mas agora tudo foi bastante simplificado graças à aparência da <template> HTML <template> , que possui excelente suporte para navegadores modernos. Este elemento e seu conteúdo não são exibidos no DOM, mas você pode trabalhar com ele no JavaScript. Considere um exemplo simples:

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

Se você incluir esse design na marcação HTML da página, o conteúdo da tag <p> descrita por ela não aparecerá na tela até que seja explicitamente anexado ao DOM do documento. Por exemplo, pode ser assim:

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

Existem outros meios para obter o mesmo efeito, mas, como já mencionado, os modelos são uma ferramenta padrão muito conveniente que possui um bom suporte ao navegador.


Suporte de navegador HTML para navegadores modernos

Os modelos são úteis por si só, mas seus recursos são totalmente divulgados quando usados ​​com elementos personalizados. elementos personalizados - este é um assunto para um artigo separado, mas agora, para a compreensão do que está acontecendo, é suficiente para levar em conta o fato de que o navegador API customElement permite que o programador para descrever suas próprias HTML-tags e definir como os elementos são criados com estas tags será exibido na tela.

Defina um componente da web que usa nosso modelo como conteúdo para seu DOM sombra. Chame esse novo elemento <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)); } }); 

O mais importante a se prestar atenção é que anexamos um clone do conteúdo do modelo criado usando o método Node.cloneNode () à raiz da sombra.

Como anexamos o conteúdo do modelo ao DOM da sombra, podemos incluir algumas informações de estilo no modelo no elemento <style> , que serão encapsuladas no elemento user. Todo esse esquema não funcionará conforme o esperado se você trabalhar com o DOM normal, em vez do DOM da sombra.

Por exemplo, um modelo pode ser modificado da seguinte maneira, incluindo informações de estilo:

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

Agora, o elemento de usuário descrito por nós pode ser usado em páginas da Web comuns da seguinte maneira:

 <my-paragraph></my-paragraph> 

Slots


Os modelos HTML têm várias desvantagens, a principal é que os modelos contêm marcação estática, o que não permite, por exemplo, exibir o conteúdo de determinadas variáveis ​​com sua ajuda para trabalhar com elas da mesma maneira que trabalham com HTML padrão padrões. É aqui que a tag <slot> entra.

Os slots podem ser percebidos como espaços reservados que permitem incluir seu próprio código HTML no modelo. Isso permite criar modelos HTML universais e torná-los personalizáveis ​​adicionando slots a eles.

Veja como o modelo acima será exibido usando a <slot> :

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

Se o conteúdo do slot não for especificado quando o elemento estiver incluído na marcação, ou se o navegador não suportar o trabalho com slots, o elemento <my-paragraph> incluirá apenas o conteúdo Default text .

Para definir o conteúdo do slot, é necessário incluir o código HTML com o atributo slot no elemento <my-paragraph> , cujo valor é equivalente ao nome do slot no qual você deseja colocar esse código.

Como antes, pode haver qualquer coisa. Por exemplo:

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

Os elementos que podem ser colocados nos slots são chamados de elementos Slotable .

Observe que no exemplo anterior, adicionamos o elemento <span> ao slot, que é o chamado elemento com fenda. Ele tem o atributo de slot , que é definido my-text , que é - o mesmo valor, que é usado no atributo name ranhura conforme descrito no modelo.

Após o processamento da marcação acima, o navegador criará a seguinte árvore DOM achatada:

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

Preste atenção ao elemento #shadow-root . Este é apenas um indicador da existência do DOM da sombra.

Estilização


Os componentes que usam a tecnologia Shadow DOM podem ser estilizados de maneira comum, podem definir seus próprios estilos ou fornecer ganchos na forma de propriedades CSS personalizadas que permitem que os usuários de componentes substituam os estilos padrão.

Described Estilos descritos nos componentes


O isolamento de CSS é um dos recursos mais notáveis ​​da tecnologia Shadow DOM. Ou seja, estamos falando sobre o seguinte:

  • Os seletores de CSS da página na qual o componente correspondente é colocado não afetam o que ele contém.
  • Os estilos descritos no componente não afetam a página. Eles são isolados no elemento host.

Os seletores de CSS usados ​​no DOM sombra aplicam-se localmente ao conteúdo do componente. Na prática, isso significa a capacidade de reutilizar os mesmos identificadores e nomes de classes em diferentes componentes e não é necessário se preocupar com conflitos de nomes. Seletores CSS simples também significam melhor desempenho para as soluções em que são usados.

Dê uma olhada no elemento #shadow-root , que define alguns estilos:

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

Todos os estilos acima são locais para #shadow-root .

Além disso, você pode usar a tag <link> para incluir folhas de estilo externas em #shadow-root . Tais estilos também serão locais.

SePseudoclasse: host


A pseudo :host permite acessar um elemento que contém uma árvore DOM sombra e estilizar esse elemento:

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

Usando a pseudo :host , lembre-se de que as regras da página pai têm prioridade mais alta do que as especificadas no elemento usando essa pseudo classe. Isso permite que os usuários substituam os estilos de componentes do host definidos nele de fora. Além disso, a pseudo- :host funciona apenas no contexto do elemento raiz da sombra; você não pode usá-lo fora da árvore DOM da sombra.

A forma funcional da pseudo-classe: :host(<selector>) permite acessar o elemento host se ele corresponder ao elemento <selector> especificado. Essa é uma ótima maneira de permitir que os componentes encapsulem o comportamento que responde às ações do usuário ou alterações no estado de um componente e permite que você estilize nós internos com base no componente host:

 <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> 

▍ Temas e elementos com uma pseudo-classe: contexto do host (<seletor>)


A pseudo-classe :host-context(<selector>) ) correspondente ao elemento de host, se ele ou qualquer de seus antepassados coincidir com o elemento de dado <selector> .

Um caso de uso comum para esse recurso é estilizar elementos com temas. Por exemplo, os temas são frequentemente usados ​​atribuindo a classe apropriada às tags <html> ou <body> :

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

A pseudo- :host-context(.lightheme) será aplicada a <fancy-tabs> se esse elemento for descendente de .lightteme :

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

A construção :host-context() pode ser útil para aplicar temas, mas para esse propósito, é melhor usar ganchos usando propriedades CSS personalizadas .

▍ Modelando o elemento host do componente de fora


O elemento host do componente pode ser estilizado externamente usando o nome de sua tag como um seletor:

 custom-container { color: red; } 

Os estilos externos têm precedência sobre os estilos definidos no DOM da sombra.
Suponha que um usuário crie o seguinte seletor:

 custom-container { width: 500px; } 

Ele substituirá a regra definida no próprio componente:

 :host { width: 300px; } 

Usando essa abordagem, você pode estilizar apenas o componente em si. Como estilizar a estrutura interna de um componente? Propriedades CSS personalizadas são usadas para esse fim.

Criando ganchos de estilo usando propriedades CSS personalizadas


Os usuários podem personalizar os estilos das estruturas internas dos componentes se o autor do componente fornecer ganchos de estilo usando propriedades CSS personalizadas .

Essa abordagem é baseada em um mecanismo semelhante ao usado ao trabalhar com tags <slot> , mas, nesse caso, se aplica a estilos.

Considere um exemplo:

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

Aqui está o que está dentro da árvore DOM da sombra:

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

Nesse caso, o componente usa preto como cor de fundo, pois foi o usuário que o especificou. Caso contrário, a cor de fundo será #CECECE .

Como autor do componente, você é responsável por informar aos usuários quais propriedades CSS específicas eles podem usar. Considere esta parte da interface aberta do seu componente.

API JavaScript para trabalhar com slots


A API Shadow DOM fornece a capacidade de trabalhar com slots.

▍Evento de troca de slot


O evento slotchange gerado quando os nós colocados no slot são alterados. Por exemplo, se um usuário adicionar ou remover nós filhos no Light DOM:

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

Para rastrear outros tipos de alterações no Light DOM, você pode usar MutationObserver no construtor do elemento. Leia mais sobre isso aqui .

▍ Método assignNodes ()


O método assignedNodes() pode ser útil se você precisar saber quais elementos estão associados ao slot. Chamar o método slot.assignedNodes() permite descobrir exatamente quais elementos são exibidos pelo slot. O uso da opção {flatten: true} permite obter o conteúdo padrão do slot (exibido se nenhum nó estiver anexado a ele).

Considere um exemplo:

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

Imagine que esse slot esteja localizado no componente <my-container> .

Vejamos os vários usos do componente e que será emitido chamada de método assignedNodes() .

No primeiro caso, adicionamos nosso próprio conteúdo ao espaço:

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

Nesse caso, a chamada assignedNodes() retornará [ container text ] . Observe que esse valor é uma matriz de nós.

No segundo caso, não preenchemos o espaço com nosso próprio conteúdo:

 <my-container> </my-container> 

A chamada assignedNodes() retornará uma matriz vazia - [] .

Se, no entanto, você passar o parâmetro {flatten: true} para esse método, chamá-lo para o mesmo elemento retornará seu conteúdo padrão: [ Default content ]

[ Default content ]

[ Default content ]

Além disso, para acessar um elemento dentro do slot, você pode chamar assignedNodes() para informar a qual slot de componente seu elemento está atribuído.

Modelo de evento


Vamos falar sobre o que acontece quando um evento que aparece na sombra da árvore DOM aparece. O objetivo do evento é definido levando em consideração o encapsulamento suportado pela tecnologia Shadow DOM. Quando um evento é redirecionado, parece que ele vem do próprio componente, e não de seu elemento interno, localizado na árvore DOM da sombra e que faz parte desse componente.

Aqui está uma lista de eventos que são transmitidos da árvore de sombra DOM (esse comportamento não é característico de alguns eventos):

  • Eventos foco (eventos de foco Ler): blur , focus , focusin , focusout .
  • Eventos do mouse s: click , dblclick , mousedown , mouseenter , mousemove e outros.
  • Eventos da wheel : wheel .
  • Eventos de entrada: beforeinput input , input .
  • Eventos do teclado: keydown , keyup .
  • Eventos de compositionstart : compositionstart , compositionupdate , compositionend .
  • Arrastar eventos: drag , drag , drag , drop e assim por diante.

Eventos personalizados


Os eventos do usuário por padrão não deixam a árvore de sombra do DOM. Se você deseja acionar um evento e deseja que ele saia do Shadow DOM, é necessário fornecer os parâmetros bubbles: true e composed: true . É assim que a chamada de um evento como esse se parece:

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

Suporte para navegadores Shadow DOM


Para descobrir se o navegador suporta a tecnologia Shadow DOM, você pode verificar a presença de attachShadow :

 const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow; 

Aqui estão as informações sobre como vários navegadores suportam essa tecnologia.


Suporte à tecnologia Shadow DOM nos navegadores

Sumário


A árvore DOM de sombra não se comporta como uma árvore DOM normal. Em particular, de acordo com o autor deste material, na biblioteca SessionStack isso é expresso na complicação do procedimento para rastrear alterações do DOM, informações sobre as quais são necessárias para reproduzir o que aconteceu com a página. Ou seja, MutationObserver usado para rastrear alterações. Nesse caso, a árvore de sombra do DOM não gera o evento MutationObserver no escopo global, o que leva à necessidade de usar abordagens especiais para trabalhar com componentes que usam o DOM da sombra.

, - Shadow DOM, , , , .

Caros leitores! -, Shadow DOM?

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


All Articles