Como abordar animações SVG em CSS
Publicados: 2022-03-11Animações são uma parte onipresente da web. Ao contrário das imagens GIF piscantes que atormentavam os sites nos primeiros dias da internet, as animações de hoje são mais sutis e de bom gosto. Designers e especialistas em front-end os usam para tornar os sites mais sofisticados, aprimorar a experiência do usuário, chamar a atenção para elementos importantes e transmitir informações.
Os desenvolvedores da Web podem se beneficiar da combinação do poder do SVG e do CSS para criar animações sem usar bibliotecas externas. Este tutorial de animação SVG mostra como criar animações personalizadas para projetos do mundo real.
Animação SVG usando CSS: conceitos básicos
Antes de animar SVGs com CSS, os desenvolvedores precisam entender como os SVGs funcionam internamente. Felizmente, é semelhante ao HTML: definimos elementos SVG com sintaxe XML e os estilizamos com CSS, como se fossem HTML.
Os elementos SVG são construídos propositadamente para desenhar gráficos. Podemos usar <rect> para desenhar um retângulo, <circle> para desenhar círculos, etc.—SVG também define <ellipse> , <line> , <polyline> , <polygon> e <path> .
Nota: A lista completa de elementos SVG inclui até <animate> , que permite criar animações usando a linguagem de integração multimídia sincronizada (SMIL). No entanto, seu futuro é incerto e a equipe do Chromium recomenda usar uma abordagem baseada em CSS ou JavaScript para animar SVGs sempre que possível.
Os atributos disponíveis dependem do elemento, portanto, enquanto <rect> possui os atributos width e height , o elemento <circle> possui o atributo r , que define seu raio.
Enquanto a maioria dos elementos HTML podem ter filhos, a maioria dos elementos SVG não podem. Uma exceção é o elemento de grupo <g> , que podemos usar para aplicar estilos e transformações CSS a vários elementos de uma só vez.
O elemento <svg> e seus atributos
Outra diferença importante entre HTML e SVG é como posicionamos os elementos, principalmente por meio do atributo viewBox de um determinado elemento <svg> externo. Seu valor consiste em quatro números separados por espaço em branco ou vírgula: min-x , min-y , width e height . Juntos, eles especificam quanto do nosso desenho SVG queremos que o navegador renderize. Essa área será dimensionada para caber nos limites da viewport , conforme definido pelos atributos de width e height do elemento <svg> .
Quando se trata de letterboxing, a proporção dos atributos de width e height da janela de visualização pode realmente diferir da proporção das partes de width e height do atributo viewBox .
Por padrão, a proporção da tela SVG será preservada às custas de um viewBox maior do que o especificado, causando uma renderização menor e em letterbox dentro da viewport. Mas você pode especificar um comportamento diferente por meio do atributo preserveAspectRatio .
Isso nos permite desenhar imagens isoladamente e ter certeza de que todos os elementos serão posicionados corretamente, independentemente do contexto ou do tamanho da renderização.
Embora você possa codificar imagens SVG manualmente, imagens mais complexas podem exigir um programa de gráficos vetoriais (nosso tutorial de animação SVG demonstra ambas as técnicas). Meu editor de escolha é o Affinity Designer, mas qualquer editor deve fornecer funcionalidade suficiente para as operações simples abordadas aqui.
Transições e animações CSS
As transições CSS nos permitem definir a taxa e a duração das alterações de propriedade. Em vez de saltar instantaneamente do valor inicial para o valor final, os valores transitam suavemente como neste exemplo em que a cor de um círculo SVG muda quando você passa o mouse sobre ele:
Veja o exemplo Pen Transition de Filip Defar (@dabrorius) no CodePen.
Podemos definir transições com a propriedade de transition , que aceita o nome da propriedade que queremos fazer a transição, a duração da transição, uma função de tempo de transição (também conhecida como função de atenuação) e a duração do atraso antes do início do efeito :
/* property name | duration | easing function | delay */ transition: margin-right 4s ease-in-out 1s;Podemos definir transições para várias propriedades CSS, cada uma das quais pode ter valores de transição separados. No entanto, existem duas limitações óbvias para esta abordagem.
A primeira limitação é que as transições são acionadas automaticamente quando o valor de uma propriedade é alterado. Isso é inconveniente em alguns casos de uso. Por exemplo, não podemos ter uma animação que faz um loop infinito.
A segunda limitação é que as transições sempre têm duas etapas: o estado inicial e o estado final. Podemos estender a duração da animação, mas não podemos adicionar quadros-chave diferentes.
É por isso que existe um conceito mais poderoso: animações CSS. Com animações CSS, podemos ter vários quadros-chave e um loop infinito:
Veja o exemplo Pen Animation de Filip Defar (@dabrorius) no CodePen.
Para animar propriedades CSS em vários quadros-chave, primeiro precisamos definir os quadros-chave usando uma regra @keyframes . O tempo dos quadros-chave é definido em unidades relativas (porcentagens) porque, neste momento, ainda não definimos a duração da animação. Cada quadro-chave descreve os valores de uma ou mais propriedades CSS naquele momento. As animações CSS garantirão transições suaves entre os quadros-chave.
Aplicamos a animação com os keyframes descritos ao elemento desejado usando a propriedade animation . Semelhante à propriedade de transition , ela aceita uma duração, uma função de atenuação e um atraso.
A única diferença é que o primeiro parâmetro é nosso nome @keyframes em vez de um nome de propriedade:
/* @keyframes name | duration | easing-function | delay */ animation: my-sliding-animation 3s linear 1s;Animando uma alternância de menu de hambúrguer
Agora que temos uma compreensão básica de como funciona a animação de SVGs, podemos começar a criar uma animação clássica - uma alternância de menu que alterna suavemente entre um ícone de "hambúrguer" e um botão Fechar (um "X"):
Veja o Pen Hamburger de Filip Defar (@dabrorius) no CodePen.
Esta é uma animação sutil, mas valiosa. Chama a atenção do usuário, informando que o ícone pode ser usado para fechar o menu.
Começamos nossa demonstração criando um elemento SVG com três linhas:
<svg class="hamburger"> <line x1="0" y1="50%" x2="100%" y2="50%" class="hamburger__bar hamburger__bar--top" /> <line x1="0" y1="50%" x2="100%" y2="50%" class="hamburger__bar hamburger__bar--mid" /> <line x1="0" y1="50%" x2="100%" y2="50%" class="hamburger__bar hamburger__bar--bot" /> </svg> Cada linha tem dois conjuntos de atributos. O x1 e y1 representam as coordenadas do início da linha, enquanto x2 e y2 representam as coordenadas do final da linha. Usamos unidades relativas para definir posições. Essa é uma maneira simples de garantir que o conteúdo da imagem seja redimensionado para caber no elemento SVG contido. Embora essa abordagem funcione nesse caso, há uma grande desvantagem: não podemos manter a proporção dos elementos posicionados dessa maneira. Para isso, teríamos que usar o atributo viewBox do elemento <svg> .
Observe que aplicamos classes CSS a elementos SVG. Existem muitas propriedades que podem ser alteradas via CSS, então vamos aplicar alguns estilos básicos aos nossos elementos SVG.
Vamos definir o tamanho do elemento <svg> , bem como alterar o tipo de cursor para indicar que é clicável. Mas para definir a cor e a espessura das linhas, usaremos as propriedades stroke e stroke-width . Você pode ter esperado usar color ou border , mas ao contrário do próprio <svg> , os subelementos SVG não são elementos HTML, então eles geralmente têm nomes de propriedades diferentes:
.hamburger { width: 62px; height: 62px; cursor: pointer; } .hamburger__bar { stroke: white; stroke-width: 10%; } Se renderizarmos neste ponto, veremos que todas as três linhas têm o mesmo tamanho e posição, sobrepondo-se completamente umas às outras. Infelizmente, não podemos alterar as posições inicial e final independentemente via CSS, mas podemos mover elementos inteiros. Vamos mover as barras superior e inferior com a propriedade transform CSS:
.hamburger__bar--top { transform: translateY(-40%); } .hamburger__bar--bot { transform: translateY(40%); }Ao mover as barras no eixo Y, acabamos com um hambúrguer de aparência decente.
Agora é hora de codificar nosso segundo estado: o botão fechar. Contamos com uma classe CSS .is-opened aplicada ao elemento SVG para alternar entre os dois estados. Para tornar o resultado mais acessível, vamos envolver nosso SVG em um elemento <button> e lidar com cliques nesse nível.
O processo de adicionar e remover a classe será tratado por um simples trecho de JavaScript:
const hamburger = document.querySelector("button"); hamburger.addEventListener("click", () => { hamburger.classList.toggle("is-opened"); }); Para criar nosso X, podemos aplicar uma propriedade de transform diferente às nossas barras de hambúrguer. Como a nova propriedade de transform substituirá a antiga, nosso ponto de partida será a posição original e compartilhada das três barras.
A partir daí, podemos girar a barra superior 45 graus no sentido horário em torno de seu centro e girar a barra inferior 45 graus no sentido anti-horário. Podemos encolher a barra do meio horizontalmente até que fique estreita o suficiente para ficar escondida atrás do centro do X:
.is-opened .hamburger__bar--top { transform: rotate(45deg); } .is-opened .hamburger__bar--mid { transform: scaleX(0.1); } .is-opened .hamburger__bar--bot { transform: rotate(-45deg); } Por padrão, a propriedade transform-origin para elementos SVG normalmente é 0,0 . Isso significa que nossas barras serão giradas em torno do canto superior esquerdo da viewport, mas queremos que elas girem em torno do centro. Para corrigir isso, vamos definir a propriedade transform-origin como center para a classe .hamburger__bar .

Animando propriedades CSS com transition
A propriedade CSS de transition informa ao navegador para fazer uma transição suave entre dois estados diferentes de propriedades CSS. Aqui queremos animar nossas mudanças na propriedade transform , que dita as posições, orientação e escala das barras.
Também podemos controlar a duração da transição usando a propriedade transition-duration da transição. Para que a animação pareça rápida, definiremos uma curta duração de 0,3 segundos:
.hamburger__bar { transition-property: transform; transition-duration: 0.3s; ... }A única parte de JavaScript que precisamos é o bit que torna o estado do ícone alternável:
const hamburger = document.querySelector("button"); hamburger.addEventListener("click", () => { hamburger.classList.toggle("is-opened"); }); Aqui, selecionamos o elemento SVG externo por sua classe .mute usando querySelector() . Em seguida, adicionamos um ouvinte de evento de clique. Quando um evento de clique é acionado, alternamos a classe .is-active apenas no próprio <svg> mais profundo na hierarquia. Como fizemos com que a animação CSS se aplicasse apenas a elementos com a classe .is-active , alternar essa classe ativará e desativará a animação.
Como toque final, converteremos o corpo HTML em um contêiner flexível, o que nos ajudará a centralizar o ícone horizontalmente e verticalmente. Também atualizaremos a cor do plano de fundo para um cinza muito escuro e a cor do ícone para branco, para obter uma aparência elegante de “modo escuro”:
body { display: flex; justify-content: center; align-items: center; background-color: #222; height: 100vh; }Com isso, criamos um botão animado totalmente funcional usando CSS básico e um pequeno trecho de JavaScript. É fácil alterar as transformações que aplicamos para fazer uma variedade de animações. Os leitores podem simplesmente bifurcar o CodePen - que inclui um pouco de CSS extra para polimento - e ser criativos.
Trabalhando com dados SVG de editores externos
Nosso menu de hambúrguer é extremamente simples. E se quisermos fazer algo mais complexo? É aí que codificar SVG manualmente se torna difícil e o software de edição de gráficos vetoriais pode ajudar.
Nossa segunda animação SVG é um botão mudo mostrando um ícone de fone de ouvido. Quando a música estiver ativa, o ícone irá pulsar e dançar; quando estiver silenciado, o ícone será riscado:
Veja o botão Pen Mute - 5 - tachado vermelho por Filip Defar (@dabrorius) no CodePen.
Desenhar ícones estaria fora do escopo deste tutorial (e provavelmente da descrição do seu trabalho), então vamos começar com um ícone SVG pré-desenhado. Também queremos o mesmo estilo de body do nosso exemplo de menu de hambúrguer.
Você pode querer limpar o código SVG antes de trabalhar com ele. Você pode fazer isso com o svgo, uma ferramenta de otimização de SVG de código aberto baseada em Node.js. Isso removerá elementos desnecessários e facilitará a edição manual do código, o que você precisará fazer para adicionar classes e combinar diferentes elementos.
É improvável que os ícones SVG criados em software de edição de imagem usem unidades relativas. Além disso, queremos garantir que a proporção do ícone seja mantida, independentemente da proporção do elemento SVG que o contém. Para possibilitar esse nível de controle, usaremos o atributo viewBox .
É uma boa ideia redimensionar o SVG para que viewBox possa ser definido com alguns valores fáceis de usar. Neste caso, eu o converti em um viewBox que tem 100 x 100 pixels.
Vamos garantir que o ícone esteja centralizado e com o tamanho adequado. Aplicaremos a classe mute ao nosso elemento SVG base e, em seguida, adicionaremos os seguintes estilos CSS:
.mute { fill: white; width: 80px; height: 70px; cursor: pointer; } Aqui, a width é um pouco maior que a height para evitar cortes durante as rotações de nossa animação.
Nosso ponto de partida de animação SVG
O SVG agora limpo contém um único elemento <g> que contém três elementos <path> .
O elemento path nos permite desenhar linhas, curvas e arcos. Os caminhos são descritos com uma série de comandos que descrevem como a forma deve ser desenhada. Como nosso ícone consiste em três formas desconexas, temos três caminhos para descrevê-las.
O elemento g SVG é um contêiner usado para agrupar outros elementos SVG. Nós o usamos para aplicar as transformações pulsantes e dançantes em todos os três caminhos simultaneamente.
<svg class="mute" viewBox="0 0 100 100"> <g> <path d="M92.6,50.075C92.213,26.775 73.25,7.938 50,7.938C26.75,7.938 7.775,26.775 7.388,50.075C3.112,51.363 -0.013,55.425 -0.013,60.25L-0.013,72.7C-0.013,78.55 4.575,83.3 10.238,83.3L18.363,83.3L18.363,51.6C18.4,51.338 18.438,51.075 18.438,50.813C18.438,33.275 32.6,19 50,19C67.4,19 81.563,33.275 81.563,50.813C81.563,51.088 81.6,51.338 81.638,51.6L81.638,83.313L89.763,83.313C95.413,83.313 100.013,78.563 100.013,72.713L100.013,60.263C100,55.438 96.875,51.362 92.6,50.075Z" /> <path d="M70.538,54.088L70.538,79.588C70.538,81.625 72.188,83.275 74.225,83.275L74.225,83.325L78.662,83.325L78.662,50.4L74.225,50.4C72.213,50.4 70.538,52.063 70.538,54.088Z" /> <path d="M25.75,50.4L21.313,50.4L21.313,83.325L25.75,83.325L25.75,83.275C27.788,83.275 29.438,81.625 29.438,79.588L29.438,54.088C29.45,52.063 27.775,50.4 25.75,50.4Z" /> </g> </svg> Para fazer os fones de ouvido pulsarem e dançarem, a transition não será suficiente. Este é um exemplo que é complexo o suficiente para precisar de quadros-chave.
Nesse caso, nossos quadros-chave inicial e final (em 0% e 100% da animação, respectivamente) usam um ícone de fones de ouvido ligeiramente encolhido. Nos primeiros 40% da animação, aumentamos um pouco a imagem e a inclinamos 5 graus. Então, para os próximos 40% da animação, reduzimos para 0,9x e giramos 5 graus para o outro lado. Finalmente, para os últimos 20% da animação, a transformação do ícone retorna aos mesmos parâmetros iniciais para fazer um loop suave.
@keyframes pulse { 0% { transform: scale(0.9); } 40% { transform: scale(1) rotate(5deg); } 80% { transform: scale(1) rotate(-5deg); } 100% { transform: scale(0.9) rotate(0); } }Otimizações de animação CSS
Para mostrar como os quadros-chave funcionam, deixamos nosso CSS de quadro-chave mais detalhado do que precisa ser. Há três maneiras de encurtá-lo.
Como nosso quadro-chave 100% define toda a lista de transform , se omitirmos rotate() inteiramente, seu valor padrão seria 0:
100% { transform: scale(0.9); } Em segundo lugar, sabemos que queremos que nossos quadros-chave 0% e 100% correspondam porque estamos fazendo um loop na animação. Ao defini-los com a mesma regra CSS, não teremos que lembrar de modificar ambos se quisermos alterar este ponto compartilhado no loop de animação:
0%, 100% { transform: scale(0.9); } Por fim, em breve aplicaremos transform: scale(0.9); para a classe mute__headphones e, quando o fizermos, não precisaremos definir os quadros-chave inicial e final! Eles serão padronizados para o estilo estático usado por mute__headphones .
Agora que definimos os quadros-chave de animação, podemos aplicar a animação. Adicionamos a classe .mute__headphones ao elemento <g> para que ela afete todas as três partes do ícone de fones de ouvido. Primeiro, mais uma vez definimos transform-origin como center , pois queremos que o ícone gire em torno de seu centro. Também o dimensionamos para que seu tamanho corresponda ao quadro-chave da animação inicial. Sem esta etapa, alternar do ícone estático “silenciado” para o animado sempre resultará em um salto repentino de tamanho. (De qualquer forma, voltar para silenciado causará um salto na escala - e provavelmente também na rotação - se o usuário clicar enquanto a escala for maior que 0,9x. Não podemos fazer muito sobre esse efeito apenas com CSS.)
Aplicamos a animação usando a propriedade CSS animation , mas somente quando a classe pai .is-active está presente, semelhante à forma como animamos nosso menu de hambúrguer.
.mute__headphones { transform-origin: center; transform: scale(0.9); } .is-active .mute__headphones { animation: pulse 2s infinite; }O JavaScript que precisamos para alternar entre os estados segue o mesmo padrão do menu hambúrguer também:
const muteButton = document.querySelector(".mute"); muteButton.addEventListener("click", () => { muteButton.classList.toggle("is-active"); }); A próxima peça que adicionaremos é uma linha tachada que aparece quando o ícone está inativo. Como este é um elemento de design simples, podemos codificá-lo manualmente. É aqui que é útil ter valores viewBox simples e razoáveis. Sabemos que as bordas da tela estão em 0 e 100, então é fácil calcular as posições inicial e final da nossa linha:
<line x1="12" y1="12" x2="88" y2="88" class="mute__strikethrough" />Redimensionando vs. Usando Unidades Relativas
Um caso pode ser feito para usar unidades relativas em vez de redimensionar a imagem. Isso se aplica em nosso exemplo porque estamos adicionando apenas uma linha SVG simples sobre nosso ícone.
Em cenários do mundo real, você pode querer combinar conteúdo SVG mais complexo de várias fontes diferentes. É aqui que é útil torná-los todos de um tamanho uniforme, pois não podemos codificar manualmente os valores relativos como fizemos em nosso exemplo.
Como aplicamos uma classe diretamente ao nosso elemento <line> tachado, podemos estilizá-lo via CSS. Só precisamos garantir que a linha não esteja visível quando o ícone estiver ativo:
.mute__strikethrough { stroke: red; opacity: 0.8; stroke-width: 12px; } .is-active .mute__strikethrough { opacity: 0; } Opcionalmente, podemos adicionar a classe .is-active diretamente ao SVG. Isso fará com que a animação comece assim que a página for carregada, então alteramos efetivamente o estado inicial do ícone de não animado (silenciado) para animado (sem som).
Animação SVG baseada em CSS veio para ficar
Nós apenas arranhamos a superfície das técnicas de animação CSS e como as janelas de visualização funcionam. Vale a pena saber escrever código SVG à mão para manter simples animações simples, mas também é importante saber como e quando usar gráficos criados com editores externos. Embora os navegadores modernos nos permitam criar animações impressionantes usando apenas a funcionalidade integrada, para casos de uso (muito) complexos, os desenvolvedores podem explorar bibliotecas de animação como GSAP ou anime.js.
As animações não precisam ser reservadas para projetos extravagantes. As técnicas modernas de animação CSS nos permitem criar uma ampla variedade de animações atraentes e polidas de maneira simples e compatível com vários navegadores.
Agradecimentos especiais a Mike Zeballos pela revisão técnica deste artigo!
