Atualmente animações no ambiente React se torna algo cada vez mais simples com bibliotecas que à auxiliam, hoje vamos falar de uma delas o Framer Motion.
O que é o Framer Motion?
A biblioteca Framer Motion desenvolvida pela empresa Framer, vem crescendo nos últimos meses visando seu foco na experiência do desenvolvedor. Ela busca tornar o processo de animar a sua aplicação algo simples com poucas linhas de código. Acessando a documentação do Framer Motion é possível observar alguns exemplos de como a biblioteca se comporta
Através de uma sintaxe declarativa, o dev consegue criar animações apenas por definir suas características em um objeto com suas propriedades, tornando o seu desenvolvimento simples e funcional quando visando manutenções futuras do time.
Configurando o ambiente:
Para começar, vamos criar a nossa aplicação React com a cli create-react-app
, um modulo npm para facilitar a configuração de um app React.
No nosso caso, vamos utilizar o TypeScript para facilitar o desenvolvimento, então basta digitar o comando passando o nome/diretório do projeto e opção --ts
ao final do comando:
npx create-react-app my-app --template typescript
Em seguida vamos instalar as dependências necessárias para o projeto.
No nosso caso vamos utilizar o TailwindCSS como biblioteca de estilização, por facilitar na praticidade na hora de escrever os estilos. No entanto você fica livre para escolher a que achar melhor.
npm i framer-motion tailwindcss postcss autoprefixer
Para configurar a biblioteca de estilos você pode seguir um guia bem simples criado pela Tailwind, configurando e iniciando os arquivos de configuração através de sua cli.
O uso do objeto motion:
De forma simplificada esse objeto é um conjunto de componentes agnósticos HTMLs e SVGs, com as funcionalidades e propriedades de animações do framer motion. Importando o objeto { motion }
do pacote framer-motion em um arquivo, você pode incorporar esses componentes diretamente em seu JSX.
Esses componentes podem receber propriedades específicas de animação, definidas dentro da propriedade animate
import { motion } from "framer-motion";
export const MyAnimatedComponent = () => {
return (
<motion.div
animate={{
scale: 2, // Aumenta o escala do componente em 2 vezes o tamanho original.
}}
>
I'm a div
</motion.div>
);
};
Além da propriedade animate
, o objeto fornece também outras que podem ser utilizadas para controlar diferentes aspectos das animações, como initial
, whileHover
e transition
. Que permitem a criação de animações mais complexas e dinâmicas.
Dando vida a uma lista:
Uma das nossas maiores dores no desenvolvimento em Next.js é animar uma lista de itens, componentizada e de forma simples. Vamos resolver isso utilizando dois componentes com o framer-motion, definindo primeiro quais componentes iremos utilizar:
- Uma Lista estilizada (AnimatedList)
- O Item da Lista estilizado (AnimatedListItem)
Vamos criar uma pasta AnimatedList e AnimatedListItem e definir os arquivos e estilos do componente, nesse caso estamos criando um elemento ul
e li, eles serão os nossos Container e Item.
// AnimatedList
import { ReactNode } from "react";
import { motion } from "framer-motion";
type AnimatedList = {
children: ReactNode;
};
export const AnimatedList = ({ children }: AnimatedList) => {
return (
<motion.ul className="flex flex-col gap-2 box-border">{children}</motion.ul>
);
};
O Item da lista:
Vamos fazer a mesma coisa para o nosso AnimatedListItem, definindo dessa vez nosso componente como forwardRef.
Por conta de estarmos separando o componente motion.li em um componente externo, precisamos continuar repassando a sua ref para o elemento “pai” da nossa aplicação
// AnimatedListItem
type AnimatedListItemProps = { children: ReactNode } & ComponentProps<
typeof motion.li
>;
export const AnimatedListItem = forwardRef<
HTMLLIElement,
AnimatedListItemProps
>(({ children, ...props }, ref) => {
return (
<motion.li
layout
className="rounded bg flex items-center p-4 font-bold text-slate-600 h-12 border-2 border-slate-600 "
{...props}
>
<span>{children}</span>
</motion.li>
);
});
Colocando-os em tela e acrescentando mais alguns componentes para a melhor implementação da nossa lista, teremos o seguinte resultado:
export default function Page() {
return (
<div className="flex flex-col gap-4 p-6 w-1/2 mx-auto">
<h1 className="text-lg font-semibold">
Animações com Framer Motion & TypeScript{" "}
</h1>
<AnimatedList>
<AnimatedListItem>Item 1</AnimatedListItem>
<AnimatedListItem>Item 2</AnimatedListItem>
<AnimatedListItem>Item 3</AnimatedListItem>
</AnimatedList>
</div>
);
}
No momento nossa lista ainda está estática, mas vamos dar vida a ela utilizando um componente muito útil da biblioteca, o AnimatePresence.
Animando com o AnimatePresence:
Um utilitário especialmente utilizado para controlar os estados de montagem e desmontagem de componentes dinâmicos, como listas e componentes condicionais.
Para a nossa lista, precisaremos acrescentar o utilitário como wrapper ao redor do nosso AnimatedListItem. E acrescentar a prop mode com o valor “popLayout” que tratá uma animação mais usual e dinamica.
Ainda nada acontece com a nossa lista, mas iremos fazer isso de forma simples manipulando as propriedades de animações de entrada (initial
e animate
) e saída (exit
), com o estilo desejado em cada estado:
export const MyPage = () => {
return (
<div className="flex flex-col gap-4 p-6 w-1/2 mx-auto">
<h1 className="text-lg font-semibold">
Animações com Framer-Motion & TypeScript{" "}
</h1>
<AnimatedList>
<AnimatePresence mode="popLayout">
<AnimatedListItem
initial={{ opacity: 0, scale: 0.8 }}
animate={{ scale: 1, opacity: 1, x: 0 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ type: "spring", bounce: 0.1, mass: 0.2 }}
>
Item 1
</AnimatedListItem>
<AnimatedListItem>Item 2</AnimatedListItem>
<AnimatedListItem>Item 3</AnimatedListItem>
</AnimatePresence>
</AnimatedList>
</div>
);
};
Como resultado, temos uma simples animação de entrada:
Diante disso, para testamos todo o potencial desse componente vamos incluir um gerenciamento junto com uma implementação de montagem e desmontagem de cada item. Juntamente com um mapeamento para reduzirmos a repetição de código:
export default function Page() {
const [items, setItems] = useState<number[]>([]);
const initialNumber = useRef<number>(0);
const addNewItem = () => {
setItems((prev) => [...prev, new Date().getTime() * Math.random()]);
};
const removeFirstItem = () => {
setItems((prev) => prev.slice(1));
initialNumber.current += 1;
};
return (
<div className="flex flex-col gap-4 p-6 w-1/2 mx-auto">
<h1 className="text-lg font-semibold">
Animações com Framer-Motion & TypeScript{" "}
</h1>
<div className="w-1/2 flex gap-4 mb-4">
<button
className="select-none text-gray-500 font-medium border-gray-400 flex-1 rounded-md py-1 md:hover:bg-slate-100 transition-colors"
onClick={removeFirstItem}
>
Remove
</button>
<button
className="select-none border-2 font-medium border-green-500 flex-1 rounded-md py-2 bg-green-300 md:hover:bg-green-200 transition-colors"
onClick={addNewItem}
>
Add
</button>
</div>
<AnimatedList>
<AnimatePresence mode="popLayout">
{items.map((item, index) => (
<AnimatedListItem key={item}>
Item {index + initialNumber.current}
</AnimatedListItem>
))}
</AnimatePresence>
</AnimatedList>
</div>
);
}
Por fim, como uma biblioteca construída para o ecossistema do React, o Framer Motion oferece uma sintaxe simples. O que facilita para nos devs entregamos animações de forma intuitiva e prática, exigindo apenas poucas linhas de código. 🚀
Seja o primeiro a comentar