Conteúdo deste artigo
- Pilha e ferramentas
- O plano de fundo do herói: distorcendo uma superfície viva
- A abordagem
- Direção e persistência do movimento
- Restrição Visual
- Arquitetura
- Reflexões
Uma reformulação da marca já estava prevista há algum tempo. Queríamos algo que nos ajudasse a nos destacar. Não apenas parecer diferente, mas possuir o que realmente fazemos.
Esse processo levou a um conceito simples: comércio magnético. Grandes experiências digitais fazem mais do que informar. Eles atraem as pessoas. Essa ideia moldou nosso novo logotipo, com parte do D puxada para dentro como se fosse por força magnética. O emblema tornou-se o ponto de partida para quase todas as decisões de design que se seguiram.
Depois que o ativo foi renderizado como um objeto 3D giratório, a interação do cursor ocorreu naturalmente. O logotipo deve se comportar de acordo com seu conceito. Arraste o cursor sobre ele e ele puxa. Esse detalhe deu o tom de como pensaríamos sobre o movimento no restante do site.
A outra decisão inicial foi deixar o design do iOS informar como lidamos com cartões e CTAs. Projetamos cartões de projeto como um aceno deliberado às notificações do iOS: familiares, táteis, um pouco inesperados no contexto. As diretrizes da marca eram rígidas. Essa restrição acabou sendo útil. Isso impediu que as decisões mudassem e nos empurrou para algo mais ponderado.
No total o projeto durou alguns meses. O conceito pousou relativamente rápido. Acertar o conteúdo e as animações devidamente refinadas no desenvolvimento: foi aí que o tempo passou.
Pilha e ferramentas
Figma para design. As diretrizes da marca foram bem definidas e a Figma manteve tudo sistemático (tipo, espaçamento, componentes) sem atrapalhar a iteração.
Fluxo da Web para a compilação. Nosso próprio site é construído em Webflow e fazia sentido usá-lo como uma prova do que é possível na plataforma para clientes em potencial. A maioria das interações reside dentro do sistema de animação do Webflow.
GSAP para as partes que as interações nativas do Webflow não conseguiram alcançar, especialmente os comportamentos e sequências mais precisos orientados pelo cursor.
WebGL personalizado para o plano de fundo do herói. Aquele não poderia viver em nenhum outro lugar.
O plano de fundo do herói: distorcendo uma superfície viva
O plano de fundo do herói começou com uma ideia simples: usar um vídeo em tela cheia como textura e distorcer em resposta ao movimento do cursor. O objetivo era fazer com que o fundo parecesse responsivo sem distrair.
Uma coisa foi importante desde o início. Não queríamos que o efeito se comportasse como uma simples ondulação centrada no cursor. Em vez disso, a distorção deve continuar na direção do movimento, dando a impressão de que a superfície está sendo puxada, em vez de simplesmente reagir a um estado de flutuação.
A abordagem
O efeito é implementado inteiramente em um fragment shader. Em vez de sobrepor efeitos sobre o vídeo, o shader remapeia diretamente as coordenadas de textura do vídeo. O cursor define a área de influência, enquanto a direção do movimento controla como a distorção se propaga. Uma função senoidal amortecida gera a onda que impulsiona o deslocamento.
Os valores configuráveis são definidos uma vez e passados para o renderizador:
const SETTINGS = {
radius: 0.41,
amplitude: 0.082,
frequency: 13,
speed: 0.98,
carry: 6,
stagger: 12,
centerPower: 2,
verticalDampPower: 2.2,
motionGain: 220,
speedDecay: 0.86,
};
A geometria consiste em um único quadrante de tela cheia:
const vertices = new Float32Array([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
-1, 1, 0, 1,
1, -1, 1, 0,
1, 1, 1, 1,
]);
A distorção em si acontece no fragment shader:
float maskFn(vec2 uv) {
vec2 d = uv - uMouseSm;
d.x *= uAspect;
float dist = length(d);
return pow(smoothstep(uRadius, uRadius - uSoft, dist), uCenterPow);
}
float wave = sin(
d.y * uFreq +
dot(d, dir) * uCarry +
uTime * uSpeed +
d.y * uStagger
);
d += dir * (wave * amp * maskFn(uv) * damp);
O resultado é um efeito que responde ao movimento do cursor sem se comportar como uma simples interação flutuante. A distorção carrega impulso na direção do deslocamento antes de se estabilizar gradualmente.
Direção e persistência do movimento
Antes que os valores sejam passados para o shader, o estado de interação é suavizado e decaído no lado do JavaScript:
motionTarget *= SETTINGS.speedDecay;
const mappedMotion = Math.min(motionTarget * SETTINGS.motionGain, 1);
motion += (mappedMotion - motion) * SETTINGS.momentum;
dirSm.x += (dirTarget.x - dirSm.x) * SETTINGS.dirSmooth;
dirSm.y += (dirTarget.y - dirSm.y) * SETTINGS.dirSmooth;
Essa persistência evita que o efeito pare assim que o cursor para. Em vez disso, a distorção perde gradualmente o impulso antes de retornar ao seu estado de repouso, fazendo com que a interação pareça mais contínua e menos mecânica.
Restrição Visual
O objetivo era adicionar movimento ao herói sem competir com o conteúdo. O efeito fica por trás da tipografia, adicionando profundidade e mantendo o texto fácil de ler.
O campo de influência usa um enfraquecimento suave em vez de uma borda rígida, evitando a aparência de um holofote ao redor do cursor. O amortecimento vertical também limita a extensão da propagação da onda, dando à distorção uma forma mais direcional em vez de se expandir uniformemente em todas as direções.
O resto do herói é deliberadamente simples: um fundo preto, um vídeo sem margens e o shader aplicado diretamente na textura. Manter todo o resto mínimo ajuda a manter o foco na interação em si.
Arquitetura
A estrutura é bastante simples.
<section id="us-sine-bg" class="hero-sine-bg">
<video id="bgvid" class="hero-sine-bg__video" muted playsinline autoplay loop></video>
</section>
A configuração consiste em um contêiner hero, um elemento de vídeo oculto usado como fonte de textura e uma tela WebGL em camadas acima dele.
A partir daí, o renderizador executa cinco tarefas principais:
- Prepare a fonte de vídeo e aguarde a reprodução.
- Inicialize o renderizador WebGL e compile os shaders.
- Rastreie o movimento do ponteiro e suavize o estado da interação.
- Carregue o quadro de vídeo atual para a GPU de cada quadro.
- Renderize uma única passagem em tela cheia.
O renderizador não depende de um gráfico de cena, pipeline de pós-processamento ou geometria adicional. Ele funciona com uma única textura, um quad de tela cheia e um par de shaders, facilitando a integração em um site de produção.
Os uploads de quadros só são realizados quando o vídeo estiver pronto:
if (video.readyState >= 2) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
}
rafId = requestAnimationFrame(renderFrame);
A implementação mantém o loop de renderização leve. Os valores de interação são suavizados ao longo do tempo, o sombreador apenas remapeia as coordenadas UV, a tela é limitada à seção principal e a renderização não começa até que o vídeo esteja pronto.
A mesma abordagem pode ser reutilizada com diferentes fontes de vídeo, perfis de distorção ou seções de um site sem alterar a estrutura geral.
Reflexões
O resultado final parece muito mais próximo do que queríamos que o site comunicasse. A implementação técnica ocorreu de forma relativamente rápida. A maior parte do tempo foi gasta refinando o movimento, editando o conteúdo e garantindo que ambos funcionassem juntos.
Um dos maiores desafios foi saber quando parar de adicionar. Pequenos ajustes no tempo, na flexibilização e na cópia muitas vezes tiveram mais impacto do que a introdução de novos efeitos.
Olhando para trás, a principal coisa que abordaríamos de forma diferente é a dependência da interação do cursor. A experiência muda significativamente em dispositivos sensíveis ao toque, então isso é algo que consideraríamos mais cedo no processo de design, em vez de adaptarmos mais tarde.
No geral, o projeto reforçou uma ideia que moldou a reformulação da marca desde o início: a interação funciona melhor quando apoia o conceito, em vez de chamar a atenção para si mesma.
