API CSS Paint

Novas possibilidades no Chrome 65

A API CSS Paint (também conhecida como "CSS Custom Paint" ou "worklet de pintura do Houdini") é ativado por padrão a partir do Chrome 65. O que é? O que você pode fazer a ele? E como funciona? Bom, continue lendo, ok...

A API CSS Paint permite gerar uma imagem de forma programática sempre que um CSS espera uma imagem. Propriedades como background-image ou border-image geralmente são usados com url() para carregar um arquivo de imagem ou com CSS integrado funções como linear-gradient(). Em vez de usá-los, agora você pode usar paint(myPainter) para fazer referência a um worklet de pintura.

Como escrever uma worklet de pintura

Para definir um worklet de pintura chamado myPainter, precisamos carregar um paint CSS arquivo do worklet usando CSS.paintWorklet.addModule('my-paint-worklet.js'). Nesse 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 faríamos uma CanvasRenderingContext2D, como sabemos de <canvas>. Se você sabe como desenhar em uma <canvas>, será possível desenhar em um worklet de pintura. geometry nos diz largura e altura da tela que está à nossa disposição. properties Eu vou explicar mais adiante neste artigo.

Como um exemplo introdutório, vamos escrever uma worklet de tinta quadriculada e usá-la como a imagem de plano de fundo de uma <textarea>. (Estou usando uma área de texto porque é 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 o <canvas>, esse código deve parecer familiar. Consulte ao vivo demonstração aqui.

Área de texto com padrão quadriculado como imagem de plano de fundo
Área de texto com padrão quadriculado como imagem de plano de fundo.

A diferença do uso de uma imagem de plano de fundo comum aqui é que o padrão serão redesenhadas sob demanda sempre que o usuário redimensionar a área de texto. Isso significa que a imagem de plano de fundo tiver o tamanho exato, incluindo para telas de alta densidade.

Isso é muito legal, mas também é bastante estático. Queríamos escrever um novo worklet toda vez que quiséssemos o mesmo padrão, mas com tamanhos diferentes quadrados? A resposta é não.

Como parametrizar seu worklet

Felizmente, o worklet de pintura pode acessar outras propriedades CSS, que é onde o código o parâmetro adicional properties entra em jogo. Ao atribuir à classe uma imagem estática inputProperties, será possível se inscrever para receber alterações de 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 tabuleiros. Mas mesmo melhor, agora podemos acessar DevTools e mudar os valores até encontrarmos o visual certo.

Navegadores que não são compatíveis com o worklet de pintura

Atualmente, apenas o Chrome tem um worklet de pintura implementado. Enquanto há são sinais positivos de todos os outros fornecedores de navegador, não há muito progresso. Para ficar por dentro das novidades, marque Is Houdini Ready Yet? (O Houdini Ready Yet?) regularmente. Enquanto isso, use configurações aprimoramento para manter seu código em execução, mesmo que não haja suporte para paint worklet. Para garantir que tudo funcione como esperado, é preciso ajustar o código dois lugares: o CSS e o JS.

A detecção do suporte ao worklet de pintura no JS pode ser feita verificando o objeto CSS: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Quanto ao CSS, há duas opções. Você pode usar @supports:

@supports (background: paint(id)) {
  /* ... */
}

Um truque mais simples é usar o fato de que o CSS invalida e, posteriormente, ignora uma declaração de propriedade inteira se houver uma função desconhecida nela. Se você especifica uma propriedade duas vezes: primeiro sem o worklet de pintura e depois com o Worklet de pintura, você obtém aprimoramento progressivo:

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

Em navegadores com suporte a worklet de pintura, a segunda declaração de background-image vai substituir a primeira. Em navegadores sem suporte para a worklet de pintura, a segunda declaração é inválida e será descartada, deixando a primeira declaração em vigor.

Polyfill de pintura CSS

Para muitos usos, também é possível usar o Polyfill de Paint CSS, que adiciona compatibilidade com CSS Custom Paint e Paint Worklets para 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 é o uso de uma worklet de tinta para reduzir o tamanho do seu 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 ondulação. Se você tiver muitos botões, isso pode somar bastante dos elementos DOM e pode prejudicar o desempenho em dispositivos móveis. Se você Implementar o efeito de ondulação usando um worklet de pintura em vez disso, você acabará com 0 elementos adicionais e apenas um worklet de pintura. Além disso, você tem algo que é muito mais fácil de personalizar e parametrizar.

Outra vantagem de usar a ferramenta de pintura é que, na maioria dos cenários, uma solução usando um worklet de pintura é pequeno em termos de bytes. Claro, existe uma desvantagem: seu código de pintura será executado sempre que o tamanho da tela ou qualquer os parâmetros mudam. Portanto, se o código for complexo e demorar muito, ele pode introduzir instabilidade. O Chrome está trabalhando para remover os worklets de tinta da linha de execução principal para que mesmo as worklets de pintura de longa duração não afetam a capacidade de resposta do fio

Para mim, o cliente em potencial mais empolgante é que a colcheia de tinta permite que uma polyfilling de recursos CSS que o navegador ainda não tem. Um exemplo seria aos gradientes cônicos de polyfill até eles acessam o Chrome de forma nativa. Outro exemplo: em uma reunião do CSS, decidiu que agora é possível ter várias cores de borda. Durante a reunião em andamento, meu colega Ian Kilpatrick escreveu um polyfill para esse novo CSS usando um 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 sobre o worklet de pintura. Um caso de uso menos intuitivo para o worklet de pintura é mask-image para que os elementos do DOM tenham formas arbitrárias. Por exemplo, um diamond:

Um elemento DOM na forma de um losango.
Um elemento DOM na forma de um losango.

mask-image usa uma imagem que tem o tamanho do elemento. Áreas onde a imagem da máscara é transparente, o elemento é transparente. Áreas em que a máscara é usada a imagem é opaca e o elemento opaco.

Agora no Chrome

O worklet de pintura está no Chrome Canary há algum tempo. Com o Chrome 65, é ativada por padrão. Vá em frente e experimente as novas possibilidades essa worklet de tinta se abre e mostra o que você construiu! Para mais inspiração, dê uma olhada na coleção de Vincent de Oliveira.