Além do Estático: Animações com TypeScript e Framer Motion

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

Faça um comentário

Seu e-mail não será divulgado.


*