CSS e estilo
Este artigo aborda mais coisas incríveis que você pode fazer com o Shadow DOM. Ele se baseia nos conceitos discutidos em Noções básicas do Shadow DOM. Se você quiser uma introdução, consulte esse artigo.
Introdução
Vamos ser honestos. Não há nada de interessante em uma marcação sem estilo. Para nossa sorte, as pessoas brilhantes por trás dos componentes da Web previram isso e não nos deixaram na mão. O módulo de escopo do CSS define muitas opções para estilizar o conteúdo em uma árvore de sombra.
Encapsulamento de estilo
Um dos principais recursos do Shadow DOM é o limite de sombra. Ele tem muitas propriedades interessantes, mas uma das melhores é que ele oferece encapsulamento de estilo sem custo financeiro. Em outras palavras:
<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
`;
</script>
Há duas observações interessantes sobre essa demonstração:
- Há outros h3s nesta página, mas o único que corresponde ao seletor h3 e, portanto, tem o estilo vermelho, é o que está no ShadowRoot. Novamente, os estilos têm escopo por padrão.
- Outras regras de estilo definidas nesta página que segmentam h3s não se misturam ao meu conteúdo. Isso ocorre porque os seletores não cruzam o limite de sombra.
Moral da história? Temos encapsulamento de estilo do mundo externo. Agradecemos ao DOM paralelo.
Estilizar o elemento host
O :host permite selecionar e estilizar o elemento que hospeda uma árvore de sombra:
<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
<style>
:host {
text-transform: uppercase;
}
</style>
<content></content>
`;
</script>
Uma pegadinha é que as regras na página mãe têm maior especificidade do que as regras :host
definidas no elemento, mas menor especificidade do que um atributo style
definido no elemento host. Isso permite que os usuários substituam seu estilo de fora.
:host também só funciona no contexto de um ShadowRoot, então não é possível usá-lo fora do Shadow DOM.
A forma funcional de :host(<selector>) permite segmentar o elemento host se ele corresponder a um <selector>.
Exemplo: corresponder somente se o próprio elemento tiver a classe .different (por exemplo, <x-foo class="different"></x-foo>):
:host(.different) {
...
}
Como reagir aos estados do usuário
Um caso de uso comum para :host é quando você está criando um elemento personalizado e quer reagir a diferentes estados do usuário (:hover, :focus, :active etc.).
<style>
:host {
opacity: 0.4;
transition: opacity 420ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host(:active) {
position: relative;
top: 3px;
left: 3px;
}
</style>
Como aplicar um tema a um elemento
A pseudoclasse :host-context(<selector>) vai corresponder ao elemento host se ele ou qualquer um dos ancestrais dele corresponder a <selector>.
Um uso comum de :host-context() é para definir o tema de um elemento com base no que está ao redor dele. Por exemplo,
muitas pessoas aplicam temas usando uma classe em <html> ou <body>:
<body class="different">
<x-foo></x-foo>
</body>
Você pode usar :host-context(.different) para estilizar <x-foo> quando ele for descendente de um elemento com a classe .different:
:host-context(.different) {
color: red;
}
Isso permite encapsular regras de estilo no Shadow DOM de um elemento que o estiliza de forma exclusiva, com base no contexto.
Suporte a vários tipos de host em uma raiz de sombra
Outro uso para :host é se você estiver criando uma biblioteca de temas e quiser
oferecer suporte à estilização de vários tipos de elementos host no mesmo Shadow DOM.
:host(x-foo) {
/* Applies if the host is a <x-foo> element.*/
}
:host(x-foo:host) {
/* Same as above. Applies if the host is a <x-foo> element. */
}
:host(div) {
/* Applies if the host element is a <div>. */
}
Estilização de elementos internos do Shadow DOM de fora
O pseudoelemento ::shadow e o combinador /deep/ são como ter uma espada Vorpal de autoridade CSS.
Eles permitem atravessar o limite do Shadow DOM para estilizar elementos dentro de árvores de sombra.
O pseudoelemento ::shadow
Se um elemento tiver pelo menos uma árvore de sombra, o pseudoelemento ::shadow vai corresponder à própria raiz de sombra.
Ele permite que você escreva seletores que estilizam nós internos para o DOM de sombra de um elemento.
Por exemplo, se um elemento estiver hospedando uma raiz de sombra, você pode escrever #host::shadow span {} para estilizar todos os spans na árvore de sombra.
<style>
#host::shadow span {
color: red;
}
</style>
<div id="host">
<span>Light DOM</span>
</div>
<script>
var host = document.querySelector('div');
var root = host.createShadowRoot();
root.innerHTML = `
<span>Shadow DOM</span>
<content></content>
`;
</script>
Exemplo (elementos personalizados): <x-tabs> tem filhos <x-panel> no Shadow DOM. Cada painel hospeda a própria árvore de sombra que contém títulos h2. Para estilizar esses títulos na página principal, é possível escrever:
x-tabs::shadow x-panel::shadow h2 {
...
}
O combinador /deep/
O combinador /deep/ é semelhante a ::shadow, mas é mais eficiente. Ele ignora completamente todos os limites de sombra e cruza qualquer número de árvores de sombra. Em termos simples, o /deep/ permite que você analise os detalhes de um elemento e segmente qualquer nó.
O combinador /deep/ é útil principalmente no mundo dos elementos personalizados, em que é comum ter vários níveis de Shadow DOM. Os principais exemplos são o aninhamento de vários elementos personalizados (cada um hospedando a própria árvore de sombra) ou a criação de um elemento que herda de outro usando <shadow>.
Exemplo (elementos personalizados): selecione todos os elementos <x-panel> que são descendentes de <x-tabs>, em qualquer lugar da árvore:
x-tabs /deep/ x-panel {
...
}
Exemplo: estilizar todos os elementos com a classe .library-theme em qualquer lugar de uma árvore de sombra:
body /deep/ .library-theme {
...
}
Como trabalhar com querySelector()
Assim como .shadowRoot abre
árvores de sombra para travessia do DOM, os combinadores abrem árvores de sombra para travessia de seletor.
Em vez de escrever uma cadeia aninhada de loucura, você pode escrever uma única instrução:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
Estilizar elementos nativos
Os controles HTML nativos são um desafio para o estilo. Muitas pessoas simplesmente desistem
e criam as próprias soluções. No entanto, com ::shadow e /deep/, qualquer elemento na plataforma da Web que
use o Shadow DOM pode ser estilizado. Bons exemplos são os tipos <input> e <video>:
video /deep/ input[type="range"] {
background: hotpink;
}
Como criar ganchos de estilo
A personalização é boa. Em alguns casos, talvez você queira abrir furos no escudo de estilo do Shadow e criar ganchos para que outras pessoas possam definir o estilo.
Como usar ::shadow e /deep/
Há muito poder por trás de /deep/. Ele permite que os autores de componentes designem
elementos individuais como estilizados ou um conjunto de elementos como temáticos.
Exemplo: estilizar todos os elementos que têm a classe .library-theme, ignorando todas as árvores de sombra:
body /deep/ .library-theme {
...
}
Como usar pseudoelementos personalizados
O WebKit e o
Firefox definem pseudoelementos para estilizar partes internas de elementos nativos do navegador. Um bom exemplo
é o input[type=range]. É possível estilizar o controle deslizante <span style="color:blue">blue</span> segmentando ::-webkit-slider-thumb:
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
Assim como os navegadores fornecem ganchos de estilo para alguns elementos internos, os autores de conteúdo do Shadow DOM podem designar certos elementos como estilizados por usuários externos. Isso é feito com pseudoelementos personalizados.
É possível designar um elemento como um pseudoelemento personalizado usando o atributo pseudo.
O valor ou nome dele precisa ter o prefixo "x-". Isso cria
uma associação com esse elemento na árvore de sombra e dá aos usuários externos uma
faixa designada para cruzar o limite de sombra.
Veja um exemplo de como criar um widget de controle deslizante personalizado e permitir que alguém estilize o botão azul:
<style>
#host::x-slider-thumb {
background-color: blue;
}
</style>
<div id="host"></div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<div>
<div pseudo="x-slider-thumb"></div>' +
</div>
`;
</script>
Como usar variáveis CSS
Uma maneira eficiente de criar hooks de temas é usando variáveis CSS. Basicamente, criar "marcadores de posição de estilo" para outros usuários preencherem.
Imagine um autor de elemento personalizado que marca marcadores de posição de variáveis no Shadow DOM. Um para definir o estilo da fonte de um botão interno e outro para a cor:
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
Em seguida, o incorporador do elemento define esses valores como quiser. Talvez para combinar com o tema superlegal da Comic Sans da própria página:
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
Devido à forma como as variáveis CSS são herdadas, tudo funciona perfeitamente. O panorama completo é assim:
<style>
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
</style>
<div id="host">Host node</div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<style>
button {
color: var(--button-text-color, pink);
font-family: var(--button-font);
}
</style>
<content></content>
`;
</script>
Como redefinir estilos
Estilos herdáveis, como fontes, cores e alturas de linha, continuam afetando elementos
no Shadow DOM. No entanto, para máxima flexibilidade, o Shadow DOM nos dá a propriedade resetStyleInheritance para controlar o que acontece no limite da sombra.
Pense nisso como uma maneira de começar do zero ao criar um novo componente.
resetStyleInheritance
false: padrão. As propriedades CSS herdáveis continuam sendo herdadas.true: redefine as propriedades herdáveis comoinitialno limite.
Confira abaixo uma demonstração que mostra como a árvore de sombra é afetada pela mudança de resetStyleInheritance:
<div>
<h3>Light DOM</h3>
</div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
`;
</script>
<div class="demoarea" style="width:225px;">
<div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
<button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>
<script>
var container = document.querySelector('#style-ex-inheritance');
var root = container.createShadowRoot();
//root.resetStyleInheritance = false;
root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';
document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
root.resetStyleInheritance = !root.resetStyleInheritance;
e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
});
</script>
Entender .resetStyleInheritance é um pouco mais difícil, principalmente porque ele
só afeta as propriedades CSS que são herdáveis. Ela diz: quando
você estiver procurando uma propriedade para herdar, no limite entre a página e
o ShadowRoot, não herde valores do host, mas use o valor
initial (de acordo com a especificação do CSS).
Se você não tiver certeza de quais propriedades são herdadas no CSS, confira esta lista útil ou marque a caixa de seleção "Mostrar herdadas" no painel "Elemento".
Como estilizar nós distribuídos
Os nós distribuídos são elementos renderizados em um ponto de inserção (um elemento <content>). O elemento <content> permite selecionar nós do Light DOM e renderizá-los em locais predefinidos no Shadow DOM. Eles não estão logicamente no Shadow DOM, mas ainda são filhos do elemento host. Os pontos de inserção são apenas um elemento de renderização.
Os nós distribuídos mantêm os estilos do documento principal. Ou seja, as regras de estilo da página principal continuam sendo aplicadas aos elementos, mesmo quando eles são renderizados em um ponto de inserção. Novamente, os nós distribuídos ainda estão logicamente no DOM leve e não se movem. Eles só são renderizados em outro lugar. No entanto, quando os nós são distribuídos no DOM de sombra, eles podem assumir outros estilos definidos dentro da árvore de sombra.
pseudoelemento ::content
Os nós distribuídos são filhos do elemento host. Então, como podemos segmentá-los
de dentro do Shadow DOM? A resposta é o pseudoelemento CSS ::content.
É uma maneira de segmentar nós do Light DOM que passam por um ponto de inserção. Exemplo:
::content > h3 estiliza todas as tags h3 que passam por um ponto de inserção.
Vamos ver um exemplo:
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
<style>
h3 { color: red; }
content[select="h3"]::content > h3 {
color: green;
}
::content section p {
text-decoration: underline;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
<content select="section"></content>
`;
</script>
Como redefinir estilos em pontos de inserção
Ao criar um ShadowRoot, é possível redefinir os estilos herdados.
Os pontos de inserção <content> e <shadow> também têm essa opção. Ao usar
esses elementos, defina o .resetStyleInheritance em JS ou use o atributo booleano
reset-style-inheritance no próprio elemento.
Para um ShadowRoot ou pontos de inserção
<shadow>:reset-style-inheritancesignifica que as propriedades CSS herdáveis são definidas comoinitialno host, antes de atingirem o conteúdo de sombra. Esse local é conhecido como limite superior.Para pontos de inserção
<content>:reset-style-inheritancesignifica que as propriedades CSS herdáveis são definidas comoinitialantes que os filhos do host sejam distribuídos no ponto de inserção. Esse local é conhecido como limite inferior.
Conclusão
Como autores de elementos personalizados, temos muitas opções para controlar a aparência do nosso conteúdo. O Shadow DOM é a base desse novo mundo.
O Shadow DOM oferece encapsulamento de estilo com escopo e uma maneira de permitir que o mundo exterior entre tanto (ou tão pouco) quanto quisermos. Ao definir pseudoelementos personalizados ou incluir marcadores de posição de variáveis CSS, os autores podem fornecer a terceiros hooks de estilo convenientes para personalizar ainda mais o conteúdo. Em suma, os autores da Web têm controle total sobre como o conteúdo é representado.