Novas possibilidades no Chrome 65
A CSS Paint API (também conhecida como "CSS Custom Paint" ou "worklet de pintura do Houdini") é ativada por padrão a partir do Chrome 65. O que é? O que é possível fazer com ele? E como funciona? Então continue lendo…
A CSS Paint API permite gerar uma imagem programaticamente sempre que uma propriedade
CSS espera uma imagem. Propriedades como background-image
ou border-image
geralmente são usadas com url()
para carregar um arquivo de imagem ou com funções
integradas do CSS, como linear-gradient()
. Em vez disso, use
paint(myPainter)
para fazer referência a um worklet de pintura.
Como escrever um worklet de pintura
Para definir um worklet de pintura chamado 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);
No callback paint()
, podemos usar ctx
da mesma forma que um
CanvasRenderingContext2D
, como sabemos em <canvas>
. Se você sabe
desenhar em um <canvas>
, pode desenhar em um worklet de pintura. geometry
informa a
largura e a altura da tela disponível. properties
Vou
explicar mais adiante neste artigo.
Como exemplo introdutório, vamos escrever um worklet de pintura de tabuleiro de xadrez e usá-lo
como uma imagem de plano de fundo de um <textarea>
. Estou usando um textarea porque ele é
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 vai parecer familiar. Confira
a demonstração
ao vivo.
A diferença de usar uma imagem de plano de fundo comum aqui é que o padrão será redesenhado sob demanda, sempre que o usuário redimensionar o textarea. Isso significa que a imagem de plano de fundo é sempre exatamente do tamanho necessário, incluindo a compensação para telas de alta densidade.
Isso é muito legal, mas também é bastante estático. Seria necessário escrever um novo worklet sempre que quiséssemos o mesmo padrão, mas com quadrados de tamanhos diferentes? A resposta é não.
Como parametrizar seu worklet
Felizmente, o worklet de pintura pode acessar outras propriedades do CSS, que é onde o
parâmetro adicional properties
entra em ação. Ao atribuir à classe um atributo
inputProperties
estático, você pode se inscrever para receber notificações de mudanças em qualquer propriedade CSS,
incluindo propriedades personalizadas. Os valores serão fornecidos 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 tipos de tabuleiro de xadrez. Mas ainda melhor, agora podemos acessar o DevTools e mexer nos valores até encontrar a aparência certa.
Navegadores que não oferecem suporte ao worklet de pintura
No momento da escrita, apenas o Chrome tem o worklet de pintura implementado. Embora existam sinais positivos de todos os outros fornecedores de navegadores, não há muito progresso. Para se manter atualizado, confira regularmente a página Is Houdini Ready Yet?. 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 as coisas funcionem conforme o esperado, ajuste o código em dois lugares: CSS e JS.
Para detectar o suporte a worklets de pintura no JS, verifique o objeto CSS
:
js
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('mystuff.js');
}
No lado do CSS, você tem duas opções. É possível usar @supports
:
@supports (background: paint(id)) {
/* ... */
}
Um truque mais compacto é usar o fato de que o CSS invalida e, em seguida, ignora uma declaração de propriedade inteira se houver uma função desconhecida nela. Se você especificar uma propriedade duas vezes, primeiro sem o worklet de pintura e depois com o worklet de pintura, vai ocorrer o aprimoramento progressivo:
textarea {
background-image: linear-gradient(0, red, blue);
background-image: paint(myGradient, red, blue);
}
Em navegadores com suporte para o worklet de pintura, a segunda declaração de
background-image
vai substituir a primeira. Em navegadores sem suporte
para o worklet de pintura, a segunda declaração é inválida e é descartada,
deixando a primeira declaração em vigor.
CSS Paint Polyfill
Para muitos usos, também é possível usar o polyfill do CSS Paint, que adiciona suporte ao CSS Custom Paint e aos worklets do Paint a navegadores modernos.
Casos de uso
Há muitos casos de uso para worklets de pintura, alguns deles mais óbvios do que
outros. Uma das mais óbvias é usar o worklet de pintura para reduzir o tamanho
do DOM. Muitas vezes, os elementos são adicionados apenas 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>
adicionais para implementar o
efeito. Se você tiver muitos botões, isso pode resultar em um grande número
de elementos DOM e levar a uma degradação no desempenho em dispositivos móveis. Se você
implementar o efeito de ondulação usando a worklet de pintura, você terá zero elementos adicionais e apenas uma worklet de pintura.
Além disso, você tem algo muito mais fácil de personalizar e
parametrizar.
Outra vantagem do uso de worklets de pintura é que, na maioria dos casos, uma solução que usa worklets de pintura é pequena em termos de bytes. É claro que há um trade-off: seu 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á causar jank. O Chrome está trabalhando para mover os worklets de pintura da linha de execução principal para que mesmo os worklets de pintura de longa duração não afetem a capacidade de resposta da linha de execução principal.
Para mim, a perspectiva mais interessante é que o worklet de pintura permite um polyfill eficiente de recursos CSS que um navegador ainda não tem. Um exemplo seria usar o polifill de gradientes cônicos até que eles sejam lançados no Chrome de forma nativa. Outro exemplo: em uma reunião do CSS, foi decidido que agora é possível ter várias cores de borda. Enquanto essa reunião ainda estava acontecendo, meu colega Ian Kilpatrick criou um polyfill para esse novo comportamento do CSS usando o 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 o worklet de pintura. Um caso de uso menos intuitivo para o worklet de pintura é
mask-image
para fazer com que os elementos do DOM tenham formas arbitrárias. Por exemplo, um
diamante:
mask-image
usa uma imagem do tamanho do elemento. As áreas em que a
imagem da máscara é transparente, o elemento é transparente. Áreas em que a imagem
da máscara é opaca, o elemento é opaco.
Agora no Chrome
O worklet de pintura está no Chrome Canary há algum tempo. No Chrome 65, ele é ativado por padrão. Teste as novas possibilidades que o worklet de pintura oferece e mostre o que você criou. Para mais inspiração, confira a coleção de Vincent De Oliveira.