Artigo original: How to Add Interactive Animations and Page Transitions to a Next.js Web App with Framer Motion
A web é bastante vasta e está cheia de sites e aplicações estáticas. No entanto, embora sejam estáticas, elas não necessariamente precisam ser sem graça.
Como podemos usar a Framer Motion para adicionar algumas animações às nossas aplicações para web e tornar a experiência com elas mais interativa?
- O que é o Framer Motion?
- O que vamos desenvolver?
- Etapa 0: instalando o Framer Motion na sua aplicação do Next.js
- Etapa 1: animando o título da página com Framer Motion em uma aplicação do Next.js
- Etapa 2: adicionando efeitos de hover animados com Framer Motion a elementos de uma aplicação do Next.js
- Etapa 3: adicionando transições de página com Framer Motion a uma aplicação do Next.js
- Etapa 4: usando keyframes do Framer Motion para animações avançadas
- Etapa bônus: pirando um pouco na animação da nossa aplicação do Next.js de Rick and Morty
O que é o Framer Motion?
O Framer Motion (página em inglês) é uma API que vem diretamente da API Framer. Ela fornece uma biblioteca pronta de animações e controles de gestos que facilita a criação de efeitos dinâmicos.
O que é o Framer (página em inglês)? O próprio Framer é uma ferramenta de prototipagem de UI que permite criar interfaces interativas com animações que você pode disponibilizar para a sua equipe, ao passo que a API Framer (página em inglês) é uma biblioteca do Javascript que permite fazer isso com código.
A Motion API é derivada desse trabalho, mas está convenientemente disponível como um pacote separado, que pode ser usado para controle de animações.
O que vamos desenvolver?
Vamos usar os conceitos do Framer Motion para adicionar efeitos de interação e de transição de página à nossa aplicação.

Vamos começar com algumas animações básicas que acontecem quando a página é carregada. Depois, vamos aprender como acioná-las ao passar o mouse para, então, criar um wrapper que nos permita fazer uma transição elegante de nossas páginas em Next.js.
Antes de começarmos
Esta é a segunda parte de uma série de artigos sobre a construção de uma wiki de Rick and Morty. A primeira parte (texto em inglês) tem como foco solicitar os dados da API de Rick and Morty e criar páginas dinâmicas.
How to Create a Dynamic Rick and Morty Wiki Web App with Next.js (Como criar uma aplicação para a web dinâmica que sirva como Wiki de Rick and Morty no Next.js – página em inglês)
É possível acompanhar este tutorial sem ter passado pela primeira parte. Porém, pode ser de grande ajuda usá-la como ponto de partida. Caso queira continuar mesmo assim, saiba que a maior parte do que você vai ver aqui pode ser aplicada a qualquer aplicação feita em React.
Etapa 0: instalando o Framer Motion na sua aplicação do Next.js
Como vamos usar o Framer Motion para nos fornecer seus recursos de animação, a primeira coisa que devemos fazer é instalá-lo!
Com sua aplicação rodando localmente, execute o comando:
yarn add framer-motion
# ou
npm install framer-motion
Agora, você pode iniciar novamente o seu servidor de desenvolvimento e estaremos prontos para começar!

Este é o commit para você acompanhar
Etapa 1: animando o título da página com Framer Motion em uma aplicação do Next.js
Para começar, vamos animar o título da página da nossa aplicação wiki. Mais precisamente, vamos configurar o Framer Motion para fazer o título aparecer gradualmente enquanto a página é carregada.
A primeira coisa que devemos fazer é importar o Motion na nossa aplicação.
Comece adicionando a seguinte declaração de importação no topo do arquivo pages/index.js
:
import { motion } from 'framer-motion';
Feito isso, estamos prontos para usar o motion
. Podemos agora começar envolvendo o título <h1>
com um componente motion:
<motion.div>
<h1 className="title">
Wubba Lubba Dub Dub!
</h1>
</motion.div>
Envolver o elemento é o que nos permite conectá-lo à API do Motion.
Porém, nossa página ainda não faz nada ao ser recarregada. Isso acontece porque ainda não configuramos nossa animação. Vamos fazer isso agora!
Existem dois conceitos básicos que precisamos ter em mente quando usamos a API Motion com nosso componente <motion.x>
. São eles:
- O ciclo de vida da animação
- As variantes
Cada uma das props do ciclo de vida da animação — como initial
e animate
— nos permite definir o nome da nossa animação como uma variante.
Nossas props variants
são aquelas em que configuramos as animações ao definir nomes de variantes em conjunto com a animação que queremos usar.
Então, para começar, vamos adicionar duas definições de ciclo de vida ao nosso componente de título. Faremos isso ao adicionar duas props de ciclo de vida (initial e animate):
<motion.div initial="hidden" animate="visible">
<h1 className="title">
Wubba Lubba Dub Dub!
</h1>
</motion.div>
Agora, precisamos das seguintes definições:
<motion.div initial="hidden" animate="visible" variants={{
hidden: {},
visible: {},
}}>
<h1 className="title">
Wubba Lubba Dub Dub!
</h1>
</motion.div>
Aqui estamos definindo duas variantes: hidden e visible. Elas estão, respectivamente, referenciadas nas props de ciclo de vida initial
e animate
.
Ao recarregar a página, percebemos que nada acontece ainda. Isso porque ainda não definimos as animações em si. Então vamos fazer exatamente isso agora:
<motion.div initial="hidden" animate="visible" variants={{
hidden: {
scale: .8,
opacity: 0
},
visible: {
scale: 1,
opacity: 1,
transition: {
delay: .4
}
},
}}>
<h1 className="title">
Wubba Lubba Dub Dub!
</h1>
</motion.div>
Entenda o que está acontecendo:
- Até o momento, temos dois ciclos de vida: um initial e um animate. O initial se refere àquilo que acontece inicialmente quando a página é carregada; animate, por sua vez, se refere ao que deve acontecer quando a página termina de ser carregada.
- Inicialmente, estamos definindo que o elemento tenha seu tamanho ligeiramente reduzido e que sua opacidade seja 0.
- Quando a página carregar e acionar nossa animação, definimos para que seu tamanho e sua opacidade sejam restaurados para 1.
- Também definimos um atraso de 0,4 segundo na transição da animação. O objetivo disso é aguardar um pouco o carregamento dos elementos da página antes que a animação seja acionada.
Resumindo: 0,4 segundo após a página carregar, o título vai aparecer na página com um efeito de estar aumentando ligeiramente de tamanho.
Ao salvar essas alterações e recarregar a página, podemos ver o efeito do nosso título!

Este é o commit para você acompanhar
Etapa 2: adicionando efeitos de hover animados com Framer Motion a elementos de uma aplicação do Next.js
Agora que entendemos o básico de como adicionar animações no carregamento da página, é hora de adicionar alguma interação.
Nosso objetivo é adicionar alguns efeitos de hover aos cartões de cada personagem. Assim, quando você mover o cursor sobre um deles, a animação será ativada.
Primeiramente, dentro da nossa lista não ordenada <ul className="grid">
, precisamos transformar o elemento <li>
em um elemento <motion.li>
:
<motion.li key={id} className="card">
...
</motion.li>
Ao salvar e recarregar a página, você pode notar que tivemos um problema.

Por conta da integração entre o motion e o CSS do Next.js, nossa aplicação está se confundindo no nome da classe.
Embora isso não resolva o problema em si, uma maneira de consertar esse erro para a nossa demo é remover a prop jsx
do topo do nosso bloco <style>
, no qual nosso .card
é definido.
Mude de:
<style jsx>{`
Para:
<style>{`
Agora, é só recarregar a página e tudo parece estar como deveria.

Para adicionar o nosso efeito de hover, vamos criar uma nova prop no nosso componente <motion.li>
, chamada whileHover
, e preenchê-la com alguns estilos de base:
<motion.li key={id} className="card" whileHover={{
scale: 1.2,
transition: {
duration: .2
}
}}>
Nossa declaração acima diz ao motion que nosso elemento deve aumentar de tamanho para 1.2x do seu tamanho original quando passarmos o cursor sobre ele e que essa animação deve demorar 0,2 segundo.
Basta recarregarmos a página para ver nossa animação funcionando!

Repare na parte de baixo dessa animação. Quando o cartão aumenta de tamanho, ele atravessa o cartão de baixo; e isso faz o design parecer quebrado. Podemos corrigir isso aplicando algumas configurações de z-index e background-color!
<motion.li key={id} className="card" whileHover={{
position: 'relative',
zIndex: 1,
background: 'white',
scale: 1.2,
transition: {
duration: .2
}
}}>
Se recarregarmos a página outra vez, notamos que agora o cartão se sobrepõe ao de baixo quando aumenta de tamanho.

Este é o commit para você acompanhar
Etapa 3: adicionando transições de página com Framer Motion a uma aplicação do Next.js
Acionar uma animação quando a página termina de carregar ou quando passamos o cursor sobre um elemento é bacana. Porém, adicionar bons efeitos de transição dá um toque elegante à sua aplicação. Esse é um dos diferenciais que vai tornar o seu projeto mais parecido com uma "aplicação para a web", ao invés dos sites estáticos da web.
Para fazer isso, precisamos configurar nossa aplicação do Next.js (página em inglês) envolvendo as páginas root do site com um wrapper. Isso vai nos conectar ao ciclo de vida da navegação do site e animar adequadamente nossas páginas.
Para começar, precisamos criar um arquivo no diretório pages
chamado _app.js
:
// In pages/_app.js
function App({ Component, pageProps }) {
return (
<Component {...pageProps} />
)
}
export default App;
Nesse momento, não há necessidade de saber em detalhes o que está acontecendo, mas tenha em mente que estamos criando um wrapper no qual podemos inserir funcionalidades adicionais.
Com esse arquivo até o momento, você não deve notar nenhuma diferença ao recarregar a página.
A seguir, vamos definir a base que nos permite configurar as transições entre páginas.
Vamos começar importando o motion no topo do arquivo:
import { motion } from 'framer-motion';
Feito isso, vamos fazer como nas outras animações e adicionar um novo componente <motion.div>
que envolva nossa página.
<motion.div initial="pageInitial" animate="pageAnimate" variants={{
pageInitial: {
opacity: 0
},
pageAnimate: {
opacity: 1
},
}}>
<Component {...pageProps} />
</motion.div>
Aqui estamos configurando um estado inicial com opacidade 0 e um estado animado com opacidade 1. A intenção é criar um efeito fade in, que fará o elemento aparecer.
Agora, ao recarregar a página, é possível notar que ela tem esse efeito de fade in. Porém, o mesmo não acontece ao clicar no cartão de um personagem e abrir a página referente a ele.

O problema é que o motion não reconhece a página do personagem como uma nova rota. Por isso, temos que forçá-la a reconhecer a página dessa forma e atualizá-la.
Para isso, vamos desestruturar o argumento router
nas nossas props da função App:
function App({ Component, pageProps, router }) {
Vamos também usar esse argumento como uma key no nosso componente motion:
<motion.div key={router.route} initial="pageInitial" animate="pageAnimate" variants={{
Você pode notar que agora o conteúdo está com um efeito fade in quando navegamos entre a página inicial e a página do personagem.

Este é o commit para você acompanhar
Etapa 4: usando keyframes do Framer Motion para animações avançadas
Agora que já definimos as configurações básicas de animação para o ciclo de vida da nossa aplicação, podemos ir ainda mais longe com a ajuda dos keyframes.
Os keyframes funcionam da seguinte maneira: ao definirmos uma animação, podemos configurá-la para passar por uma série de outros valores nas propriedades que escolhermos — o que nos permite construir animações da maneira que preferirmos.
Por exemplo, vamos supor que o nosso objetivo é que um elemento, durante o hover, aumente 2x o seu tamanho original, depois fique 1.5x menor e, por fim, aumente novamente para 2x do tamanho original. Tudo isso em apenas uma sequência de animação. Podemos fazer isso utilizando keyframes!
A sintaxe para isso ficaria assim:
scale: [1, 2, 1.5, 2]
Aqui estamos especificando nossa sequência a partir de um array. Esse array diz que nosso elemento deve ter seu estado inicial no seu tamanho normal (1x), depois deve aumentar 2x, diminuir um pouco 1.5x e, por fim, aumentar novamente em 2x.
Para testar isso, precisamos fazer algumas mudanças no efeito de hover que já havíamos configurado para os cartões de personagem.
Em pages/index.js
, mude a propriedade whileHover
no elemento li motion
para:
<motion.li key={id} className="card" whileHover={{
position: 'relative',
zIndex: 1,
background: 'white',
scale: [1, 1.4, 1.2],
rotate: [0, 10, -10, 0],
transition: {
duration: .2
}
}}>
Estamos definindo duas configurações de keyframes para o nosso elemento. São elas:
- O tamanho inicial com 0 de rotação (ou "sem rotação", se preferir).
- Depois, o elemento aumenta em 1.4x do seu tamanho original e tem uma rotação de 10 graus.
- Ele diminui o tamanho em 1.2x e rotaciona para o lado inverso com -10 graus.
- Nesse ponto, a escala do keyframes está completa. Logo, não precisamos mais mexer nela, mas ainda temos uma rotação para fazer, na qual retornamos o elemento para sua posição inicial 0 (sem rotação).
Agora, basta recarregarmos a página e passar o cursor sobre os elementos para vermos os efeitos animados em ação!

Sem os keyframes, tudo o que podemos fazer são animações de um único estado inicial a um único estado final. No entanto, com os keyframes, podemos adicionar animações mais dinâmicas variando entre diferentes valores.
Este é o commit para você acompanhar
Etapa bônus: pirando um pouco na animação da nossa aplicação do Next.js de Rick and Morty
Para tornar nosso projeto ainda mais divertido, podemos brincar com outras propriedades que vão deixar nossas animações ainda mais dinâmicas.
Vamos começar intensificando nossos efeitos de hover.
No arquivo pages/index.js
, adicione a seguinte alteração à prop whileHover
do elemento <motion.li>
:
filter: [
'hue-rotate(0)',
'hue-rotate(360deg)',
'hue-rotate(45deg)',
'hue-rotate(0)'
],
Aqui, estamos definindo novas configurações de keyframes que vão "rotacionar" a matriz de cor (hue) da imagem com base na função hue-rotate do CSS (página em inglês).
Ao salvar e recarregar a página, temos um pequeno efeito de cor:

Assim ainda está um pouco sutil. O ideal seria deixar uma impressão de estranhamento ainda maior.
Vamos aplicar as seguintes alterações à propriedade filter
:
filter: [
'hue-rotate(0) contrast(100%)',
'hue-rotate(360deg) contrast(200%)',
'hue-rotate(45deg) contrast(300%)',
'hue-rotate(0) contrast(100%)'
],
Agora, além de mudarmos a cor, usamos a função contrast do CSS para tornar as cores ainda mais extremas e causar uma maior impressão de estranhamento.

Em seguida, podemos fazer algo semelhante com nossas transições de página!
Para isso, vamos fazer uso de uma outra parte do ciclo de vida do componente Motion — a saída (exit). Aqui, nós vamos precisar usar o componente AnimatePresence, do Motion — que vai nos permitir animar componentes que forem removidos da árvore do React.
Vamos começar abrindo o arquivo pages/_app.js
e importando o componente no topo:
import { motion, AnimatePresence } from 'framer-motion';
Agora, precisamos envolver nosso componente <motion.div>
com o componente AnimatePresence
:
<AnimatePresence>
<motion.div key={router.route} initial="pageInitial" animate="pageAnimate" variants={{
Depois de envolvermos nosso componente, podemos definir um novo ciclo de vida da prop exit
junto com sua variante:
<motion.div key={router.route} initial="pageInitial" animate="pageAnimate" exit="pageExit" variants={{
pageInitial: {
opacity: 0
},
pageAnimate: {
opacity: 1
},
pageExit: {
backgroundColor: 'white',
filter: `invert()`,
opacity: 0
}
}}>
O que fizemos no código acima:
- Configuramos a variante
pageExit
- Configuramos a propriedade de ciclo de vida
exit
parapageExit
- Na nossa variante
pageExit
, definimos a cor de fundo para branco e adicionamos um filtro para inverter as cores.
Observação: é importante definir a cor de fundo para branco, caso contrário, o filtro invert não será aplicado ao fundo.
Agora, podemos salvar e recarregar a página para vermos nosso efeito quando navegamos para um outro elemento.

Este é o commit para você acompanhar
O que mais podemos fazer?
Adicione algumas animações para dar um efeito de escalonamento nos resultados da pesquisa.
Você pode verificar minha demo original na qual baseei este tutorial. Nela, eu adicionei uma funcionalidade que faz os resultados flutuarem ao aparecer, deslocando-se ligeiramente para cima.
Podemos fazer isso com a propriedade de transição staggerChildren e definindo as posições x e y (página em inglês) na nossa lista de elementos.
https://github.com/colbyfayock/rick-and-morty-wiki/blob/master/pages/index.js#L11
Anime os botões
Atualmente, os botões são apenas estáticos. Você pode adicionar alguns efeitos de hover e de clique a eles, como, por exemplo, no Load More, na parte de baixo.
Adicione mais efeitos estranhos
Afinal de contas, estamos falando de Rick and Morty. Você pode pirar à vontade. Certifique-se, porém, de que o resultado final ainda seja algo utilizável.
