Novas possibilidades no Chrome 65
A API CSS Paint, também conhecida como "CSS Custom Paint" ou "worklet de pintura da Hudini", é ativada por padrão a partir do Chrome 65. O que é? O que você pode fazer com ele? E como funciona? Continue lendo: está...
A API CSS Paint permite que você gere uma imagem de forma programática sempre que uma propriedade CSS espera uma imagem. Propriedades como background-image
ou border-image
costumam ser usadas com url()
para carregar um arquivo de imagem ou com funções integradas
de CSS, como linear-gradient()
. Em vez de usá-los, agora você pode usar
paint(myPainter)
para referenciar um worklet de pintura.
Como escrever uma worklet de tinta
Para definir uma worklet de pintura chamada myPainter
, precisamos carregar um arquivo de worklet de pintura CSS
usando CSS.paintWorklet.addModule('my-paint-worklet.js')
. Nesse
arquivo, podemos usar a função registerPaint
para registrar uma classe de worklet de pintura:
class MyPainter {
paint(ctx, geometry, properties) {
// ...
}
}
registerPaint('myPainter', MyPainter);
Dentro do callback paint()
, podemos usar ctx
da mesma forma que usamos um
CanvasRenderingContext2D
como o conhecemos em <canvas>
. Se você sabe
como desenhar em uma <canvas>
, pode usar uma worklet de pintura. geometry
informa a
largura e a altura da tela que está à nossa disposição. properties
Explicaremos isso posteriormente neste artigo.
Como exemplo introdutório, vamos escrever uma worklet de tinta quadriculada e usá-la
como uma imagem de plano de fundo de uma <textarea>
. Estou usando uma área de texto
porque ela é redimensionável por padrão:
<!-- index.html -->
<!doctype html>
<style>
textarea {
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
paint(ctx, geom, properties) {
// Use `ctx` as if it was a normal canvas
const colors = ['red', 'green', 'blue'];
const size = 32;
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
const color = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.fillStyle = color;
ctx.rect(x * size, y * size, size, size);
ctx.fill();
}
}
}
}
// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);
Se você já usou <canvas>
, esse código parecerá familiar. Veja a demonstração ao vivo aqui.
A diferença do uso de uma imagem de plano de fundo comum aqui é que o padrão será redesenhado sob demanda sempre que o usuário redimensionar a área de texto. Isso significa que a imagem de plano de fundo tem sempre o tamanho necessário, incluindo a compensação por telas de alta densidade.
Isso é muito legal, mas também é bastante estático. Queríamos escrever um novo worklet sempre que quiséssemos o mesmo padrão, mas com quadrados de tamanho diferente? A resposta é não.
Como parametrizar o worklet
Felizmente, o worklet de pintura pode acessar outras propriedades CSS, que é quando o
parâmetro adicional properties
entra em jogo. Ao atribuir à classe um atributo
inputProperties
estático, você pode se inscrever para receber mudanças em qualquer propriedade CSS,
incluindo propriedades personalizadas. Os valores serão fornecidos a você pelo parâmetro properties
.
<!-- index.html -->
<!doctype html>
<style>
textarea {
/* The paint worklet subscribes to changes of these custom properties. */
--checkerboard-spacing: 10;
--checkerboard-size: 32;
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
// inputProperties returns a list of CSS properties that this paint function gets access to
static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }
paint(ctx, geom, properties) {
// Paint worklet uses CSS Typed OM to model the input values.
// As of now, they are mostly wrappers around strings,
// but will be augmented to hold more accessible data over time.
const size = parseInt(properties.get('--checkerboard-size').toString());
const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
const colors = ['red', 'green', 'blue'];
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
ctx.fillStyle = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
ctx.fill();
}
}
}
}
registerPaint('checkerboard', CheckerboardPainter);
Agora, podemos usar o mesmo código para todos os diferentes tipos de tabuleiro. Melhor ainda, podemos acessar o DevTools e trabalhar com os valores até encontrar o visual certo.
Navegadores que não são compatíveis com o worklet de pintura
No momento em que este artigo foi escrito, apenas o Chrome tinha worklet de pintura implementada. Embora haja sinais positivos de todos os outros fornecedores de navegador, não há muito progresso. Para ficar por dentro das novidades, consulte Is Houdini Ready ainda? regularmente. Enquanto isso, use o aprimoramento progressivo para manter o código em execução, mesmo que não haja suporte para o worklet de pintura. Para garantir que tudo funcione conforme esperado, é preciso ajustar seu código em dois lugares: CSS e JS.
Para detectar o suporte a worklet de pintura em JS, verifique o objeto CSS
:
js
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('mystuff.js');
}
Para o CSS, há duas opções. Você pode usar @supports
:
@supports (background: paint(id)) {
/* ... */
}
Uma dica mais compacta é usar o fato de que o CSS invalida e, em seguida, ignora uma declaração de propriedade inteira se houver uma função desconhecida. Se você especificar uma propriedade duas vezes, primeiro sem o worklet de pintura e depois com o worklet de pintura, terá o aprimoramento progressivo:
textarea {
background-image: linear-gradient(0, red, blue);
background-image: paint(myGradient, red, blue);
}
Em navegadores que oferecem suporte ao worklet de pintura, a segunda declaração de
background-image
substituirá a primeira. Em navegadores sem suporte
a worklet de pintura, a segunda declaração será inválida e será descartada,
deixando a primeira declaração em vigor.
Polifill de pintura CSS
Para muitos usos, também é possível usar o Polyfill de pintura de CSS (em inglês), que adiciona compatibilidade com os Worklets de pintura personalizada e os Worklets de CSS aos navegadores mais recentes.
Casos de uso
Há muitos casos de uso para worklets de pintura, alguns deles mais óbvios do que
outros. Uma das mais óbvias é usar a worklet de pintura para reduzir o tamanho
do DOM. Muitas vezes, os elementos são adicionados puramente para criar enfeites
usando CSS. Por exemplo, no Material Design Lite, o botão
com o efeito de ondulação contém dois elementos <span>
extras para implementar a
ondulação em si. Se você tiver muitos botões, isso poderá gerar uma grande quantidade de elementos DOM e prejudicar o desempenho em dispositivos móveis. Se você
implementar o efeito de ondulação usando a worklet de pintura, vai acabar sem elementos adicionais e apenas uma worklet de pintura.
Além disso, você tem algo muito mais fácil de personalizar e
parametrizar.
Outra vantagem de usar a worklet de pintura é que, na maioria dos cenários, uma solução que usa a worklet de pintura é pequena em termos de bytes. É claro que há uma desvantagem: o código de pintura será executado sempre que o tamanho da tela ou qualquer um dos parâmetros mudar. Portanto, se o código for complexo e demorar muito, ele poderá introduzir instabilidade. O Chrome está trabalhando para remover os worklets de pintura da linha de execução principal, para que mesmo os trabalhos de pintura mais longos não afetem a capacidade de resposta da linha de execução principal.
Para mim, a perspectiva mais interessante é que a worklet de pintura permite um polyfill eficiente de recursos CSS que um navegador ainda não tem. Um exemplo seria fazer o polyfill de gradientes de cónic até que cheguem ao Chrome de maneira nativa. Outro exemplo: em uma reunião do CSS, decidimos que agora você pode ter várias cores de borda. Durante a reunião, meu colega Ian Kilpatrick escreveu um polyfill para esse novo comportamento do CSS usando a worklet de pintura.
Pensar fora da “caixa”
A maioria das pessoas começa a pensar em imagens de plano de fundo e de borda quando
aprendem sobre a worklet de pintura. Um caso de uso menos intuitivo para a worklet de pintura é
mask-image
para fazer com que elementos DOM tenham formas arbitrárias. Por exemplo, um
diamond:
mask-image
usa uma imagem do tamanho do elemento. Áreas em que a
imagem da máscara é transparente, o elemento é transparente. Áreas em que a imagem
da máscara é opaca e o elemento é opaco.
Agora no Chrome
A worklet de pintura está no Chrome Canary há algum tempo. No Chrome 65, ele é ativado por padrão. Teste as novas possibilidades da Worklet de pintura e mostre o que você criou. Para mais inspiração, dê uma olhada na coleção de Vincent De Oliveira.