Pular para o conteúdo principal

Animações de Carregamento: Março

· Leitura de 11 minutos

Terceira das doze animações.


Animações de Carregamento (série de 8 partes)
  1. Introdução
  2. Janeiro
  3. Fevereiro
  4. Março
  5. Abril
  6. Maio
  7. Junho
  8. Julho

Animação

Resultado final:

Introdução

Criei essa animação com SVG, seguindo uma estrutura parecida com as anteriores:

<!-- definições de tamanho --><svg>  <defs>    <!-- caminho que os círculos percorrem -->    <path />  </defs>  <!-- plano de fundo -->  <rect />  <!-- desenho do caminho que os círculos percorrem, para debug -->  <use />  <!-- três círculos como este -->  <ellipse>    <!-- animação que faz o círculo percorrer o caminho -->    <animateMotion>      <mpath />    </animateMotion>    <!-- animação que estica o círculo horizontalmente -->    <animate />    <!-- animação que estica o círculo verticalmente -->    <animate />  </ellipse></svg>

Como funciona

info

Omiti alguns elementos e atributos nos blocos a seguir para manter a explicação concisa.

Tamanho

Mantive o tamanho igual às animações anteriores.

Resultado



Código

<svg  xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 200 200"  width="200"  height="200">  <rect    width="199"    height="199"    x="0.5"    y="0.5"    fill="white"    stroke="lightgray"    strokeWidth="1"    rx="6"  /></svg>

Caminho

Dentro de defs, defini o path que os círculos percorrem. Ele será referenciado pelo seu id, motion-path. O caminho é o símbolo do infinito, desenhado com quatro comandos de arco (A).

  • M52,100: O caminho começa no extremo esquerdo.
    • x = 52 é a largura do quadrado (200) menos o diâmetro dos dois círculos (2 * 48 = 96) divido pelos dois lados ((200 - 96) / 2 = 52).
    • y = 100 é o centro vertical do quadrado.
  • Todos os arcos têm os mesmos valores para os quatro primeiros argumentos.
    • rx,ry = 24,24: mesmo raio horizontal e vertical.
    • x-axis-rotation = 0: sem rotação.
    • large-arc-flag = 0: o arco deve seguir o menor caminho entre os pontos de início e fim.
  • A24,24 0 0 1 100,100:
    • sweep-flag = 1: define o sentido do arco no sentido horário.
    • x = 100: centro horizontal.
    • y = 100: centro vertical.
  • A24,24 0 0 0 148,100:
    • sweep-flag = 0: define o sentido do arco no sentido anti-horário.
    • x = 148: extremo direito (200 - 52 = 148).
    • y = 100: centro vertical.
  • A24,24 0 0 0 100,100:
    • sweep-flag = 0: define o sentido do arco no sentido anti-horário.
    • x = 100: centro horizontal.
    • y = 100: centro vertical.
  • A24,24 0 0 1 52,100:
    • sweep-flag = 1: define o sentido do arco no sentido horário.
    • x = 52: extremo esquerdo (0 + 52 = 52).
    • y = 100: centro vertical.
  • z: fecha o caminho.

Depois do rect, que é o plano de fundo, defini um use para visualizar o caminho, visto que o que é definido dentro de defs não é renderizado. O elemento use é removido na conclusão da animação.

Resultado



Código

<svg>  <defs>    <path      id="motion-path"      d="M52,100 A24,24 0 0 1 100,100 A24,24 0 0 0 148,100 A24,24 0 0 0 100,100 A24,24 0 0 1 52,100 z"    />  </defs>  <rect />  <use href="#motion-path" fill="none" stroke="lightgray" stroke-width="1" /></svg>

Círculos e suas animações

A ideia era que os círculos percorressem o caminho em formato de símbolo do infinito. No momento em que os círculos se movimentassem para baixo, eles deveriam acelerar. No momento de aceleração, ele deveriam sofrer uma distorção sutil para indicar velocidade.

info

A duração final da animação é de dois segundos. Nesta seção as animações têm duração de 10 segundos para ficarem mais evidentes.

Animações que percorrem o caminho

Inicialmente, criei a animação que define o ciclo de ida e volta dos círculos. Minha intenção aqui era criar uma animação apenas, mudando o tempo de início para cada círculo.

Animando um círculo

Começando com um dos círculos, usei um elemento ellipse, pois ele permite definir o raio nos eixos x e y separadamente. O elemento mpath referencia o path#motion-path, enquanto o elemento animateMotion define a animação.

No elemento animateMotion, defini os atributos da seguinte forma:

  • repeatCount="indefinite": a animação repete indefinidamente.
  • dur="10000ms": a duração é de 10 segundos para os exemplos.
  • calcMode="spline": para poder usar curvas de Bézier no atributo keySplines.

Dado que funcionam juntos, representei os valores dos atribues keyTimes, keyPoints e keySplines em uma tabela:

keyTimeskeyPointskeySplines
000.6, 0.3, 0.4, 0.7
0.50.50.6, 0.3, 0.4, 0.7
11-

Lembrando como funcionam:

  • keyTimes: define em quais momentos específicos durante a duração da animação os keyPoints ocorrerão, varia de 0 a 1.
  • keyPoints: define onde o círculo deve estar ao longo do caminho, varia de 0 a 1.
  • keySplines: define curvas de Bézier para suavizar a progressão entre keyPoints.

Representação em gráfico:

keyPointskeyTimes101

  • Os eixos x e y representam os valores dos atributos keyTimes e keyPoints, respectivamente.
  • No topo e à direita, as linhas em cinza escuro representam os intervalos onde os valores do atributo keySplines são aplicados.
  • A curva 0.6,0.3,0.4,0.7 começa lenta, acelera no meio e desacelera no final.

Ou seja, na primeira metade da animação (keyTimes e keyPoints indo de 0 a 0.5), o círculo vai do extremo esquerdo ao direito seguindo a curva de Bézier (0.6, 0.3, 0.4, 0.7), começando com um movimento mais lento, acelerando no meio e desacelerando ao fim. Na segunda metade da animação (keyTimes e keyPoints indo de 0.5 a 1), o mesmo acontece indo do extremo direito de volta para o esquerdo.

Apenas um círculo animado



Código

<svg>  <defs>    <path />  </defs>  <rect />  <use />  <ellipse rx="8" ry="8" fill="black">    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="10000ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"    >      <mpath href="#motion-path" />    </animateMotion>  </ellipse></svg>

Animando os outros círculos

Assim como com o primeiro círculo, usei mais dois elementos ellipse. Os atributos do elemento animateMotion são os mesmos usados no primeiro círculo, com a diferença que a animação é adiantada em um terço do tempo:

  • begin="-3333.3333ms" e begin="-6666.6666ms": definem quando a animação deve começar.

Todos os círculo animados



Código

<svg>  <defs>    <path />  </defs>  <rect />  <use />  <ellipse>    <animateMotion>      <mpath />    </animateMotion>  </ellipse>  <ellipse>    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="10000ms"      begin="-3333.3333ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"    >      <mpath />    </animateMotion>  </ellipse>  <ellipse>    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="10000ms"      begin="-6666.6666ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"    >      <mpath />    </animateMotion>  </ellipse></svg>

Animações que esticam

Em seguida, fiz as animações que distorcem os círculos de alguma forma.

Animando rx e ry do primeiro círculo

Para transmitir a ideia de velocidade, criei duas animações que, em conjunto, esticam os círculos horizontal e verticalmente.

No elemento animateMotion defini rotate="auto" para que o círculo siga a rotação do caminho, fazendo que os efeitos de distorção também sigam a rotação automaticamente.

Os elementos animate possuem atributos com o mesmo valor:

  • repeatCount="indefinite": a animação repete indefinidamente.
  • dur="10000ms": a duração é de 10 segundos para os exemplos.
  • keyTimes="0; .25; .5; .75; 1": define em quais momentos específicos durante a duração da animação os values serão assumidos pelo atributo attributeName, varia de 0 a 1.

Para o atributo values:

attributeName="rx"attributeName="ry"Descrição
88Começa redondo
8.47.6Estica horizontalmente e encolhe verticalmente, para expressar velocidade ao se mover para baixo
88Volta a ser redondo ao se mover para cima
8.47.6Volta a se esticar ao se mover para baixo
88Volta a ser redondo ao se mover para cima

Apenas um círculo animado



Código

<svg>  <defs>    <path />  </defs>  <rect />  <use />  <ellipse>    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="10000ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"      rotate="auto"    >      <mpath />    </animateMotion>    <animate      attributeName="rx"      repeatCount="indefinite"      dur="10000ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 8.4; 8; 8.4; 8"    />    <animate      attributeName="ry"      repeatCount="indefinite"      dur="10000ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 7.6; 8; 7.6; 8"    />  </ellipse>  <ellipse>    <animateMotion>      <mpath />    </animateMotion>  </ellipse>  <ellipse>    <animateMotion>      <mpath />    </animateMotion>  </ellipse></svg>

Todos os círculos animados



Código

<svg>  <defs>    <path />  </defs>  <rect />  <use />  <ellipse>    <animateMotion>      <mpath />    </animateMotion>    <animate />    <animate />  </ellipse>  <ellipse>    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="10000ms"      begin="-3333.3333ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"      rotate="auto"    >      <mpath />    </animateMotion>    <animate      attributeName="rx"      repeatCount="indefinite"      dur="10000ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 8.4; 8; 8.4; 8"      begin="-3333.3333ms"    />    <animate      attributeName="ry"      repeatCount="indefinite"      dur="10000ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 7.6; 8; 7.6; 8"      begin="-3333.3333ms"    />  </ellipse>  <ellipse>    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="10000ms"      begin="-6666.6666ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"      rotate="auto"    >      <mpath />    </animateMotion>    <animate      attributeName="rx"      repeatCount="indefinite"      dur="10000ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 8.4; 8; 8.4; 8"      begin="-6666.6666ms"    />    <animate      attributeName="ry"      repeatCount="indefinite"      dur="10000ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 7.6; 8; 7.6; 8"      begin="-6666.6666ms"    />  </ellipse></svg>

Conclusão

Essa animação foi mais simples de desenvolver, principalmente porque não há interação entre os círculos e todos eles compartilham a mesma animação. Além disso, não utilizei nenhum elemento ou atributo novo desta vez, mas consegui aplicar tudo o que aprendi ao longo das animações anteriores.

Durante o desenvolvimento, utilizei JavaScript para realizar cálculos e gerar o valor do atributo d do elemento path. Isso possibilitou o teste de diferentes valores radius de forma rápida. Por exemplo:

// ellipse
const radius = 24;

// rect
const width = 200;
const height = 200;
const xCenter = width / 2;
const yCenter = height / 2;
const xMargin = (width - 2 * (2 * radius)) / 2;
const yMargin = (height - 2 * radius) / 2;

// path
const motionPath = `
M${xMargin},${yCenter}
A${radius},${radius} 0 0 1 ${xCenter},${yCenter}
A${radius},${radius} 0 0 0 ${width - xMargin},${yCenter}
A${radius},${radius} 0 0 0 ${xCenter},${yCenter}
A${radius},${radius} 0 0 1 ${xMargin},${yCenter}
z
`;

Usar JavaScript para cálculos como esse é uma prática que planejo aplicar nas próximas animações.

Resultado



Código

<svg  xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 200 200"  width="200"  height="200">  <defs>    <path      id="motion-path"      d="M52,100 A24,24 0 0 1 100,100 A24,24 0 0 0 148,100 A24,24 0 0 0 100,100 A24,24 0 0 1 52,100 z"    />  </defs>  <rect    width="199"    height="199"    x="0.5"    y="0.5"    fill="white"    stroke="lightgray"    stroke-width="1"    rx="6"  />  <ellipse rx="8" ry="8" fill="black">    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="1400ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"      rotate="auto"    >      <mpath href="#motion-path" />    </animateMotion>    <animate      attributeName="rx"      repeatCount="indefinite"      dur="1400ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 8.4; 8; 8.4; 8"    />    <animate      attributeName="ry"      repeatCount="indefinite"      dur="1400ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 7.6; 8; 7.6; 8"    />  </ellipse>  <ellipse rx="8" ry="8" fill="black">    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="1400ms"      begin="-466.6666ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"      rotate="auto"    >      <mpath href="#motion-path" />    </animateMotion>    <animate      attributeName="rx"      repeatCount="indefinite"      dur="1400ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 8.4; 8; 8.4; 8"      begin="-466.6666ms"    />    <animate      attributeName="ry"      repeatCount="indefinite"      dur="1400ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 7.6; 8; 7.6; 8"      begin="-466.6666ms"    />  </ellipse>  <ellipse rx="8" ry="8" fill="black">    <animateMotion      repeatCount="indefinite"      calcMode="spline"      dur="1400ms"      begin="-933.3333ms"      keyTimes="0; 0.5; 1"      keyPoints="0; 0.5; 1"      keySplines="0.6,0.3,0.4,0.7; 0.6,0.3,0.4,0.7"      rotate="auto"    >      <mpath href="#motion-path" />    </animateMotion>    <animate      attributeName="rx"      repeatCount="indefinite"      dur="1400ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 8.4; 8; 8.4; 8"      begin="-933.3333ms"    />    <animate      attributeName="ry"      repeatCount="indefinite"      dur="1400ms"      keyTimes="0; .25; .5; .75; 1"      values="8; 7.6; 8; 7.6; 8"      begin="-933.3333ms"    />  </ellipse></svg>

Leitura recomendada