Terceira das doze animações.
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
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.
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 atributokeySplines
.
Dado que funcionam juntos, representei os valores dos atribues keyTimes
, keyPoints
e keySplines
em uma tabela:
keyTimes | keyPoints | keySplines |
---|---|---|
0 | 0 | 0.6, 0.3, 0.4, 0.7 |
0.5 | 0.5 | 0.6, 0.3, 0.4, 0.7 |
1 | 1 | - |
Lembrando como funcionam:
keyTimes
: define em quais momentos específicos durante a duração da animação oskeyPoints
ocorrerão, varia de0
a1
.keyPoints
: define onde o círculo deve estar ao longo do caminho, varia de0
a1
.keySplines
: define curvas de Bézier para suavizar a progressão entrekeyPoints
.
Representação em gráfico:
- Os eixos
x
ey
representam os valores dos atributoskeyTimes
ekeyPoints
, 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"
ebegin="-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 osvalues
serão assumidos pelo atributoattributeName
, varia de0
a1
.
Para o atributo values
:
attributeName="rx" | attributeName="ry" | Descrição |
---|---|---|
8 | 8 | Começa redondo |
8.4 | 7.6 | Estica horizontalmente e encolhe verticalmente, para expressar velocidade ao se mover para baixo |
8 | 8 | Volta a ser redondo ao se mover para cima |
8.4 | 7.6 | Volta a se esticar ao se mover para baixo |
8 | 8 | Volta 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>