Criando formas animadas no React Native usando SVG e Animated API

Wes Guirra
Digital Heroes
Published in
6 min readJun 18, 2019

Fala Galera! Blz? Recentemente me deparei com um desafio ao construir uma tela de um aplicativo em React Native, onde ao rolar a página o formato do header que inicialmente é convexo vai se tornando menos convexo e por fim reto.

um header convexo por si só já é um grande desafio em reproduzir, rs.

Encontrei um design de tela no dribbble e vamos usar como base para a animação.

https://dribbble.com/shots/6643244-Financial

Vamos usar a base da imagem da direita que tem um layout parecido com o layout que queremos criar.

Para criar o header vou aproveitar as features de degradê do próprio SVG.

Pré requisitos e conhecimentos prévios desejáveis:

Estou considerando neste post que você já sabe sobre layout e também criar um projeto em React-Native, dito isto, vou ignorar toda a parte de criação e configuração do projeto.

  • React Native
  • Layout com FlexBox
  • react-native-cli
  • conhecimentos básicos de SVG

Vamos ao que importa!

A primeira coisa que vamos fazer é instalar a biblioteca react-native-svg e linkar as dependencias nativas ao nosso projeto.

yarn add react-native-svgreact-native link

Criando nossa forma com SVG

O primeiro passo é criar nosso header com a forma desejada em SVG, para isso, usei esta ferramenta de edição de path: https://jxnblk.com/paths e depois, criei meu path conforme desejado.

Dá uma olhada no link abaixo caso queira fazer a mesma forma.

Feito isso, vamos partir para a criação do componente header:

Por padrão os nossos componentes SVG não suportam animações, para trabalharmos com animações vamos precisar criar componentes animados, podemos fazer isso com o método createAnimatedComponent da Animated API passando o componente que desejamos implementar o suporte à animações como parâmetro.

const AnimatedPath = Animated.createAnimatedComponent(Path);

Para o path que criei defini as dimensões de 64 de largura por 32 altura, você pode usar qualquer tamanho, optei por tamanhos menores, dessa forma, fica mais fácil de trabalhar com os valores e mais fácil de ler também.

Vamos utilizar a tag Defs para definir nosso gradiente o qual iremos usar como fill do nosso AnimatedPath.

Dentro da tag Defs vamos definir um LinearGradient para esse componente vou atribuir um id, neste caso, será gradient mesmo x1 e y1 é o ponto inicial do meu gradiente, neste caso 0 0ou sejax: 0; y: 0 o que seria o topo esquerdo para x2 ey2 o ponto final neste caso64 que é o tamanho da minha largura e0 ou sejax: 64; y: 0 .

A rampa de cores para utilizar em um gradiente é definido pelo elemento stop que é elemento filho do elemento <linearGradient> ou do elemento <radialGradient>.

Então vamos definir nossa rampa de cores a partir de dois componentesStop. Para o primeiro vou utilizar o offset: 0 e stopColor: #CBA668 e o segundooffset: 1 e astopColor: #DDBD80 .

Depois vamos criar nosso AnimatedPath, aqui estou usando duas props: fill ed, a primeira será o preenchimento do meu Path que pode ser uma cor ou um degradê, neste caso, vamos referenciar o gradiente que criamos anteriormente usando fill="url(#gradient)". A prop d será responsável por definir a forma do nosso Path vamos utilizar aquela que construímos usando a ferramenta de path: M0 0 L64 0 L64 22 C48 32 16 32 0 22 Z.

O path é o elemento mais poderoso no SVG, para entender melhor como funciona o Path em SVG recomendo que leia a documentação do path aqui: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths (em inglês)

Criado nosso componente do Header, agora chegou a hora de utilizarmos ele:

Coloquei alguns componentes dentro do meu header apenas para torna-lo apresentável.

Animando o Header

Vamos animar o header a partir da rolagem, logo vamos precisar adicionar a proponScroll ao ScrollView que definimos no nossoApp.js.

Em seguida vamos criar um novoAnimated.Value chamadoscrollY, podemos definir esse valor dentro do state deApp.js.

constructor(props) {
super(props);
this.state = {
scrollY: new Animated.Value(0),
}
}

Gestos, como o deslocamento ou rolagem, e outros eventos podem ser mapeados diretamente para Animated Values usando o Animated.event(). Isso é feito com uma sintaxe de mapa estruturada para que os valores possam ser extraídos de objetos de eventos complexos. O primeiro nível é um Array que permite o mapeamento em vários argumentos e esse Array contém objetos aninhados.

Por exemplo, ao trabalhar com gestos de rolagem vertical, faríamos o seguinte para mapear event.nativeEvent.contentOffset.y para scrollY (um Animated.Value ):

<ScrollView
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: scrollY } } },
])}
...

Vamos precisar adicionar outra prop ao nosso ScrollView: scrollEventThrottle

Essa prop controla com que frequência o evento de rolagem será disparado durante a rolagem (como um intervalo de tempo em ms). Um número menor produz uma precisão melhor para o código que está rastreando a posição de rolagem, mas pode levar a problemas de desempenho de rolagem devido ao volume de informações sendo enviadas pela ponte do React Native. Nós não vamos notar diferença entre valores definidos entre 1-16 quando o loop de execução JS for sincronizado com a taxa de atualização da tela. Sempre que não precisarmos de rastreamento preciso da posição de rolagem, podemos definir esse valor para limitar as informações enviadas pela ponte. O valor padrão é zero, o que resulta no envio do evento de rolagem apenas uma vez a cada vez que a tela é rolada.

Neste caso vamos definir esse valor para 16 ficando assim nossa ScrollView:

<ScrollView
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: scrollY } } },
])}
scrollEventThrottle={16}
...

Neste momento já estamos mapeando o valor do scroll vertical para o nossoAnimated.Value e a partir de agora a mágica acontece, vamos passar esse valor animado para dentro do nosso SVG mas em formato de Path, ou seja vou instruir o Path a exibir determinada forma de acordo com o valor do meu scroll.

Podemos fazer isso usando o método interpolate da Animated API em nosso Animated.Value, o método interpolate() permite que os intervalos de input sejam mapeados para diferentes intervalos de output.

A partir de agora minha curva no meu elemento Path não será mais estática, vamos mapear a posição do scroll para a nossa string de curva no path, eu criei uma novo comando path, ainda é uma curva porém com a linha abaixo menos convexa (reta), para ser usado quando já tivermos rolado a tela.

O primeiro Array: inputRange define a posição do scroll, estou passando dois valores: 0 que é meu valor inicial e o scrollPos que é o calculo da altura da tela dividido por 4, já o segundo Array: outputRange define minhas curvas, também passo dois valores: o primeiro minha curva mais convexa e o segundo meu path sem a curva.

Lembrando que os comandos path ainda precisam ser uma curva, mesmo sem possuir curvas.

const scrollPos = Dimensions.get('window').height / 4;const curve = scrollY.interpolate({
inputRange: [0, scrollPos],
outputRange: [
'M0 0 L64 0 L64 22 C48 32 16 32 0 22 Z',
'M0 0 L64 0 L64 20 C48 20 16 20 0 20 Z',
]
extrapolate: 'clamp'
});

Ainda vamos precisar adicionar a propriedadeextrapolate:'clamp' ao nosso interpolate, dessa forma, vamos evitar que nossos valores extrapolem.

Por fim vamos passar nosso Animated.Value curve como prop para o nosso componente AnimatedHeader e atribui-la como valor da prop d do nossoPath.

Nosso código ficará assim:

E esse é o resultado final:

Esse é o link do projeto no github caso queira modificar a partir dele: https://github.com/digital-heroes/animated-header-svg

É isso galera! Esse foi um dos meus primeiros posts técnicos aqui no Medium espero que ajude muito.

Estou aberto a sugestões e quaisquer dúvidas foi um prazer tomar um pouco do seu tempo.

Obrigado pela leitura e até o próximo post!

--

--

Wes Guirra
Digital Heroes

Desde que me entendo por gente, procuro maneiras de inovar para melhorar o mundo em que vivemos.