Novo Livro!
Categorias
Projetos
CSS

Um SVG, nove pôsteres: CSS Linked Parameters

Um SVG, nove pôsteres: CSS Linked Parameters

Um SVG usado como <img> ou imagem de fundo sempre ignorou o CSS da página. Com CSS Linked Parameters, a página passa valores para dentro do arquivo e um único SVG ganha quantas paletas forem necessárias, com fallback automático nos navegadores que ainda não conhecem o recurso.

A seguir estão os dois lados da sintaxe (o SVG e o CSS), um pôster do Andy Warhol pintado de nove jeitos a partir do mesmo arquivo e o que ainda falta implementar. Por enquanto, é um recurso experimental para acompanhar e testar, não para usar em produção.

Alguns anos atrás, o autor, Vadim Makeev, mergulhou no assunto dos sprites SVG e voltou com uma lista de métodos, cada um quebrado do seu jeito. A linha divisória acabou ficando num lugar diferente do esperado.

Cache é um problema resolvido: um sprite cheio de <symbol>s vive num arquivo externo, entra no cache como qualquer outro e ainda respeita o currentcolor. Só que isso funciona apenas por meio de um <svg> inline como marcador no HTML.

Peça o mesmo gráfico como <img> ou imagem de fundo e a estilização sai de cena. Nada de cores.

Isso finalmente começa a mudar. Existe uma especificação nova chamada CSS Linked Parameters, que permite passar valores CSS para um recurso vinculado, e o recurso decide o que fazer com eles.

O Firefox Nightly traz uma implementação experimental desde a versão 153, com a preferência layout.css.link-parameters.enabled ativada por padrão, e nenhum outro navegador tem nada. Ou seja, está longe de produção.

Mas já dá para brincar com ele hoje, e havia a desculpa perfeita para isso: imprimir um Andy Warhol.

O beco sem saída da estilização

Uma recapitulação rápida do problema. Aqui está o mesmo logo, colocado de quatro formas diferentes:

<svg viewBox="0 0 160 160" width="160" height="160">
<path d="M123.13 36.87a61.02 61.02 0 0 0-86.26 0…"/>
</svg>
<svg width="160" height="160">
<use href="logo.svg#logo"/>
</svg>
<img src="logo.svg" width="160" height="160" alt="">
<div style="background-image: url('logo.svg')"></div>

As duas primeiras podem ser estilizadas pelo CSS da página: o currentcolor e propriedades herdadas como fill atravessam a fronteira do SVG inline e, com algumas restrições, chegam ao símbolo que ele puxa de um arquivo externo.

As duas últimas, não. Um <img> ou uma imagem de fundo é um documento separado, com seus próprios estilos, e nada do que se escreve no CSS da página alcança o lado de dentro.

O Firefox tem há anos uma solução não padronizada: -moz-context-properties permite que um SVG externo aceite o fill e o stroke do elemento que o referencia. Ela é usada na própria interface do Firefox desde sempre, mas nunca virou padrão e nunca funcionou em outro lugar.

Os linked parameters são o substituto padronizado dela, e bem mais flexível: em vez de duas propriedades fixas, dá para ter quantos valores nomeados forem necessários.

Passando uma cor

São dois lados: o SVG declara o que aceita, e o CSS fornece os valores.

O lado do SVG

Dentro do arquivo SVG, onde normalmente iria uma cor, entra uma função env() com um nome e um fallback:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160">
<rect fill="env(--color, #c1f07c)" width="160" height="160" rx="80"/>
<path fill="black" d="M123.13 36.87a61.02 61.02 0 0 0-86.26 0…"/>
</svg>

Talvez você conheça o env() do safe-area-inset-bottom e companhia, em que o navegador fornece os valores. Aqui é o contrário: o nome é seu, e a página que vincula este arquivo fornece o valor.

O segundo argumento é o fallback. Enquanto ele estiver lá, o arquivo continua sendo um SVG perfeitamente normal, que é renderizado sozinho, em qualquer navegador, sem parâmetro nenhum.

Também funciona num elemento <style> dentro do SVG, e não só em atributos de apresentação:

<style>
.logo {
fill: env(--color, #c1f07c);
}
</style>

O lado do CSS

Na página, os valores são definidos com a propriedade link-parameters e a função param():

img {
link-parameters: param(--color, #ff8052);
}

Só isso. O elemento <img> é o recurso vinculado, a propriedade se aplica a ele, e o SVG lá dentro é repintado.

Esse logo é verde em todo perfil do autor nas redes sociais, mas agora o verde é um parâmetro, então aqui ele sai coral. Se o logo continuar verde na demonstração, o navegador ainda não tem o recurso.

Abrir a demonstração do logo em nova aba

A nomenclatura confunde um pouco no começo. Em param(--color, #ff8052), o primeiro argumento é o nome do parâmetro e o segundo é o valor enviado. Em env(--color, #c1f07c), o primeiro argumento é o mesmo nome e o segundo é o fallback.

Mesmo formato, funções diferentes: param() manda um valor para dentro, e env() decide o que usar quando nada chega.

Quatro matrizes, um pôster

Uma cor é legal, mas um logo não é lá uma grande tela. Hora de apresentar uma de verdade: a gravura Marilyn, de Andy Warhol, de 1967, vetorizada em SVG.

Warhol fazia essas obras em serigrafia, o que significa que a imagem é uma pilha de matrizes de cor chapada, uma passada de tinta cada. A vetorização funciona exatamente do mesmo jeito: quatro elementos <path>, quatro cores chapadas, cada uma pintada por cima da anterior.

Nada de gradientes, nada de opacidade, nenhum truque esperto. É a imagem mais parametrizável que se poderia imaginar.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1026 1022">
<path fill="env(--dark, #37312d)" d="M294 1020h-2…"/>
<path fill="env(--face, #ebb4ad)" d="M0 511V0h1026…"/>
<path fill="env(--hair, #e6e780)" d="M801 982c0-3…"/>
<path fill="env(--back, #f25a80)" d="M6 1019A118549…"/>
</svg>

Os paths vão de baixo para cima, na ordem em que as telas desceriam. A matriz escura vai primeiro.

Depois, o tom de pele cobre a tela inteira e abre buracos em si mesmo onde o escuro deve aparecer: o atributo d dele começa traçando os quatro cantos e depois gasta algumas centenas de subpaths recortando buracos nesse retângulo. Em seguida vêm o cabelo e o fundo, cada um pintado por cima do que veio antes.

Agora, quatro parâmetros em vez de um:

img {
link-parameters:
param(--back, #cfa9bf),
param(--hair, #faa601),
param(--face, #99d2d7),
param(--dark, #73901a);
}
Abrir a demonstração do pôster em nova aba

Nove pôsteres, um arquivo

E aqui está a parte que faz desse recurso mais do que um substituto do currentcolor. O mesmo arquivo, referenciado nove vezes, pintado de nove jeitos diferentes:

<div class="poster">
<img
class="poster__panel poster__panel--pink"
src="poster.svg"
width="1026" height="1022"
alt="Marilyn Monroe portrait in pink and yellow"
>
<!-- …e mais oito -->
</div>
.poster__panel--pink {
link-parameters:
param(--back, #f85983),
param(--hair, #ecef81),
param(--face, #edb3ac),
param(--dark, #322e2e);
}
.poster__panel--turquoise {
link-parameters:
param(--back, #4a9fac),
param(--hair, #d87205),
param(--face, #cba6bd),
param(--dark, #a09797);
}
/* …e mais sete */
Abrir a demonstração dos nove pôsteres em nova aba

Demonstrações ao vivo são complicadas para um recurso que só existe no Firefox: a menos que você esteja lendo isto no Nightly, a demonstração não vai mostrar o que está descrito aqui. Esta é uma captura de tela de um navegador que tem o recurso:

Nove retratos de Marilyn Monroe numa grade de três por três, cada um com um conjunto diferente de cores: rosa e amarelo, turquesa e laranja, ameixa e azul-marinho, lilás e azul-celeste, magenta e laranja, verde-menta e âmbar, cinza e azul-ardósia, verde e vermelho-tijolo, verde-azulado e magenta.

Nove pinturas, um arquivo de 50 KB, uma requisição de rede. A especificação é explícita: quando a mesma URL aparece várias vezes com parâmetros diferentes, os navegadores precisam pintar cada uma corretamente, mas podem compartilhar o documento por trás delas.

Essa é a parte que nenhuma técnica existente oferece. Símbolos SVG podem mudar de cor, mas só por meio de um marcador inline. Fragmentos e stacks funcionam em <img>, mas cada variação de cor precisa estar gravada no arquivo de antemão. Aqui, o arquivo continua sendo um modelo, e a página decide.

O fallback

Agora, a mesma página num navegador sem suporte a nada disso:

A mesma grade de três por três, mas os nove retratos são idênticos: fundo rosa, cabelo amarelo, rosto cor de pele e uma matriz de tinta quase preta.

Nove pôsteres idênticos, nas cores originais da gravura. Warhol não ficaria impressionado, mas a página não quebra. Todo env() do arquivo tem um fallback, então um navegador sem suporte simplesmente renderiza o SVG do jeito que ele foi desenhado.

Isso faz deste um tipo raro de recurso novo do CSS: o progressive enhancement sai de graça. Não é preciso @supports nem uma folha de estilo de fallback, basta escolher cores padrão sensatas ao exportar o arquivo. O que já ia acontecer de qualquer jeito.

Se isso parece familiar, é o mesmo contrato que as custom properties sempre tiveram. var(--brand, black) cai no preto quando --brand nunca foi definida, e env(--brand, black) cai no preto quando nada foi passado.

Ainda não chegou lá

Mesmo no Nightly, só uma parte da especificação está implementada, e o resto é acompanhado no bug 1812163. A propriedade em si funciona, inclusive em imagens de fundo:

div {
background-image: url('poster.svg');
link-parameters: param(--back, tomato);
}

Mas as outras duas formas que a especificação define para passar parâmetros ainda não funcionam. Nem o modificador de url():

div {
/* Não, ainda não */
background-image: url('poster.svg' param(--back, tomato));
}

Nem o fragmento da URL, que é o mais aguardado pelo autor, porque funcionaria direto do HTML, sem CSS nenhum:

<!-- Também não, ainda -->
<img src="poster.svg#:~:param(--back,tomato)" alt="">

Os atalhos param(color, …) e param(accent-color, …) também estão faltando, e essa é a perda maior: são eles que levariam o currentcolor para um <img>.

Passar param(--color, currentcolor) no lugar não resolve, porque a palavra-chave é resolvida dentro da imagem, onde a cor ainda é preta.

Algumas faltas menores:

  • A anotação de tipo opcional, param(--back <color>, tomato), não existe ainda. Sem ela, funciona normalmente.
  • Um <svg> inline com <use href="sprite.svg#icon"/> não recebe parâmetros. O currentcolor atravessa essa fronteira, os linked parameters não.
  • O <object> também não recebe nada. Por enquanto, só <img> e imagens do CSS.

Aliás, os valores não se limitam a cores. Qualquer coisa que caiba numa declaração CSS funciona, inclusive var() da página:

img {
link-parameters: param(--color, var(--brand));
}

E, do lado do SVG, o env() também não se limita ao fill. Larguras de traço, opacidades, transformações, o que a imagem precisar:

<path
stroke="env(--stroke, gray)"
stroke-width="env(--width, 2)"
d="…"
/>

Vale a espera

Então, dá para usar CSS Linked Parameters hoje? Não, e não vai dar por um bom tempo. Um motor, um canal, meia especificação.

Mesmo assim, vale acompanhar, pelo que isso mudaria quando estiver em todo lugar. Um arquivo de ícone em vez de um por tema. Uma ilustração que segue a cor da marca sem ser colocada inline em cada página. Qualquer caso em que a forma é fixa e a paleta não, servido como uma imagem comum em cache.

Até lá, a única coisa a fazer com ele é brincar. Abra os nove pôsteres no Nightly, escolha as próprias cores e veja até onde quatro parâmetros chegam.

Warhol provavelmente teria gostado deste. Mesma tela, tinta diferente, roda de novo 🎨