Decomposição de círculo SVG para caminhos
Publicados: 2022-03-10Este artigo começa com uma confissão: eu gosto de codificar SVG manualmente. Nem sempre é o caso, mas muitas vezes pode parecer peculiar para pessoas que não compartilham minha predileção. Há um bom número de benefícios em poder escrever SVG manualmente, como otimizar SVGs de maneiras que uma ferramenta não pode (transformar um caminho em um caminho ou forma mais simples) ou simplesmente entender como funcionam bibliotecas como D3 ou Greensock .
Com isso dito, gostaria de olhar mais de perto as formas circulares em SVG e as coisas que podemos fazer com elas quando passamos por um círculo básico. Por que círculos? Bem, eu amo círculos. Eles são minha forma favorita.
Primeiro (espero que você já tenha visto um círculo básico em SVG antes), aqui está uma caneta que mostra um:
Muitas coisas podem ser feitas com um círculo: ele pode ser animado e pode ter cores diferentes aplicadas a ele. Ainda assim, há duas coisas muito boas que você não pode fazer um círculo no SVG 1.1: Você não pode fazer outro elemento gráfico se mover ao longo do caminho do círculo (usando o elemento animateMotion
) e você não pode moldar um texto ao longo do caminho de um círculo (isso irá só será permitido após o lançamento do SVG 2.0).
Transformando nosso círculo em um caminho
Existe uma pequena ferramenta online que pode ajudá-lo a criar caminhos fora dos círculos (você pode experimentá-la aqui), mas vamos criar tudo do zero para que possamos descobrir o que realmente está acontecendo nos bastidores.
Para fazer um caminho circular, vamos fazer dois arcos, ou seja, semicírculos que completam o círculo em um caminho. Como você provavelmente notou no SVG acima, os atributos CX
, CY
e R
respectivamente definem onde o círculo é desenhado ao longo dos eixos X e Y, enquanto R
define o raio do círculo. O CX
e o CY
criam o centro do círculo, então o círculo é desenhado em torno desse ponto.
A replicação desse círculo pode ficar assim:
<path d=" M (CX - R), CY a R,R 0 1,0 (R * 2),0 a R,R 0 1,0 -(R * 2),0 " />
Observe que CX
é o mesmo que o atributo cx
do círculo; o mesmo vale para CY
e o atributo cy
do círculo, assim como R
e o atributo r
do círculo. O pequeno caractere a
é usado para definir um segmento de um arco elíptico. Você pode usar um Z
opcional (ou z
) para fechar o caminho.
A letra minúscula a
denota o início de um arco elíptico desenhado em relação à posição atual — ou em nosso caso específico:
<path d=" M 25, 50 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 " />
Você pode ver a mágica acontecendo nesta caneta:
Escondido embaixo do caminho está um círculo com um preenchimento vermelho. Conforme você brinca com os valores do caminho, você verá esse círculo desde que o caminho cubra totalmente o círculo (o caminho em si é um círculo do mesmo tamanho), e saberemos que estamos fazendo as coisas certas .
Uma coisa que você também deve saber é que, enquanto estiver desenhando arcos relativos, não precisará repetir a
comando a para cada arco desenhado. Quando suas primeiras 7 entradas forem feitas para o seu arco, as segundas 7 entradas serão feitas para o próximo arco.
Você pode tentar isso com a caneta acima removendo o segundo a
no caminho:
a 25,25 0 1,1 50,0 25,25 0 1,1 -50,0
Isso pode parecer o mesmo, mas prefiro deixá-lo até estar pronto para terminar um desenho, e isso também me ajuda a acompanhar onde estou.
Como funciona este caminho
Primeiro, nos movemos para uma coordenada X,Y
absolutamente posicionada na imagem. Ele não desenha nada lá - apenas se move para lá. Lembre-se que para um elemento de círculo CX
, CY
denota o centro do círculo; mas como acontece no arco elíptico, os verdadeiros CX
e CY
do arco serão calculados a partir das outras propriedades desse arco.
Em outras palavras, se queremos que nosso CX
esteja em 50
e nosso raio seja 25
, precisamos mover para 50 - 25
(se estivermos desenhando da esquerda para a direita, é claro). Isso significa que nosso primeiro arco é desenhado de 25 X, 50 Y
o que resulta em nosso primeiro arco sendo 25,25 0 1,0 50,0
.
Vamos detalhar o que o valor 25,25 0 1,0 50,0
do nosso arco realmente significa:
-
25
: O raio X relativo do arco; -
25
: O raio Y relativo do arco; -
0 1,0
: Eu não vou falar sobre os três valores centrais (rotação, large-arc-flag e as propriedades sweep-flag) porque eles não são muito importantes no contexto do exemplo atual, desde que eles são os mesmos para ambos os arcos; -
50
: A coordenada X final (relativa) do arco; -
0
: A coordenada Y final (relativa) do arco.
O segundo arco é um 25,25 0 1,0 -50,0
. Tenha em mente que este arco começará a desenhar de onde o último arco parou de desenhar. Claro, os raios X e Y são os mesmos ( 25
), mas a coordenada X final é -50
de onde está a atual.
Obviamente, este círculo poderia ter sido desenhado de muitas maneiras diferentes. Este processo de transformar um círculo em um caminho é conhecido como decomposição. Na especificação SVG 2, a decomposição de um círculo será feita com 4 arcos, no entanto, o método recomendado ainda não é possível de ser usado, pois atualmente depende de um recurso chamado caminho fechado de conclusão de segmento que ainda não foi especificado.
Para mostrar que podemos desenhar o círculo de várias maneiras, preparei uma pequena caneta com vários exemplos:
Se você olhar mais de perto, verá nosso círculo original junto com cinco exemplos diferentes de como desenhar caminhos no topo desse círculo. Cada caminho tem um elemento desc
filho que descreve o uso dos valores CX
, CY
e R
para construir o círculo. O primeiro exemplo é o que discutimos aqui, enquanto três outros usam variações que devem ser compreensíveis a partir da leitura do código; os últimos exemplos usam quatro arcos semicirculares em vez de dois, replicando um pouco o processo descrito nas especificações do SVG 2 vinculadas acima.
Os círculos são colocados em camadas uns sobre os outros usando o z-indexing natural do SVG de colocar os elementos que vêm mais tarde na marcação em cima dos que vêm mais cedo.
Se você clicar nos caminhos circulares na caneta, o primeiro clique imprimirá como o caminho está estruturado no console e adicionará uma classe ao elemento para que você veja a cor do traço de como o círculo é desenhado (você pode ver que o primeiro círculo é desenhado com uma cunha inicial do traço). O segundo clique removerá o círculo para que você possa interagir com o círculo abaixo.
Cada círculo tem uma cor de preenchimento diferente; o elemento círculo real é amarelo e dirá "Você clicou no círculo" para o console sempre que for clicado. Você também pode, é claro, simplesmente ler o código, pois os elementos desc
são bastante diretos.
Indo de um caminho para um círculo
Suponho que você tenha notado que, embora existam muitas maneiras diferentes de desenhar o círculo, os caminhos usados ainda parecem bastante semelhantes. Muitas vezes - especialmente na saída de SVGs de um programa de desenho - os círculos serão representados por caminhos. Isso provavelmente se deve à otimização do código do programa gráfico; uma vez que você tenha o código para desenhar um caminho, você pode desenhar qualquer coisa, então apenas use isso. Isso pode levar a SVGs um pouco inchados que são difíceis de raciocinar.
Leitura recomendada : “ Dicas para criar e exportar melhores SVGs para a Web” por Sara Soueidan
Vamos pegar o seguinte SVG da Wikipedia como exemplo. Quando você olhar para o código desse arquivo, verá que ele tem muito trabalho de editor depois de executá-lo no SVGOMG de Jake Archibald! (sobre o qual você pode ler mais aqui), você terminará com algo como o arquivo a seguir, que foi bastante otimizado, mas os círculos no documento ainda são renderizados como caminhos:
Então, vamos ver se podemos descobrir o que esses círculos deveriam ser se fossem elementos de círculo reais, dado o que sabemos sobre como os caminhos funcionam. O primeiro caminho no documento obviamente não é um círculo enquanto os próximos dois são (mostrando apenas o atributo d
):
M39 20a19 19 0 1 1-38 0 19 19 0 1 1 38 0z
M25 20a5 5 0 1 1-10 0 5 5 0 1 1 10 0z
Então, lembrando que o segundo a
pode ser deixado de fora, vamos reescrever isso para fazer um pouco mais de sentido. (O primeiro caminho é o grande círculo.)
M39 20 a19 19 0 1 1-38 0 a19 19 0 1 1 38 0z
Esses arcos são obviamente os seguintes:
aR R 0 1 1 - (R * 2) 0 aR R 0 1 1 (R * 2) 0
Isso significa que o raio do nosso círculo é 19
, mas quais são os nossos valores CX
e CY
? Acho que nosso M39
é na verdade CX + R
, o que significa que CX
é 20
e CY
é 20
também.
Digamos que você adicione um círculo depois de todos os caminhos como este:
<circle fill="none" stroke-width="1.99975" stroke="red" r="19" cx="20" cy="20" />
Você verá que está correto e que o círculo traçado vermelho cobre exatamente o círculo grande. O segundo caminho circular reformulado fica assim:
M25 20 a5 5 0 1 1-10 0 5 5 0 1 1 10 0z
Obviamente, o raio é 5
, e aposto que nossos valores de CX
e CY
são os mesmos de antes: - 20
.
Nota : Se CX = 20
, então CX + R = 25
. O círculo está dentro do maior no centro, então obviamente deve ter os mesmos valores de CX
e CY
.
Adicione o seguinte círculo no final dos caminhos:
<circle fill="yellow" r="5" cx="20" cy="20" />
Agora você pode ver que isso está correto dando uma olhada na seguinte caneta:
Agora que sabemos quais devem ser os círculos, podemos remover esses caminhos desnecessários e realmente criar os círculos - como você pode ver aqui:
Usando nosso caminho circular para quebrar o texto
Agora que temos nossos círculos em caminhos, podemos envolver o texto nesses caminhos. Abaixo está uma caneta com os mesmos caminhos que nossa caneta “All Circles” anterior, mas com texto envolvido no caminho. Sempre que você clicar em um caminho, esse caminho será excluído e o texto será agrupado no próximo caminho disponível, assim:
Olhando para os diferentes caminhos, você verá pequenas diferenças entre cada um (mais sobre isso daqui a pouco), mas primeiro há uma pequena incompatibilidade entre navegadores a ser vista - especialmente perceptível no primeiro caminho:
Desenvolvedor Firefox | |
cromada | |
Microsoft borda |
A razão pela qual o “S” inicial de “Smashing” está nesse ângulo engraçado na solução Firefox é que é onde realmente começamos a desenhar nosso caminho (devido ao comando vR que usamos). Isso é mais óbvio na versão do Chrome, onde você pode ver claramente a primeira fatia em forma de torta do nosso círculo que desenhamos:
O Chrome não segue todas as cunhas, então esse é o resultado quando você altera o texto para “Smashing Magazine”. |
O motivo é que o Chrome tem um bug em relação à herança do atributo textLength
declarado no elemento de text
pai. Se você quiser que ambos tenham a mesma aparência, coloque o atributo textLength
no elemento textPath
, bem como o texto. Por quê? Porque acontece que o Firefox Developer tem o mesmo bug se o atributo textLength
não for especificado no elemento de text
(este tem sido o caso há alguns anos).
O Microsoft Edge tem um bug totalmente diferente; ele não pode manipular espaços em branco entre o Text
e o elemento TextPath
filho. Depois de remover o espaço em branco e colocar o atributo textLength
nos elementos text
e textPath
, todos eles parecerão relativamente iguais (com pequenas variações devido a diferenças nas fontes padrão e assim por diante). Então, três bugs diferentes em três navegadores diferentes - é por isso que as pessoas geralmente preferem trabalhar com bibliotecas!
A caneta a seguir mostra como os problemas podem ser corrigidos:
Também removi as várias cores de preenchimento porque facilita a visualização da quebra de texto. Remover as cores de preenchimento significa que minha pequena função para permitir que você percorra os caminhos e veja como eles se parecem não funcionará a menos que eu adicione um atributo pointer-events="all"
, então adicionei-os também.
Observação : você pode ler mais sobre as razões para isso em “Gerenciando a interação SVG com a propriedade Pointer Events” explicada por Tiffany B. Brown.
Já discutimos o encapsulamento do caminho multiarco, então vamos agora ver os outros. Como temos um caminho no qual estamos envolvendo, o texto sempre se moverá na mesma direção.
Imagem | Caminho | Explicação |
---|---|---|
M CX, CY a R, R 0 1,0 -(R * 2), 0 a R, R 0 1,0 R * 2, 0 e usa a função de translate para mover +R no eixo X. | A posição inicial do nosso textPath (já que não a especificamos de forma alguma) é determinada pelo nosso primeiro arco final -(R * 2) , dado o raio que o próprio arco possui. | |
M (CX + R), CY a R,R 0 1,0 -(R * 2),0 a R,R 0 1,0 (R * 2),0 | O mesmo se aplica ao caminho anterior. | |
M CX CY m -R, 0 a R,R 0 1,0 (R * 2),0 a R,R 0 1,0 -(R * 2),0 | Como estamos terminando em (R * 2 ) em nosso primeiro arco, obviamente começaremos na posição oposta. Em outras palavras, este começa onde nossos dois caminhos anteriores terminaram. | |
M (CX - R), CY a R,R 0 1, 1 (R * 2),0 a R,R 0 1, 1 -(R * 2),0 | Isso começa na mesma posição que o último devido a (R * 2) , mas está rodando no sentido horário porque definimos a propriedade sweep-flag (marcada em amarelo) como 1 . |
Vimos como quebrar o texto em um único caminho em um círculo. Vamos agora dar uma olhada em como podemos dividir esse caminho em dois caminhos e os benefícios que você pode obter com isso.
Quebrando nossos caminhos em partes
Há muitas coisas que você pode fazer com o texto em seu caminho, ou seja, obter efeitos estilísticos com elementos tspan
, definir o deslocamento do texto ou animar o texto. Basicamente, tudo o que você fizer será restringido pelo próprio caminho. Mas ao dividir nossos caminhos multiarco em caminhos de arco único, podemos brincar com a direção do nosso texto, a indexação z de diferentes partes do nosso texto e obter animações mais complexas.
Primeiro, vamos querer usar outra imagem SVG para mostrar alguns dos efeitos. Usarei o diamante do artigo sobre eventos de ponteiro que mencionei anteriormente. Primeiro, vamos mostrar como ficará com um texto circular de caminho único colocado sobre ele.
Vamos supor que nosso círculo seja CX 295, CY 200, R 175
. Agora, seguindo o método do caminho circular, agora vemos o seguinte:
M (CX - R), CY a R,R 0 1,1 (R * 2),0 a R,R 0 1,1 -(R * 2),0
Não vou falar sobre o caminho ou o tamanho do texto, preenchimento ou cor do traço. Todos nós devemos entender isso agora e ser capazes de fazer com que seja o que quisermos. Mas olhando para o texto, podemos ver algumas desvantagens ou limitações imediatamente:
- Todo o texto corre em uma direção;
- Pode ser bom ter parte do texto por trás da ametista, especialmente onde diz MAGAZINE. Para fazer o 'M' e o 'E' se alinharem no círculo, o 'A' tem que estar no lado inferior da ametista, que parece meio desequilibrado de outra maneira. (Sinto que o 'A' deve ser posicionado com precisão e apontando para baixo nesse ponto.)
Se quisermos corrigir esses problemas, precisamos dividir nosso caminho único em dois. Na caneta a seguir, separei o caminho em dois caminhos (e os coloquei na área defs
do SVG para nossos textPath
s para referência):
Novamente, assumindo que nosso CX
é 295, CY 200, R 175
, então os dois caminhos estão no formato a seguir (para o caminho semicircular superior):
M (CX - R), CY a R,R 0 1,1 (R * 2),0
E o seguinte para o fundo:
M (CX + R), CY a R,R 0 1,1 -(R * 2),0
No entanto, ainda temos textos circulares que se movem todos na mesma direção. Para corrigir isso para tudo, menos para o Edge, tudo o que você precisa fazer é adicionar o atributo side="right"
ao elemento de text
que contém o 'MAGAZINE' textPath
.
Fazendo o texto ir em outra direção
Se quisermos oferecer suporte a tantos navegadores quanto pudermos, temos que alterar o caminho e não confiar no atributo side
que não é totalmente suportado. O que podemos fazer é copiar nosso caminho do semicírculo superior, mas alterar a varredura de 1
para 0
:
Antes de:
M 120, 200 a 175,175 0 1,
M 120, 200 a 175,175 0 1,
1
350,0
350,0
Depois de:
M 120, 200 a 175,175 0 1,
M 120, 200 a 175,175 0 1,
0
350,0
350,0
Mas nosso texto agora está desenhado no círculo interno definido pela varredura e não ficará tão bonito em diferentes navegadores. Isso significa que teremos que mover a posição do nosso caminho para alinhar com o 'S' de 'Smashing', aumentar o X final do caminho e definir algum deslocamento para o texto. Como você pode ver, também há uma pequena diferença de texto entre o Firefox e os outros que podemos melhorar aumentando o atributo textLength
no elemento de text
, bem como removendo o espaço em branco do textPath
(já que o Firefox evidentemente acha que o espaço em branco é significativo).
A solução:
Alterar o índice Z de parte do nosso texto circular
Finalmente, queremos que nosso texto vá tanto na frente quanto atrás da ametista. Bem, isso é fácil. Lembre-se de que a indexação z do elemento do SVG é baseada em onde eles estão na marcação? Portanto, se tivermos dois elementos, o elemento 1
será desenhado atrás do elemento 2
. Em seguida, tudo o que precisamos fazer é mover um elemento de text
para cima em nossa marcação SVG para que seja desenhado antes da ametista.
Você pode ver o resultado abaixo em que partes da palavra 'MAGAZINE' estão ocultas pelo ponto inferior da ametista.
Se você der uma olhada na marcação, verá que o semicírculo inferior do texto foi movido para antes do caminho que desenha a ametista.
Animando as partes do nosso círculo
Portanto, agora temos a capacidade de fazer texto circular controlando completamente a direcionalidade das partes do nosso texto, colocando o texto em dois semicírculos. Isso também pode, é claro, ser explorado para fazer animações do texto. Fazer animações SVG entre navegadores é realmente o assunto de outro artigo (ou muito mais artigos). Esses exemplos funcionarão apenas no Chrome e no Firefox devido ao uso da sintaxe de animações SMIL em vez de quadros-chave CSS ou ferramentas como Greensock. Mas dá um bom indicador dos efeitos que você pode obter ao animar o círculo decomposto.
Pegue a seguinte caneta:
Por favor, pressione o botão 'Reexecutar' no codepen para ver a animação em ação. As duas partes do nosso texto circular começam a se animar ao mesmo tempo, mas têm uma duração diferente, então terminam em momentos diferentes. Como estamos animando o atributo textLength
, colocamos duas diretivas de animate
em cada texto — uma para o elemento text
(para que o Firefox funcione) e outra para o elemento textpath
(para que o Chrome funcione).
Conclusão
Neste artigo, vimos como transformar um círculo em um caminho e vice-versa, para entender melhor quando precisamos otimizar um caminho e quando não. Vimos como transformar o círculo em um caminho nos libera para colocar o texto no caminho circular, mas também como dividir ainda mais o caminho circular em semicírculos e obter controle total sobre a direcionalidade e animação das partes componentes do nosso texto circular .
Leitura adicional no SmashingMag:
- Repensando o SVG responsivo
- Animando arquivos SVG com SVGator
- Estilizando e animando SVGs com CSS
- Gerenciando a interação SVG com a propriedade Pointer Events