Animações no React: Framer Motion e CSS transitions
1. Fundamentos das Animações no Ecossistema React
Animações em React não são apenas um detalhe estético — elas impactam diretamente a percepção de performance e a experiência do usuário. Uma transição suave entre estados pode transformar uma interface confusa em uma experiência fluida e intuitiva.
No React, as animações podem ser abordadas de duas formas principais: imperativa (manipulando diretamente o DOM via JavaScript) e declarativa (descrevendo o estado final desejado). A abordagem declarativa se alinha perfeitamente com o paradigma do React, onde você declara "o que" deve acontecer, não "como".
O Virtual DOM e o processo de reconciliação do React criam um desafio único: quando o estado muda, o React precisa calcular as diferenças e atualizar o DOM real. Animações mal implementadas podem causar re-renders desnecessários, impactando a performance. Por isso, entender como o React gerencia o ciclo de vida dos componentes é essencial para criar animações eficientes.
2. CSS Transitions e Animações Nativas no React
A maneira mais simples de adicionar animações no React é usando CSS transitions com classes dinâmicas:
import { useState } from 'react';
function FadeInBox() {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>
{visible ? 'Esconder' : 'Mostrar'}
</button>
<div
className={`box ${visible ? 'box--visible' : 'box--hidden'}`}
>
Conteúdo animado
</div>
</div>
);
}
.box {
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.box--visible {
opacity: 1;
transform: translateY(0);
}
Outra técnica útil é usar a propriedade key para forçar a remontagem de componentes, criando animações de entrada/saída:
function ListaAnimada({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={item.id} className="item-entrada">
{item.text}
</li>
))}
</ul>
);
}
Limitações do CSS puro: Animações complexas (timelines, sequências, gestos) exigem JavaScript. Além disso, controlar o momento exato de entrada e saída de componentes é difícil com CSS.
3. Introdução ao Framer Motion: Instalação e Conceitos
O Framer Motion é a biblioteca mais popular para animações declarativas em React. Para instalar:
npm install framer-motion
Componentes fundamentais:
import { motion } from 'framer-motion';
function ComponenteBasico() {
return (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
>
Animação simples
</motion.div>
);
}
As propriedades principais são:
- initial: Estado inicial da animação
- animate: Estado final (pode ser reativo a props/state)
- exit: Estado ao remover o componente (requer AnimatePresence)
- transition: Configurações de timing, easing e delay
4. Animações Avançadas com Framer Motion
Variants: Organização de Estados Reutilizáveis
const variants = {
hidden: { opacity: 0, x: -100 },
visible: {
opacity: 1,
x: 0,
transition: { staggerChildren: 0.1 }
},
exit: { opacity: 0, x: 100 }
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
function ListaAnimada() {
return (
<motion.ul
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
>
{[1, 2, 3].map((item) => (
<motion.li key={item} variants={itemVariants}>
Item {item}
</motion.li>
))}
</motion.ul>
);
}
Gestos Interativos
function CardInterativo() {
return (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
whileFocus={{ boxShadow: '0 0 0 3px blue' }}
drag="x"
dragConstraints={{ left: -100, right: 100 }}
>
Arraste-me!
</motion.div>
);
}
5. Animações de Entrada e Saída (Mount/Unmount)
O AnimatePresence permite animar a remoção de componentes:
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
function ModalAnimado() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Fechar' : 'Abrir'} Modal
</button>
<AnimatePresence>
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.3 }}
style={{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: 'white',
padding: '2rem',
borderRadius: '8px'
}}
>
<h2>Modal Animado</h2>
<p>Este modal tem animação de entrada e saída!</p>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
6. Integração com Gerenciamento de Estado e Roteamento
Transições de Rota com React Router
import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
function App() {
const location = useLocation();
return (
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route path="/" element={
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
>
Home
</motion.div>
} />
<Route path="/about" element={
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
>
About
</motion.div>
} />
</Routes>
</AnimatePresence>
);
}
7. Performance e Boas Práticas
Para animações otimizadas, siga estas práticas:
import { motion } from 'framer-motion';
// 1. Use transform em vez de propriedades que causam layout
<motion.div
animate={{ x: 100 }} // Prefira x/y em vez de left/top
style={{ willChange: 'transform' }}
/>
// 2. Evite re-renders com React.memo
const CardAnimado = React.memo(({ item }) => (
<motion.div whileHover={{ scale: 1.02 }}>
{item}
</motion.div>
));
// 3. Use useMemo para variants complexas
const variants = useMemo(() => ({
hidden: { opacity: 0 },
visible: { opacity: 1 }
}), []);
8. Projeto Prático: Componente de Carrossel Animado
import { useState, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
const slides = [
{ id: 1, color: '#ff6b6b', text: 'Slide 1' },
{ id: 2, color: '#4ecdc4', text: 'Slide 2' },
{ id: 3, color: '#45b7d1', text: 'Slide 3' }
];
const slideVariants = {
enter: (direction) => ({
x: direction > 0 ? 300 : -300,
opacity: 0
}),
center: {
x: 0,
opacity: 1
},
exit: (direction) => ({
x: direction < 0 ? 300 : -300,
opacity: 0
})
};
function CarrosselAnimado() {
const [[page, direction], setPage] = useState([0, 0]);
const slideIndex = ((page % slides.length) + slides.length) % slides.length;
const paginate = useCallback((newDirection) => {
setPage([page + newDirection, newDirection]);
}, [page]);
return (
<div style={{ width: 300, height: 200, overflow: 'hidden', position: 'relative' }}>
<AnimatePresence initial={false} custom={direction}>
<motion.div
key={page}
custom={direction}
variants={slideVariants}
initial="enter"
animate="center"
exit="exit"
transition={{ x: { type: 'spring', stiffness: 300, damping: 30 }, opacity: { duration: 0.2 } }}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(e, { offset, velocity }) => {
const swipe = Math.abs(offset.x) * velocity.x;
if (swipe < -10000) paginate(1);
else if (swipe > 10000) paginate(-1);
}}
style={{
position: 'absolute',
width: '100%',
height: '100%',
background: slides[slideIndex].color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '2rem',
color: 'white',
cursor: 'grab'
}}
>
{slides[slideIndex].text}
</motion.div>
</AnimatePresence>
<button
onClick={() => paginate(-1)}
style={{ position: 'absolute', left: 10, top: '50%' }}
>
‹
</button>
<button
onClick={() => paginate(1)}
style={{ position: 'absolute', right: 10, top: '50%' }}
>
›
</button>
</div>
);
}
Este carrossel demonstra:
- Animações de entrada/saída com direção
- Gestos de arrastar (drag) para navegação
- Controle de timeline com spring physics
- Integração com estado para controle de navegação
Referências
- Documentação oficial do Framer Motion — Guia completo de todos os componentes, gestos e animações disponíveis
- CSS Transitions no MDN — Documentação detalhada sobre como usar transições CSS nativas
- React Animation: The Complete Guide (2024) — Tutorial abrangente sobre animações em React, incluindo Framer Motion e CSS
- AnimatePresence Documentation — Documentação específica sobre o componente AnimatePresence para animações de entrada/saída
- React Router + Framer Motion Transition Tutorial — Guia prático de como integrar animações de transição de rota com React Router
- Performance Tips for React Animations — Guia do Google sobre boas práticas de performance para animações web