Framer Motion e Intersection Observer: Uma Dupla Poderosa para Animações no React
Tags: react, animations, typescript, braziliandevs
Published: 2022-12-19 17:16:45

Olá Pessoal,

 Hoje vamos combinar o [`Framer Motion`](https://www.framer.com/motion/) e a [`Intersection Observer API`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) (IO API) para criar animações legais, portanto é importante compreender o que cada ferramenta oferece antes de começar. 

 A Intersection Observer API permite monitorar mudanças na interseção de um elemento marcado em relação ao seu pai ou à _`viewport`_ de forma assíncrona. Já o framer-motion facilita a criação de animações, transições de layout e gestos de maneira declarativa, mantendo a semântica dos elementos `HTML` e `SVG`.

 Os exemplos estão disponíveis no seguinte [repositório](https://github.com/dellamora/Framer-Motion-Intersection-Observer-API-animations).

## Criando um observador 

![animations examples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/080wb9nu3i3c5q4lnt59.gif)

 Antes de começarmos a criar animações, precisamos criar o componente `Section` que utilizara o `hook` `useInView` para monitorarmos sua presença na tela. 

 Para identificarmos o elemento que será monitorado, aplicaremos a propriedade `ref` (que será o próprio section) e o parâmetro `threshold` que irá indicar, em porcentagem, a quantidade do elemento que precisa estar visível para que o estado `inView` seja atualizado.

 Sempre que houver alterações no estado inView, o `useEffect` será acionado e chamará um `callback` no componente pai, permitindo que uma animação seja iniciada assim que o elemento entrar na tela.

```typescript
export const Section = ({
  id,
  children,
  setIsInView,
  className,
}: Props): JSX.Element => {
  const { ref, inView } = useInView({
    threshold: 0.4,
  });
  
  useEffect(() => {
    if (setIsInView) {
      setIsInView(inView);
    }
  }, [inView, setIsInView]);

  return (
    <section
      className={`relative overflow-hidden ${className}`}
      ref={ref}
      id={id}
    >
      {children}
    </section>
  );
};

export default Section;
```

##  Animação de títulos 

![Title animation example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v6zfoh4wv56yvsgtcmnx.gif)

 No HTML, todo texto dentro de uma `heading tag` é considerado um elemento próprio na `DOM`, desse modo para a animação funcionar é necessário transformar cada caractere do texto em um elemento diferente. 

 O processo começa com a função `split`, que divide o título em palavras. Em seguida, a função `map` é usada para retornar cada palavra e repetimos a lógica para separá-la em caracteres únicos.
 Para dar espaço entre as palavras, foi adicionado `mr-2` no estilo (_className_). Além disso, a propriedade `key` é adicionada para garantir a identificação única de cada elemento e melhorar o desempenho da aplicação.

 A fim de aproveitar a mágica do framer-motion, é necessário transformamos todas as tags de `span` em motions tags.
 Dessa forma, o componente `motion.span` permite controlar a animação de cada caractere, definindo o estado inicial, animação, variações de animação e transições. 

 O uso da função useEffect também é necessário para simular o efeito `triggerOnce` do useInView e garantir que a animação ocorra apenas uma vez.

 Finalmente, é preciso ajustar a propriedade `transition` para que cada caractere tenha o atraso adequado com base na sua posição. 
 O framer-motion permite que você controle a animação dos filhos com a propriedade `staggerChildren` na transição, então só precisamos adicionar ela e definir o tempo de _delay_.

 No caso de títulos com mais de uma palavra, foi necessário usar o `delayChildren` e dividir o texto em palavras para atrasar a animação de cada uma delas. O processo envolve usar a função split, `slice`, `join` e `length` para determinar o tamanho total e multiplicá-lo pelo tempo especificado no staggerChildren acima.


```typescript
const Title = ({
  title,
  triggerAnimation,
}: Props): JSX.Element => {
  const [triggered, setTriggered] = useState(false)

  useEffect(() => {
    setTriggered(curr => triggerAnimation || curr)
  },[triggerAnimation])


  const characterAnimation = {
    hidden: {
      opacity: 0,
    },
    visible: {
      opacity: 1,
    },
  };
  return (
      <div className="flex items-center">
        {title.split(" ").map((word, index) => {
          return (
            <motion.span
              className="mr-2"
              aria-hidden="true"
              key={`key-${word}-${index}`}
              initial="hidden"
              animate={triggered ? "visible" : "hidden"}
              transition={{
                staggerChildren: 0.1,
                delayChildren:
                  index === 0
                    ? 0
                    : title.split(" ").slice(0, index).join(" ").length * 0.1,
              }}
            >
              {word.split("").map((character, index) => {
                return (
                  <motion.span
                    className="text-2xl md:text-3xl text-gray  "
                    aria-hidden="true"
                    key={`key-${character}-${index}`}
                    variants={characterAnimation}
                  >
                    {character}
                  </motion.span>
                );
              })}
            </motion.span>
          );
        })}
      </div>
  );
};

export default Title;

```



###  Animação de delay na opacity para textos 

![animations examples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/496rg3v28v8mq846uzns.gif)

 O gif acima apresenta um componente que exibe duas colunas: uma coluna com parágrafos e outra com tópicos. 

 A coluna com parágrafos usa a função map para percorrer o array "_paragraphs_" e renderizar cada item como uma tag `motion.p`. 
 Cada tag `motion.p` tem a propriedade `initial` com valor de `opacity: 0`, o que significa que inicialmente a opacidade será 0%. A propriedade `animate` tem valor de `opacity: 1`, indicando que a animação deve mudar a opacidade para 100%. 

 A propriedade `transition` tem o valor `delay: 1 + i * 0.2`, o que significa que o tempo de atraso para cada tag será calculado pela soma de 1 mais o resultado da multiplicação de i por 0.2. O "i" é o valor do índice, começando em 0 e incrementando em 1 a cada iteração.

 A segunda coluna exibe os tópicos e usa a função map para percorrer o array "_topics_" e renderizar cada item como uma tag "motion.li" e repetimos a mesma logica da animação dos parágrafos, com pequenas adaptações no valor de delay. 


```typescript
                             [...]
        <div className="md:flex gap-4">
          <div className="md:w-1/2">
          {paragraphs.map((paragraph, i) => {
            return (
              <motion.p
                initial={{opacity:0}}
                animate={{opacity: 1, transition: {delay: 1 + i * 0.2}}}
                className="text-justify " 
                key={`paragraph-${i}`}
              >
                {paragraph}
              </motion.p>
            )
          })}
          </div>
          <ul className="md:w-1/2 h-fit grid grid-cols-topics gap-4">
            {topics.map((topic, i) => {
              return (
                <motion.li
                  className={i === topics.length - 1 ? "lg:col-span-2": " "}
                  key={`topic-${i}`}
                  initial={{opacity: 0}}
                  animate={{ opacity: 1}}
                  transition={{                                                   
                    delay:                              
                    1.2 + 0.2 + i * 0.3,
                  }} 
                >                                                                                                                   
                <div className="flex items-start">
                <hr className="mr-2 mt-3 w-5 h-1 text-grayLight" />
                   {topic}
                   </div>
                 </motion.li>
              )
            })}
          </ul>
      </div>
                             [...]

```



###  Animação para imagens durante o viewport on

![animations examples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4v8xb09gzyehdrjgg5gv.gif)

 Para organizar o código e torná-lo mais limpo, usaremos a propriedade `variants` para controlar a posição, rotação e opacidade de quatro elementos diferentes. Utilizaremos a propriedade staggerChildren da transition para controlar a opacidade dos quatro elementos ao mesmo tempo.

 Definiremos o posicionamento dos elementos usando os seguintes atributos:

- **y:**  que é a posição vertical dos elementos no eixo Y
- **x:**  que é a posição horizontal dos elementos no eixo X
- **rotate:** que é a rotação dos elementos em graus

 Para controlar a animação entre os estados, usaremos o objeto "transition" que inclui dois atributos:


- 
**type:** "spring", indica que a animação usará uma transição "mola" (spring)

- 
**stiffness**: 50, que indica a rigidez da mola. Quanto maior o número, mais rápida e suave será a animação.

```typescript 
const ExampleTwo: React.FC<ExampleTwoProps> = (): JSX.Element => {
  const [inView, setInView] = useState(false);
  const animations = {
    hidden: {
      opacity: 0
    },
    view: {
      opacity: 1,
    }
   }
  const firstGirl = {
    hidden: {
      y: 0, 
      x: -200, 
      rotate: "12deg"
    },
    view: {
      y:0, 
      x:-55, 
      rotate: "30deg", 
      transition: {
        type: "spring", 
        stiffness: 50
      }
    }
  }
                             [...]
  return (
                             [...]
      <motion.div
        className="flex flex-col"
        initial="hidden"
        animate={inView ? "view" : "hidden"}
        variants={animations}  
        transition={{staggerChildren: 0.5}}
      >
        <motion.img
          variants={firstGirl}
          src="/firstGirl.png"
          className=" absolute top-3 left-0  h-[21rem] lg:h-[25rem]"
        />
                             [...]
      </motion.div>
    </Section>
  );
};

export default ExampleTwo;

```


###  Animação de switch para botão 

![animations examples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xw64g1ftwdw08m1s4imu.gif)

 O componente `ButtonExample` é composto por um botão HTML e uma div, ambos estilizados com CSS. O botão tem uma cor que muda dependendo da propriedade `active`. 

 Quando clicado, ele executa a função `onClick` e a div é exibida apenas se a propriedade `active` for verdadeira.

 As propriedades onClick, active e `children` permitem personalizar a funcionalidade e o conteúdo do botão.

```typescript 
const ButtonExample = ({
  onClick,
  active,
  children,
}: Props): JSX.Element => {
  return (
    <div className="relative w-full">
      <button
        className={` w-full flex relative font-Inter items-center text-xl  py-2 md:px-6 px-4 z-20 
        ${active ? "text-redLight" : "text-grayMedium"}  `}
        onClick={onClick}
      >
        {children}
      </button>
      {active && (
        <motion.div
          className=" rounded absolute top-0 bottom-0 left-0 right-0 bg-whiteBasic  z-10 flex justify-end"
          layoutId="buttonBg"
        />
      )}
    </div>
  );          
};

export default ButtonExample;

```

---


 Em conclusão, a união de Framer Motion e Intersection Observer no desenvolvimento de aplicações React é um passo importante para alcançar animações de alta qualidade. 
 A biblioteca Framer Motion oferece aos desenvolvedores a capacidade de criar animações complexas de forma simples, enquanto o Intersection Observer garante que as animações só sejam executadas quando os elementos estiverem na tela. 
 Juntos, eles permitem a criação de aplicações atraentes e interativas, proporcionando uma experiência fluida e envolvente aos usuários finais.

Ideias e comentários são bem-vindos e apreciados! (: