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 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.

framer-motion-nextjs-animation-demo
Demonstração de animação com Framer Motion

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!

rick-and-morty-wiki-nextjs
Ponto de partida — aplicação de uma wiki de Rick and Morty feita em Next.js

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!

animated-header-framer-motion
Título com animação feita com Framer Motion na aplicação Next.js

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.

rick-and-morty-wiki-missing-styles
Nossa aplicação está sem os estilos para os cartões de personagem.

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.

rick-and-morty-wiki-nextjs--1-
Aplicação com os estilos corretos

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!

rick-and-morty-wiki-hover-framer-motion
Efeito de hover feito com Framer Motion para a aplicação do Next.js

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.

rick-and-morty-framer-motion-animation-fixed-layer
Consertando o z-index e o background no efeito de hover

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.

rick-and-morty-app-no-page-transitions
Mudando de página sem efeitos de transição

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.

rick-and-morty-app-fade-page-transitions-framer-motion
Transição de página com efeito fade in feita com Framer Motion para a aplicação em Next.js

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!

rick-and-morty-hover-effect-framer-motion
Efeito de rotação e mudança de tamanho feitos com Framer Motion

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:

rick-and-morty-hover-effect-weird-framer-motion
Mudando a cor da imagem com Framer Motion e filtros do CSS

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.

rick-and-morty-framer-js-hover-effect-color
Alterando a cor da imagem no efeito de hover com Framer Motion e com os filtros do CSS

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 para pageExit
  • 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.

rick-and-morty-app-page-transitions
Invertendo as cores com Framer Motion na transição entre páginas

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.

social-footer-card-1