Uma das coisas que nos deixou confusos quando estávamos a construir o nosso novo sítio Web foram as ondas transformadas que se podem ver em diferentes locais das páginas. Tínhamos muitas ideias sobre como implementá-las da forma correta sem grande esforço. No entanto, a maior parte das soluções eram lentas e tivemos de construir de raiz algo que fosse mais rápido do que as bibliotecas já existentes.
Propostas de solução
Começámos com um objeto SVG simples que foi animado com diferentes bibliotecas. Como queríamos ter 3 objectos numa página, o resultado não foi muito satisfatório. Todas as animações eram lentas - todos os caminhos de um único objeto SVG tinham de ser actualizados em períodos de tempo muito curtos, o que tornava a página inteira tão lenta como um caracol. Tivemos de rejeitar a solução com SVG puro inserido num documento. Isso deixou nós com duas outras soluções à escolha.
O vídeo foi a segunda opção. Começámos com dois problemas:
- fundo transparente, que não pode ser aplicado com os formatos de vídeo mais populares, como .mp4 ou .webm,
- a capacidade de resposta, o que constituía um verdadeiro problema, uma vez que os vídeos não são escaláveis enquanto tal.
Decidimos manter esta solução em suspenso - "se não encontrarmos mais nada, escolhemos esta".
A última opção era utilizar tela com WebGL renderização. Foi uma opção muito invulgar, porque tivemos de conceber todos os mecanismos de renderização por nós próprios. Isto porque as ondas mórficas que tínhamos eram personalizadas - o que nos obrigou a conceber uma solução personalizada.
Arquitetura da solução
Vamos começar do zero. Qual era o material que tínhamos de utilizar para construir estas ondas? A ideia era que todas as ondas fossem um ficheiro SVG de tamanho 1×1 e caminhos específicos posicionados à volta desta área. A animação deste SVG foi construída através de algumas formas de estados para este ficheiro. Assim, todas as animações eram representadas como um conjunto de ficheiros que continham as fases de movimentação de uma forma.
Se olharmos mais profundamente para o que são os estados - todos os caminhos são apenas uma espécie de matriz com valores específicos posicionados numa ordem específica. Alterar esses valores em posições específicas dentro desta matriz altera a forma nas suas partes específicas. Podemos simplificar isto com o seguinte exemplo:
estado 1: [1, 50, 25, 40, 100]
estado 2: [0, 75, -20, 5, 120]
estado 3: [5, 0, -100, 80, 90]
Assim, podemos assumir que a forma que queremos renderizar consiste numa matriz com 5 elementos que estão a mudar com a flexibilização linear em períodos de tempo específicos. Quando a animação termina a última fase, recomeça com a primeira para nos dar a impressão de uma animação infinita.
Mas... espera - o que é exatamente a matriz apresentada acima? Tal como referi, é um caminho responsável pela apresentação de uma forma específica. Toda a mágica está incluída no d propriedade do caminho do SVG. Esta propriedade contém um conjunto de "comandos" para desenhar uma forma e cada comando tem um tipo de argumentos. A matriz acima mencionada é constituída por todos os valores (argumentos) associados a estes comandos.
A única diferença entre estes "ficheiros de estado" eram os valores de comandos específicos, uma vez que a ordem dos comandos era a mesma. Assim, toda a magia consistia em obter todos os valores e animá-los.
O assistente chamado Física
No parágrafo anterior, referi que a única magia na animação de um objeto é a criação de transições entre todas as fases de uma forma. A questão é: como fazer isso com o canvas?
A função que todos os que trabalharam com tela deve saber é requestAnimationFrame. Se estiveres a ver isto pela primeira vez, acredito sinceramente que deves começar por ler isto. Portanto, o que nos interessa nesta função é o argumento - DOMHighResTimeStamp. Parece realmente assustador, mas na prática não é assim tão difícil de entender. Podemos dizer que é uma marca temporal do tempo decorrido desde o primeiro render.
Ok, mas o que é que podemos fazer com isto? Como o requestAnimationFrame deve ser chamada recursivamente, podemos aceder a um delta de tempo entre as suas chamadas. E aqui vamos nós com a ciência! ⚛️ (ok, talvez não seja ciência de foguetões... ainda)
A física ensina-nos que o delta de uma distância é igual ao delta do tempo multiplicado pela velocidade. No nosso caso, a velocidade é constante porque queremos atingir o ponto final num determinado período de tempo. Então, vamos ver como podemos representá-lo com os estados acima:
Digamos que queremos fazer a transição entre estes estados em mil milissegundos, pelo que os valores de velocidade serão os seguintes:
delta: [ -1, 25, -45, -35, 20]
velocidade: [-1/1000, 25/1000, -45/1000, -35/1000, 20/1000]
A velocidade acima diz-nos: por cada milissegundo, vamos aumentar o valor em -1/1000. E aqui é o ponto em que podemos voltar ao nosso requestAnimationFrame e o tempo delta. O valor de uma determinada posição que queremos aumentar é o tempo delta multiplicado pela velocidade da sua posição. Outra coisa que se pode conseguir sem problemas é limitar o valor para não ultrapassar o estado para o qual se está a ir.
Quando a transição termina, chamamos outra e assim por diante. E não parece ser assim tão difícil de implementar, mas uma das principais regras do desenvolvimento de software é não perder tempo com coisas que já estão implementadas. Por isso - escolhemos um pequena biblioteca que nos permite criar estas transições sem esforço.
Foi assim que criámos uma onda animada que parece uma criatura viva.
Algumas palavras sobre a clonagem de formas
Como se pode ver, as ondas da marca The Codest não são uma única figura animada. São compostas por muitas figuras com a mesma forma, mas com tamanho e posição diferentes. Nesta etapa, vamos dar uma olhadela rápida sobre como duplicar desta forma.
Assim, o contexto do ecrã permite-nos área de desenho à escala (nos bastidores - podemos dizer que multiplica todas as dimensões passadas para os métodos drawable por "k", em que "k" é um fator de escala, por defeito definido para "1"), tornar a tela traduzidaÉ como mudar o ponto de ancoragem de uma área de desenho. E também podemos saltar entre estes estados com estes métodos: salvar e restaurar.
Estes métodos permitem-nos guardar o estado de "zero modificações" e, em seguida, renderizar um número específico de ondas no ciclo com uma tela corretamente dimensionada e traduzida. Logo a seguir, podemos voltar ao estado guardado. Isto é tudo sobre clonagem de figuras. É muito mais fácil do que clonar ovelhas, não é?
Cereja no topo
Mencionei que rejeitámos uma das potenciais soluções devido ao desempenho. A opção com o canvas é bastante rápida, mas ninguém disse que não podia ser ainda mais optimizada. Comecemos pelo facto de não nos preocuparmos com a transição de formas quando estas estão fora da janela de visualização do browser.
Há outra API de browser que os programadores adoravam - IntersecçãoObservador. Permite-nos seguir elementos específicos da página e tratar eventos que são invocados quando esses elementos aparecem ou desaparecem da janela de visualização. Neste momento - temos uma situação bastante fácil - vamos criar o estado de visibilidade, alterá-lo através dos manipuladores de eventos IntersectionObserver e simplesmente ativar/desativar o sistema de renderização para formas específicas. E ... boom, o desempenho melhorou muito! Estamos a renderizar apenas as coisas que estão visíveis na janela de visualização.
Resumo
Escolher uma forma de implementar as coisas é muitas vezes uma escolha difícil, especialmente quando as opções disponíveis parecem ter vantagens e desvantagens semelhantes. A chave para fazer uma escolha correta é analisar cada uma delas e excluir as que consideramos menos vantajosas. Nem tudo é claro - alguma solução requer mais tempo do que as outras, mas pode ser optimizada mais facilmente ou mais personalizável.
Embora estejam a aparecer novas bibliotecas JS quase todos os minutos, há coisas que não conseguem resolver. E é por isso que todos os engenheiros de front-end (e não só eles) devem conhecer as APIs dos browsers, manter-se a par das novidades técnicas e, por vezes, pensar "como seria a minha implementação desta biblioteca se tivesse de fazer isto?". Com mais conhecimento sobre navegadores, podemos construir coisas realmente sofisticadas, tomar boas decisões sobre as ferramentas que usamos e ficar mais confiantes sobre nossas código.
Ler mais:
– Ruby 3.0. Ruby e métodos de controlo da privacidade menos conhecidos
– Calem-se e levem o vosso dinheiro #1: Custos ocultos e verdadeira agilidade no processo de desenvolvimento de produtos
– Desafios do CTO - aumento de escala e crescimento de produtos de software