CSS e estilo
Neste artigo, discutimos mais sobre as coisas incríveis que você pode fazer com o Shadow DOM. Ele se baseia nos conceitos discutidos em Introdução ao Shadow DOM. Se você está procurando uma introdução, consulte este artigo.
Introdução
Vamos ser honestos. Não há nada de especial em uma marcação sem estilo. Por sorte, as pessoas incríveis por trás dos Web Components previram isso e não nos deixaram na mão. O módulo de delimitação de CSS define muitas opções para definir o estilo do conteúdo em uma árvore de sombra.
Encapsulamento de estilo
Um dos principais recursos do Shadow DOM é o limite da sombra. Ele tem muitas propriedades legais, 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 estilo vermelho é o que está no ShadowRoot. Novamente, estilos com escopo por padrão.
- As outras regras de estilo definidas nesta página que segmentam h3s não interferem no meu conteúdo. Isso acontece porque os seletores não cruzam o limite da sombra.
Moral da história? Temos o encapsulamento de estilo do mundo exterior. Obrigado, Shadow DOM!
Como definir o estilo do elemento host
O :host
permite selecionar e estilizar o elemento que hospeda uma árvore paralela:
<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>
Um problema é que as regras na página pai têm especificidade maior que as regras :host
definidas no elemento, mas especificidade menor que um atributo style
definido no elemento host. Isso permite que os usuários modifiquem externamente o estilo do componente.
:host
também funciona apenas no contexto de uma ShadowRoot. Portanto, não pode ser usada fora do shadow DOM.
A forma funcional de :host(<selector>)
permite que você direcione o elemento host se ele corresponder a um <selector>
.
Exemplo: corresponde apenas se o elemento tiver a classe .different
(por exemplo, <x-foo class="different"></x-foo>
):
:host(.different) {
...
}
Reagir a estados do usuário
Um caso de uso comum de :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>)
corresponde ao elemento host se ele ou qualquer um dos ancestrais corresponder a <selector>
.
Um uso comum de :host-context()
é para aplicar temas a um elemento com base no que está ao redor dele. Por exemplo,
muitas pessoas implementam temas aplicando uma classe a <html>
ou <body>
:
<body class="different">
<x-foo></x-foo>
</body>
É possível 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 realizam um estilo único com base no contexto.
Oferecer suporte a vários tipos de host em uma raiz paralela
Outro uso para :host
é se você estiver criando uma biblioteca de temas e quiser
oferecer suporte ao estilo de vários tipos de elementos de 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>. */
}
Como estilizar os 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 que o limite do Shadow DOM seja atravessado para estilizar elementos em árvores de sombra.
O pseudoelemento ::shadow
Se um elemento tiver pelo menos uma árvore paralela, o pseudoelemento ::shadow
corresponderá à própria raiz paralela.
Ele permite que você escreva seletores que estilizam nós internos no DOM de sombra de um elemento.
Por exemplo, se um elemento hospedar 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 paralela com títulos h2
. Para estilizar esses títulos na página principal, é possível escrever:
x-tabs::shadow x-panel::shadow h2 {
...
}
O combinator /deep/
O combinador /deep/
é semelhante a ::shadow
, mas mais poderoso. Ele ignora completamente todos os limites das sombras e atravessa todas as árvores paralelas. Simplificando, /deep/
permite que você analise as entranhas de um elemento e direcione qualquer nó.
O combinator /deep/
é particularmente útil no mundo dos elementos personalizados, em que é comum ter vários níveis de Shadow DOM. Os principais exemplos são aninhar vários elementos personalizados (cada um hospedando a própria árvore de sombra) ou criar 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: estilize todos os elementos com a classe .library-theme
em qualquer lugar em uma árvore de sombra:
body /deep/ .library-theme {
...
}
Como trabalhar com querySelector()
Assim como .shadowRoot
abre
árvores de sombra para a travessia do DOM, os combinatores abrem árvores de sombra para a travessia do 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');
Estilo de elementos nativos
Os controles HTML nativos são um desafio para estilizar. Muitas pessoas simplesmente desistem
e fazem o próprio. No entanto, com ::shadow
e /deep/
, qualquer elemento na plataforma da Web que
usa o Shadow DOM pode receber estilo. 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 certos casos, você pode querer fazer buracos no escudo de estilo da sua Sombra e criar ganchos para outras pessoas personalizarem.
Como usar ::shadow e /deep/
O /deep/
tem muito poder. Ele oferece aos autores de componentes uma maneira de designar
elementos individuais como estilizados ou vários elementos como temas.
Exemplo: estilize 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 do navegador nativo. Um bom exemplo
é o input[type=range]
. É possível estilizar o cursor do 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 em alguns elementos internos, os autores de conteúdo do Shadow DOM podem designar certos elementos como estilizáveis por usuários externos. Isso é feito com pseudoelementos personalizados.
Use o atributo pseudo
para designar um elemento como pseudoelemento personalizado.
O valor ou nome 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 da sombra.
Confira um exemplo de como criar um widget de controle deslizante personalizado e permitir que alguém defina o estilo do controle deslizante para 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 poderosa de criar ganchos de temas é usando variáveis CSS. Basicamente, criar "marcadores de estilo" para que outros usuários preencham.
Imagine um autor de elementos personalizados que marca marcadores de posição variáveis no Shadow DOM. Um para estilizar a 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 Comic Sans super legal 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 é perfeito e isso funciona perfeitamente. A imagem completa fica 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>
Redefinir estilos
Estilos herdáveis, como fontes, cores e alturas de linhas, continuam afetando os elementos
no Shadow DOM. No entanto, para ter o máximo de flexibilidade, o Shadow DOM oferece a
propriedade resetStyleInheritance
para controlar o que acontece no limite do shadow.
Pense nisso como uma forma de começar do zero ao criar um novo componente.
resetStyleInheritance
false
: padrão. As propriedades CSS herdáveis continuam herdando.true
: redefine as propriedades herdáveis comoinitial
na fronteira.
Confira abaixo uma demonstração que mostra como a árvore de sombra é afetada pela alteração 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 complicado, principalmente porque
ele afeta apenas as propriedades CSS que são herdáveis. Ele diz: quando
você está 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 ative a caixa de seleção "Mostrar herdado" no painel "Elemento".
Aplicação de estilo em 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. Elas não estão logicamente no shadow DOM, ainda são filhos do elemento host. Os pontos de inserção são apenas uma renderização.
Os nós distribuídos retêm os estilos do documento principal. Ou seja, as regras de estilo da página principal continuam sendo aplicadas aos elementos, mesmo quando 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 mexem. Elas são renderizadas em outro lugar. No entanto, quando os nós são distribuídos no shadow DOM, eles podem assumir estilos adicionais definidos na árvore de sombra.
Pseudoelemento ::content
Os nós distribuídos são filhos do elemento host. Como podemos segmentá-los
dentro do Shadow DOM? A resposta é o pseudoelemento CSS ::content
.
É uma maneira de segmentar nós do DOM leve que passam por um ponto de inserção. Exemplo:
O ::content > h3
define o estilo de todas as tags h3
que passam por um ponto de inserção.
Confira 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>
Redefinir estilos nos pontos de inserção
Ao criar uma ShadowRoot, você tem a opção de redefinir os estilos herdados.
Os pontos de inserção <content>
e <shadow>
também têm essa opção. Ao usar
esses elementos, defina .resetStyleInheritance
no JS ou use o atributo
booleano reset-style-inheritance
no próprio elemento.
Para pontos de inserção de ShadowRoot ou
<shadow>
:reset-style-inheritance
significa que as propriedades CSS herdáveis são definidas comoinitial
no host antes de atingir o conteúdo da sombra. Esse local é conhecido como limite superior.Para pontos de inserção
<content>
:reset-style-inheritance
significa que as propriedades CSS heredáveis são definidas comoinitial
antes 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 deste admirável mundo novo.
O Shadow DOM oferece encapsulamento de estilo com escopo e uma maneira de permitir que o mundo externo entre tanto quanto (ou tão pouco quanto) quisermos. Ao definir pseudoelementos personalizados ou incluir marcadores de posição de variáveis do CSS, os autores podem fornecer ganchos de estilo convenientes a terceiros para personalizar ainda mais o conteúdo. Resumindo, os autores da Web têm total controle de como o conteúdo deles é representado.