CSS e web

Gradientes com shaders WebGL, explicados para designers

Alguns gradientes na web derivam, brilham e cintilam de uma forma que nenhuma imagem ou regra CSS consegue. São shaders. Eis como funcionam, em palavras que um designer pode usar.

GradientlyConta Gradiently verificada··7 min de leitura
Capa: Thistle Mosaic · GR·2BAQ·CR

A versão curta

  • Um gradiente WebGL é desenhado por um shader: um pequeno programa que corre no chip gráfico e decide a cor de cada píxel, muitas vezes várias vezes por segundo.
  • Como um shader calcula a cor com matemática em vez de ler uma imagem guardada, pode juntar movimento, ruído, grão e luz em qualquer tamanho sem o ficheiro crescer.
  • Os gradientes com shaders custam mais a correr do que os gradientes CSS, por isso um bom shader pausa quando está fora do ecrã, respeita a redução de movimento e tem uma alternativa em CSS simples.
  • Usa CSS para transições paradas e movimento simples, e recorre a um shader quando a textura, a profundidade ou o movimento são o objetivo do design.
Nesta página

Um gradiente WebGL é um gradiente que o teu browser pinta com um shader: um programa curto que corre no chip gráfico (a GPU) e responde a uma pergunta para cada píxel do ecrã: “que cor tens agora?” Um gradiente CSS é uma receita fixa de cores e posições. Um shader é uma fórmula, por isso pode dobrar, granular, iluminar e mover a cor, e voltar a fazê-lo sessenta vezes por segundo, sem descarregar nenhuma imagem.

Não precisas de escrever shaders para desenhar com eles, mas saber o que conseguem e não conseguem fazer ajuda-te a preparar um briefing para um programador, a avaliar uma ferramenta e a decidir quando um simples gradiente CSS é a melhor escolha.

O que é um shader, em palavras simples

O WebGL é a forma de o browser falar com a GPU, e cada desenho em WebGL usa dois tipos de shader. O vertex shader posiciona as formas; num gradiente de ecrã inteiro, é só um retângulo a cobrir o canvas. O fragment shader faz o trabalho interessante: corre uma vez por píxel, recebe a posição desse píxel e o tempo atual, e devolve uma cor.

Como a GPU corre milhares destes pequenos programas em paralelo, uma fórmula que seria lenta em código comum fica barata o suficiente para ser redesenhada a cada fotograma. Eis um fragment shader completo para um gradiente de três cores cuja fronteira ondula suavemente ao longo do tempo, com uma linha de grão por cima.

text
#version 300 es
precision highp float;

uniform vec2 u_resolution;   // canvas size in pixels
uniform float u_time;        // seconds since start
out vec4 outColor;

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;   // 0 to 1 across the canvas

  vec3 night  = vec3(0.07, 0.04, 0.18);  // #120a2e
  vec3 violet = vec3(0.49, 0.23, 0.93);  // #7c3aed
  vec3 coral  = vec3(0.98, 0.45, 0.45);  // #fa7373

  // A slow wave bends the blend so the colours drift.
  float wave = sin(u_time * 0.3 + uv.x * 3.0) * 0.08;
  float t = clamp(uv.y + wave, 0.0, 1.0);

  vec3 color = mix(night, violet, smoothstep(0.0, 0.6, t));
  color = mix(color, coral, smoothstep(0.55, 1.0, t));

  // Fine grain: a different random value per pixel, per frame.
  float n = fract(sin(dot(gl_FragCoord.xy + u_time, vec2(12.9898, 78.233))) * 43758.5453);
  color += (n - 0.5) * 0.04;

  outColor = vec4(color, 1.0);
}
Um fragment shader WebGL2 em GLSL. O smoothstep suaviza cada transição, a onda sinusoidal move a fronteira e as últimas linhas juntam grão, que também disfarça as bandas.
As três cores do shader. O GLSL escreve a cor de 0 a 1 por canal, por isso #7c3aed passa a 124, 58 e 237 divididos por 255.

Em que difere um gradiente com shader de um gradiente CSS

Um gradiente CSS é declarativo: listas cores e posições e o browser pinta-as. É a ferramenta certa na maior parte das vezes. Um shader é procedimental: descreves como a cor se deve comportar e a matemática produ-la. Essa diferença nota-se no que cada um consegue fazer e no que cada um custa.

AspetoGradiente CSSGradiente com shader
Definido porCores, paragens e uma direçãoUma fórmula corrida por píxel
MovimentoMover posição, tamanho ou propriedades personalizadas animadasTudo o que consigas exprimir em matemática, a cada fotograma
Textura e grãoPrecisa de uma camada extra, como ruído SVGIncluídos, e podem mover-se
Custo de execuçãoMuito baixo depois de pintadoMantém a GPU ocupada enquanto anima
Funciona sem JavaScriptSimNão: é um script que prepara o canvas
Funciona em emailEm parte, consoante o clienteNão
Nenhum é melhor em geral. Cada um ganha num trabalho diferente.
A alternativa em CSSlinear-gradient(180deg, #120a2e 0%, #4c1d95 40%, #7c3aed 62%, #fa7373 100%)
A mesma paleta como gradiente CSS parado. Todos os fundos com shader devem incluir uma alternativa assim, mostrada antes de o shader arrancar e sempre que não possa correr.

Porque é que os gradientes com shaders parecem vivos

Três técnicas fazem quase todo o trabalho, e vais ouvir os programadores referi-las. O ruído é aleatoriedade suave: em vez de uma transição reta, a cor segue uma paisagem de colinas suaves, e é por isso que os gradientes com shaders parecem orgânicos e não geométricos. O domain warping volta a alimentar o ruído com ele próprio, para a cor se dobrar e rodopiar como tinta na água. O tempo desliza a paisagem inteira, o que transforma um campo parado numa deriva.

  • O grão pode ser gerado por píxel e por fotograma, por isso cintila como película em vez de ficar parado como uma textura posta por cima. Vê os gradientes com grão.
  • A luz pode ser simulada com poucas linhas: um reflexo que segue o ruído lê-se como brilho em seda ou vidro.
  • A resolução é gratuita. A fórmula corre no tamanho que o ecrã tiver, por isso um gradiente com shader mantém-se nítido num telemóvel e num monitor 4K com o mesmo código.
  • As bandas são mais fáceis de vencer, porque o shader pode juntar um pouco de dithering antes de a cor ser arredondada a 8 bits. As bandas em gradientes explicam porque é que isso conta.

Usa CSS quando

  • O gradiente está parado, ou move-se de uma forma simples
  • Fica por trás de muito texto numa página longa
  • Tem de funcionar sem scripts, ou em email
  • Precisas dele numa linha de código

Usa um shader quando

  • O movimento ou a textura são o objetivo do design
  • É uma zona de destaque, não todas as secções
  • Queres grão, brilho ou fluxo que o CSS não consegue imitar
  • Consegues dar uma alternativa em CSS e uma pausa

O custo de um fundo WebGL em movimento

Um shader animado mantém a GPU a trabalhar enquanto estiver no ecrã. Num computador de secretária, quase não se nota. Num telemóvel mais antigo, pode significar mãos quentes, bateria a descer e uma página que desliza com menos fluidez. O custo cresce com o número de píxeis e com a complexidade da fórmula, por isso um shader de ecrã inteiro num ecrã de alta densidade é o caso mais pesado. O guia os gradientes abrandam um site? explica como medir.

  1. 1

    Mostra primeiro a alternativa em CSS

    Pinta o gradiente parado de imediato e depois faz aparecer o canvas quando o shader tiver desenhado o primeiro fotograma. Ninguém deve ver uma caixa vazia.

  2. 2

    Verifica se o WebGL está disponível

    Alguns browsers, dispositivos e definições de privacidade desativam-no. Se o contexto não puder ser criado, mantém a alternativa.

  3. 3

    Respeita a redução de movimento

    Se o visitante pedir menos movimento, desenha um fotograma e para. A redução de movimento explica a definição.

  4. 4

    Pausa quando não se vê

    Para o ciclo de animação quando o canvas sai do ecrã ou o separador fica oculto.

  5. 5

    Renderiza numa resolução sensata

    Os gradientes suaves raramente precisam de todos os píxeis do dispositivo. Desenhar a meia resolução e deixar o browser ampliar pode reduzir muito o trabalho, com uma desfocagem que não se distingue.

js
const canvas = document.querySelector('#hero-canvas')
const gl = canvas.getContext('webgl2')
const still = matchMedia('(prefers-reduced-motion: reduce)').matches
let visible = true

if (!gl) {
  canvas.remove() // the CSS gradient behind it stays
} else {
  new IntersectionObserver(([entry]) => {
    visible = entry.isIntersecting
  }).observe(canvas)

  const frame = (ms) => {
    if (visible && !document.hidden) draw(gl, ms / 1000) // your draw call
    if (!still) requestAnimationFrame(frame)
  }
  requestAnimationFrame(frame)
}
As proteções em poucas linhas: uma alternativa quando falta o WebGL, um único fotograma para a redução de movimento e nada desenhado fora do ecrã ou num separador oculto.

Como referência, a API WebGL no MDN documenta todas as chamadas usadas aqui, e o caniuse acompanha o suporte do WebGL2 nos vários browsers.

Desenhar um gradiente com shader: o que pôr no briefing

Podes dirigir um shader sem ler GLSL. Faz o briefing como farias o de uma matéria, não o de uma amostra de cor. Indica a cor de fundo e as três a cinco cores que vivem sobre ela, em hex. Descreve a estrutura: poças suaves, faixas longas, seda dobrada. Indica a velocidade em segundos por ciclo em vez de “lento”, porque lento significa coisas diferentes para pessoas diferentes. Diz quanto grão queres e se se move. Por fim, diz onde vai ficar o texto, para a parte mais agitada do campo se manter longe dele.

Como a Gradiently desenha os Marks com shaders

Os Marks da Gradiently são gradientes com shaders. Cada Mark é uma receita de cores, até três matérias sobrepostas, luz, grão e movimento, e o Studio desenha-o ao vivo com WebGL2 por trás de tudo o que desenhas. Como é calculado e não guardado, o mesmo Mark é renderizado com nitidez em qualquer tamanho de plataforma e move-se quando exportas um vídeo em loop. O renderizador também sabe onde estão as tuas palavras, por isso consegue deslocar a parte mais calma do campo para trás delas. O guia o que é um Mark explica a receita por inteiro.

Thistle Mosaic

GR·2BAQ·CR

Thistle Mosaic

Ver este Mark
Um Mark é uma receita de shader com um nome, um código e um dono. A sua página mostra-o a ser renderizado ao vivo.

Perguntas frequentes

O que é um gradiente WebGL?

É um gradiente desenhado por um shader, um pequeno programa que corre no chip gráfico e calcula a cor de cada píxel. Como é uma fórmula, pode mover-se, juntar grão e ser renderizado em qualquer tamanho.

Um gradiente com shader prejudica o desempenho?

Um shader parado ou em pausa custa pouco, mas um animado mantém a GPU ocupada enquanto está no ecrã. Pausa-o quando está fora de vista, desenha numa resolução sensata e mantém uma alternativa em CSS.

Posso fazer um gradiente com shader sem programar?

Sim. Várias ferramentas de design geram fundos com shaders a partir de controlos, e a Forge da Gradiently constrói Marks, que são gradientes com shaders, a partir de uma frase ou controlo a controlo.

Os gradientes com shaders respeitam as definições de redução de movimento?

Só se forem construídos para isso. Verifica a media query prefers-reduced-motion em JavaScript e desenha um único fotograma parado quando estiver ativa.

O que é o GLSL?

O GLSL é a linguagem de shading que o WebGL usa. Os fragment shaders escritos nela decidem a cor de cada píxel, e o browser compila-os para o chip gráfico.

Partilhar este guia

Escrito pela GradientlyConta Gradiently verificada

A equipa por trás da Gradiently, uma ferramenta de design construída em torno dos Marks: gradientes vivos que fazem tudo o que crias parecer teu.

Ver o nosso perfil