React & Frontend

Guia Completo de Animações em React: Framer Motion, React Spring e CSS Transitions

18 min de leitura

Guia Completo de Animações em React: Framer Motion, React Spring e CSS Transitions

Fundamentos de Animações em React Animações são um aspecto crítico da experiência do usuário moderna. Elas comunicam feedback, guiam o olhar, criam transições suaves e tornam interfaces digitais mais vivas e responsivas. Em React, você tem várias abordagens para implementar animações, cada uma com seus pontos fortes e casos de uso específicos. A escolha entre CSS Transitions, Framer Motion e React Spring depende da complexidade da animação, da performance necessária e de quanto controle você quer ter sobre os detalhes. CSS Transitions são ideais para mudanças simples de estado, Framer Motion brilha em animações declarativas e sequenciadas, enquanto React Spring é perfeita quando você precisa de física realista e animações que se movem como objetos do mundo real. CSS Transitions e Animações Nativas Entendendo CSS Transitions CSS Transitions é a abordagem mais leve e nativa do navegador. Quando você muda uma propriedade CSS, a transição interpola suavemente entre o valor antigo e o novo. Isso é perfeito para efeitos simples

<h2>Fundamentos de Animações em React</h2>

<p>Animações são um aspecto crítico da experiência do usuário moderna. Elas comunicam feedback, guiam o olhar, criam transições suaves e tornam interfaces digitais mais vivas e responsivas. Em React, você tem várias abordagens para implementar animações, cada uma com seus pontos fortes e casos de uso específicos.</p>

<p>A escolha entre CSS Transitions, Framer Motion e React Spring depende da complexidade da animação, da performance necessária e de quanto controle você quer ter sobre os detalhes. CSS Transitions são ideais para mudanças simples de estado, Framer Motion brilha em animações declarativas e sequenciadas, enquanto React Spring é perfeita quando você precisa de física realista e animações que se movem como objetos do mundo real.</p>

<h2>CSS Transitions e Animações Nativas</h2>

<h3>Entendendo CSS Transitions</h3>

<p>CSS Transitions é a abordagem mais leve e nativa do navegador. Quando você muda uma propriedade CSS, a transição interpola suavemente entre o valor antigo e o novo. Isso é perfeito para efeitos simples como hover states, alterações de cor, ou mudanças de tamanho. O navegador otimiza essas transições em nível de renderização, tornando-as muito eficientes.</p>

<p>A chave para usar transições em React é gerenciar as classes CSS ou propriedades de estilo através do estado. Quando o estado muda, você aplica estilos diferentes, e a transição CSS faz o trabalho de animar entre eles.</p>

<pre><code class="language-jsx">import { useState } from &#039;react&#039;;

export default function ButtonWithTransition() {

const [isActive, setIsActive] = useState(false);

const handleClick = () =&gt; {

setIsActive(!isActive);

};

return (

&lt;&gt;

&lt;style&gt;{`

.box {

width: 100px;

height: 100px;

background-color: #3498db;

transition: all 0.3s ease;

cursor: pointer;

}

.box.active {

width: 200px;

background-color: #e74c3c;

transform: rotate(45deg);

}

`}&lt;/style&gt;

&lt;div

className={box ${isActive ? &#039;active&#039; : &#039;&#039;}}

onClick={handleClick}

/&gt;

&lt;/&gt;

);

}</code></pre>

<p>Neste exemplo, a caixa muda de tamanho, cor e rotação quando clicada. A propriedade <code>transition: all 0.3s ease</code> no CSS faz com que todas as mudanças de propriedade sejam animadas suavemente. Isso é simples, direto e excelente para efeitos rápidos que não precisam de sincronização complexa.</p>

<h3>Limitações das CSS Transitions</h3>

<p>Embora CSS Transitions sejam poderosas, elas têm limitações importantes. Você não pode animar com precisão sequências de eventos, não consegue parar uma animação no meio do caminho facilmente, e é difícil criar animações que respondem a gestos ou entrada do usuário em tempo real. Além disso, quando a lógica fica complexa, você acaba escrevendo muito CSS para gerenciar diferentes estados, tornando o código frágil.</p>

<p>É por isso que bibliotecas como Framer Motion e React Spring existem. Elas dão ao desenvolvedor controle total e previsibilidade, além de abstrair complexidades da renderização.</p>

<h2>Framer Motion: Animações Declarativas e Poderosas</h2>

<h3>Conceitos Fundamentais do Framer Motion</h3>

<p>Framer Motion é uma biblioteca que torna animações declarativas. Em vez de se preocupar com detalhes de timing e interpolação, você descreve como um elemento deve se animar e a biblioteca cuida do resto. A API é intuitiva e segue o paradigma React de componentes.</p>

<p>Os dois componentes principais são <code>motion.div</code> (ou qualquer tag HTML) e <code>AnimatePresence</code>. O componente <code>motion</code> rastreia mudanças de propriedade e as anima automaticamente. Você especifica estados diferentes (initial, animate, exit, hover, etc.) e Framer Motion interpola entre eles.</p>

<pre><code class="language-jsx">import { motion } from &#039;framer-motion&#039;;

export default function SimpleMotionComponent() {

return (

&lt;motion.div

initial={{ opacity: 0, y: -50 }}

animate={{ opacity: 1, y: 0 }}

transition={{ duration: 0.6, ease: &#039;easeOut&#039; }}

style={{

width: 200,

height: 200,

background: &#039;linear-gradient(135deg, #667eea 0%, #764ba2 100%)&#039;,

borderRadius: 12,

}}

/&gt;

);

}</code></pre>

<p>Aqui, o elemento começa invisível e deslocado para cima, depois anima para sua posição normal com opacidade total. A propriedade <code>transition</code> controla a duração e o tipo de easing. Framer Motion cuida de interpolar todos os valores entre os dois estados.</p>

<h3>Animações Complexas com Variantes</h3>

<p>Quando você tem múltiplos elementos que devem animar em sequência ou com coordenação, use variantes. Uma variante é um objeto que descreve diferentes estados visuais. Você nomeia cada estado e pode reutilizá-lo em múltiplos elementos.</p>

<pre><code class="language-jsx">import { motion } from &#039;framer-motion&#039;;

export default function ListAnimation() {

const containerVariants = {

hidden: { opacity: 0 },

visible: {

opacity: 1,

transition: {

staggerChildren: 0.1,

},

},

};

const itemVariants = {

hidden: { opacity: 0, x: -20 },

visible: { opacity: 1, x: 0 },

};

const items = [&#039;Item 1&#039;, &#039;Item 2&#039;, &#039;Item 3&#039;, &#039;Item 4&#039;];

return (

&lt;motion.ul

variants={containerVariants}

initial=&quot;hidden&quot;

animate=&quot;visible&quot;

style={{ listStyle: &#039;none&#039;, padding: 0 }}

&gt;

{items.map((item, index) =&gt; (

&lt;motion.li

key={index}

variants={itemVariants}

style={{

padding: &#039;16px&#039;,

marginBottom: &#039;8px&#039;,

background: &#039;#f0f0f0&#039;,

borderRadius: &#039;8px&#039;,

}}

&gt;

{item}

&lt;/motion.li&gt;

))}

&lt;/motion.ul&gt;

);

}</code></pre>

<p>A propriedade <code>staggerChildren</code> faz com que cada filho animeassistindo um após o outro, com um atraso de 0.1 segundos entre eles. Isso cria um efeito elegante onde os itens aparecem um por um. Variantes tornam fácil gerenciar animações complexas sem propagar props de timing para cada elemento.</p>

<h3>Gestos e Interatividade</h3>

<p>Framer Motion permite que você responda a gestos do usuário — hover, tap, drag — com animações. Isso torna as interfaces não apenas bonitas visualmente, mas também responsivas e agradáveis de usar.</p>

<pre><code class="language-jsx">import { motion } from &#039;framer-motion&#039;;

export default function InteractiveCard() {

return (

&lt;motion.div

whileHover={{ scale: 1.05, y: -10 }}

whileTap={{ scale: 0.95 }}

transition={{ type: &#039;spring&#039;, stiffness: 300, damping: 20 }}

style={{

width: 250,

height: 150,

background: &#039;linear-gradient(135deg, #667eea 0%, #764ba2 100%)&#039;,

borderRadius: 12,

cursor: &#039;pointer&#039;,

display: &#039;flex&#039;,

alignItems: &#039;center&#039;,

justifyContent: &#039;center&#039;,

color: &#039;white&#039;,

fontSize: 18,

fontWeight: &#039;bold&#039;,

}}

&gt;

Hover ou clique

&lt;/motion.div&gt;

);

}</code></pre>

<p>Quando o usuário passa o mouse sobre o elemento, ele escala para 1.05 (5% maior) e sobe 10 pixels. Quando clica, ele reduz para 0.95. A propriedade <code>transition</code> usa uma animação de mola (spring), que é mais natural do que uma linear — o elemento &quot;oscila&quot; levemente ao alcançar seu destino, simulando física real.</p>

<h3>AnimatePresence para Mount/Unmount</h3>

<p>Um dos problemas ao remover elementos do DOM é que as animações de saída são perdidas. <code>AnimatePresence</code> resolve isso permitindo que elementos animem antes de serem removidos. Use a propriedade <code>exit</code> para definir o estado final.</p>

<pre><code class="language-jsx">import { motion, AnimatePresence } from &#039;framer-motion&#039;;

import { useState } from &#039;react&#039;;

export default function ToggleWithExit() {

const [isVisible, setIsVisible] = useState(true);

return (

&lt;&gt;

&lt;button onClick={() =&gt; setIsVisible(!isVisible)}&gt;

{isVisible ? &#039;Ocultar&#039; : &#039;Mostrar&#039;}

&lt;/button&gt;

&lt;AnimatePresence&gt;

{isVisible &amp;&amp; (

&lt;motion.div

initial={{ opacity: 0, scale: 0.8 }}

animate={{ opacity: 1, scale: 1 }}

exit={{ opacity: 0, scale: 0.8 }}

transition={{ duration: 0.3 }}

style={{

marginTop: 16,

width: 200,

height: 200,

background: &#039;#3498db&#039;,

borderRadius: 12,

}}

/&gt;

)}

&lt;/AnimatePresence&gt;

&lt;/&gt;

);

}</code></pre>

<p>Aqui, quando o usuário clica no botão, o elemento anima sua saída (encolhendo e desaparecendo) antes de ser removido do DOM. Sem <code>AnimatePresence</code>, o elemento desapareceria instantaneamente. <code>AnimatePresence</code> é crucial para criar experiências polidas.</p>

<h2>React Spring: Animações Baseadas em Física</h2>

<h3>O Paradigma da Física em Animações</h3>

<p>React Spring é fundamentalmente diferente de Framer Motion. Em vez de basear-se em duração e easing, ela simula física do mundo real. Você não diz &quot;animar durante 0.6 segundos&quot;, mas sim &quot;simular uma mola com rigidez e amortecimento específicos&quot;. Isso resulta em animações que se sentem naturais porque obedecem às leis da física.</p>

<p>O conceito central é que um valor está em um ponto A e você quer que chegue a um ponto B. A mola puxará ele para lá, mas pode fazer oscilações — exatamente como um objeto físico real se comportaria. Isso é mais previsível e agradável visualmente do que animações baseadas em tempo.</p>

<pre><code class="language-jsx">import { useSpring, animated } from &#039;@react-spring/web&#039;;

export default function BasicSpring() {

const spring = useSpring({

from: { opacity: 0, transform: &#039;translate3d(0, -40px, 0)&#039; },

to: { opacity: 1, transform: &#039;translate3d(0, 0px, 0)&#039; },

config: { tension: 280, friction: 60 },

});

return (

&lt;animated.div

style={{

...spring,

width: 200,

height: 200,

background: &#039;linear-gradient(135deg, #667eea 0%, #764ba2 100%)&#039;,

borderRadius: 12,

}}

/&gt;

);

}</code></pre>

<p>O hook <code>useSpring</code> recebe um objeto com <code>from</code> (estado inicial), <code>to</code> (estado final) e <code>config</code> (configuração da física). <code>tension</code> controla a força da mola (quanto maior, mais rápido), enquanto <code>friction</code> controla o amortecimento (quanto maior, menos oscilações). O resultado é um objeto de valores animados que você aplica ao componente animado.</p>

<h3>Respondendo a Mudanças de Estado</h3>

<p>React Spring brilha quando valores mudam frequentemente. Se você tem um estado que muda e quer que a animação siga suavemente, useSpring fará isso com naturalidade.</p>

<pre><code class="language-jsx">import { useSpring, animated } from &#039;@react-spring/web&#039;;

import { useState } from &#039;react&#039;;

export default function ToggleBox() {

const [isOpen, setIsOpen] = useState(false);

const spring = useSpring({

width: isOpen ? 300 : 100,

height: isOpen ? 300 : 100,

backgroundColor: isOpen ? &#039;#e74c3c&#039; : &#039;#3498db&#039;,

config: { tension: 200, friction: 26 },

});

return (

&lt;&gt;

&lt;button onClick={() =&gt; setIsOpen(!isOpen)}&gt;

{isOpen ? &#039;Fechar&#039; : &#039;Abrir&#039;}

&lt;/button&gt;

&lt;animated.div

style={{

...spring,

marginTop: 16,

borderRadius: 12,

cursor: &#039;pointer&#039;,

}}

onClick={() =&gt; setIsOpen(!isOpen)}

/&gt;

&lt;/&gt;

);

}</code></pre>

<p>Sempre que <code>isOpen</code> muda, React Spring detecta e anima suavemente para os novos valores. Não há lógica manual de timing — a física cuida de tudo. Isso é particularmente útil em dashboards, filtros e componentes responsivos onde valores mudam constantemente.</p>

<h3>useTrail para Múltiplos Elementos</h3>

<p>Para animar múltiplos elementos em sequência, use <code>useTrail</code>. Cada elemento segue o anterior como se estivesse preso por uma mola, criando um efeito fluido e natural.</p>

<pre><code class="language-jsx">import { useTrail, animated } from &#039;@react-spring/web&#039;;

export default function TrailExample() {

const items = [&#039;A&#039;, &#039;B&#039;, &#039;C&#039;, &#039;D&#039;, &#039;E&#039;];

const trail = useTrail(items.length, {

from: { opacity: 0, x: -40 },

to: { opacity: 1, x: 0 },

config: { tension: 280, friction: 60 },

});

return (

&lt;div style={{ display: &#039;flex&#039;, gap: &#039;16px&#039; }}&gt;

{trail.map((style, index) =&gt; (

&lt;animated.div

key={index}

style={{

...style,

width: 60,

height: 60,

background: &#039;#667eea&#039;,

borderRadius: 8,

display: &#039;flex&#039;,

alignItems: &#039;center&#039;,

justifyContent: &#039;center&#039;,

color: &#039;white&#039;,

fontWeight: &#039;bold&#039;,

}}

&gt;

{items[index]}

&lt;/animated.div&gt;

))}

&lt;/div&gt;

);

}</code></pre>

<p><code>useTrail</code> retorna um array de objetos de estilo animados, um para cada elemento. Cada um segue o anterior com um atraso natural, criando um efeito cascata elegante. Compare isso com Framer Motion&#039;s <code>staggerChildren</code> — React Spring dá a sensação de que os elementos estão fisicamente conectados.</p>

<h3>useChain para Sincronização Complexa</h3>

<p>Quando você precisa sincronizar múltiplas animações em sequência, <code>useChain</code> é a ferramenta certa. Você define múltiplos hooks de animação e os encadeia em uma ordem específica.</p>

<pre><code class="language-jsx">import { useSpring, useChain, animated, useRef } from &#039;@react-spring/web&#039;;

export default function ChainedAnimations() {

const ref1 = useRef();

const ref2 = useRef();

const spring1 = useSpring({

ref: ref1,

from: { opacity: 0 },

to: { opacity: 1 },

config: { duration: 500 },

});

const spring2 = useSpring({

ref: ref2,

from: { transform: &#039;translateX(-50px)&#039; },

to: { transform: &#039;translateX(0px)&#039; },

config: { tension: 280, friction: 60 },

});

useChain([ref1, ref2], [0, 0.5]);

return (

&lt;&gt;

&lt;animated.div

style={{

...spring1,

width: 200,

height: 100,

background: &#039;#667eea&#039;,

borderRadius: 8,

marginBottom: 16,

}}

/&gt;

&lt;animated.div

style={{

...spring2,

width: 200,

height: 100,

background: &#039;#764ba2&#039;,

borderRadius: 8,

}}

/&gt;

&lt;/&gt;

);

}</code></pre>

<p>Aqui, o primeiro elemento anima do tempo 0 ao 0.5, e o segundo começa no tempo 0.5 e vai até o final. Você tem controle total sobre quando cada animação começa e termina, permitindo sequências complexas e coordenadas.</p>

<h2>Comparação Prática e Quando Usar Cada Uma</h2>

<h3>CSS Transitions</h3>

<p>Use CSS Transitions quando:</p>

<ul>

<li>A animação é simples (hover, mudança de cor, redimensionamento)</li>

<li>Você quer a máxima performance com mínimo overhead</li>

<li>A mudança é baseada em mudança de classe CSS simples</li>

<li>Você não precisa de sequência ou coordenação complexa</li>

</ul>

<p>Desvantagem: Difícil de controlar precisamente, não funciona bem com múltiplos elementos, sem resposta a eventos complexos.</p>

<h3>Framer Motion</h3>

<p>Use Framer Motion quando:</p>

<ul>

<li>Você precisa de animações declarativas e fáceis de ler</li>

<li>Quer sequenciar múltiplas animações com <code>staggerChildren</code></li>

<li>Precisa de gestos (hover, tap, drag) integrados</li>

<li>Quer um código que seja simples de entender e manter</li>

</ul>

<p>Desvantagem: Um pouco mais pesado que CSS puro, menos natural em simulações de física.</p>

<h3>React Spring</h3>

<p>Use React Spring quando:</p>

<ul>

<li>Precisa de animações que obedeçam física realista</li>

<li>Tem valores que mudam constantemente e quer que a animação acompanhe suavemente</li>

<li>Quer <code>useTrail</code> ou <code>useChain</code> para sincronização complexa</li>

<li>Pretende criar interações avançadas como drag com resistência</li>

</ul>

<p>Desvantagem: Curva de aprendizado um pouco maior, sintaxe menos intuitiva que Framer Motion para iniciantes.</p>

<pre><code class="language-jsx">// Exemplo comparativo: animar um elemento respondendo a um toggle

// 1. COM CSS TRANSITIONS

function CSSVersion() {

const [isOpen, setIsOpen] = useState(false);

return (

&lt;div&gt;

&lt;button onClick={() =&gt; setIsOpen(!isOpen)}&gt;Toggle&lt;/button&gt;

&lt;style&gt;{`

.box { width: 100px; transition: width 0.3s; }

.box.open { width: 200px; }

`}&lt;/style&gt;

&lt;div className={box ${isOpen ? &#039;open&#039; : &#039;&#039;}} /&gt;

&lt;/div&gt;

);

}

// 2. COM FRAMER MOTION

function FramerVersion() {

const [isOpen, setIsOpen] = useState(false);

return (

&lt;div&gt;

&lt;button onClick={() =&gt; setIsOpen(!isOpen)}&gt;Toggle&lt;/button&gt;

&lt;motion.div

animate={{ width: isOpen ? 200 : 100 }}

transition={{ duration: 0.3 }}

style={{ height: 100, background: &#039;#667eea&#039; }}

/&gt;

&lt;/div&gt;

);

}

// 3. COM REACT SPRING

function SpringVersion() {

const [isOpen, setIsOpen] = useState(false);

const spring = useSpring({

width: isOpen ? 200 : 100,

config: { tension: 280, friction: 60 },

});

return (

&lt;div&gt;

&lt;button onClick={() =&gt; setIsOpen(!isOpen)}&gt;Toggle&lt;/button&gt;

&lt;animated.div

style={{ ...spring, height: 100, background: &#039;#667eea&#039; }}

/&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Todos os três fazem a mesma coisa, mas com diferentes abordagens. CSS Transitions é a mais leve, Framer Motion é a mais expressiva, React Spring é a mais natural.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.framer.com/motion/" target="_blank" rel="noopener noreferrer">Documentação Oficial do Framer Motion</a></li>

<li><a href="https://www.react-spring.dev/" target="_blank" rel="noopener noreferrer">Documentação Oficial do React Spring</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions" target="_blank" rel="noopener noreferrer">MDN: CSS Transitions</a></li>

<li><a href="https://web.dev/animations/" target="_blank" rel="noopener noreferrer">Artigo: Animation Performance Best Practices - Web.dev</a></li>

<li><a href="https://egghead.io/courses/animate-react-components-with-framer-motion" target="_blank" rel="noopener noreferrer">Kent C. Dodds: Animating React Components with Framer Motion</a></li>

</ul>

<h2>Conclusão</h2>

<p>Você agora compreende três paradigmas diferentes de animação em React, cada um resolvendo problemas específicos. <strong>Primeiro ponto importante</strong>: CSS Transitions são suficientes para a maioria das animações simples, e começar por elas reduz complexidade desnecessária. <strong>Segundo</strong>: Framer Motion brilha em UX moderna porque sua API declarativa é intuitiva e seus recursos de gesto são poderosos — é a melhor escolha quando você quer código limpo e manutenível. <strong>Terceiro</strong>: React Spring é a ferramenta certa quando você precisa que as animações se sintam naturais ou quando tem mudanças de estado contínuas que precisam acompanhamento suave, oferecendo um nível de controle que CSS e até Framer Motion não conseguem.</p>

<p>A escolha entre elas não é &quot;qual é melhor&quot;, mas &quot;qual resolve melhor este problema específico&quot;. Um projeto profissional frequentemente usa as três — CSS para efeitos triviais, Framer Motion para componentes reutilizáveis, e React Spring para interações avançadas. O domínio de todas elas faz você um desenvolvedor mais completo e capaz de tomar decisões arquiteturais conscientes.</p>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Como Usar Playwright com React: E2E, Visual Regression e Component Testing em Produção
Como Usar Playwright com React: E2E, Visual Regression e Component Testing em Produção

Entendendo Playwright e sua Integração com React Playwright é uma framework d...

Internacionalização em React: react-i18next e Formatação de Dados na Prática
Internacionalização em React: react-i18next e Formatação de Dados na Prática

Entendendo Internacionalização em Aplicações React Internacionalização (i18n)...

useReducer em Profundidade: State Machines e Fluxo Previsível: Do Básico ao Avançado
useReducer em Profundidade: State Machines e Fluxo Previsível: Do Básico ao Avançado

Entendendo useReducer: Além do useState O é um hook do React que oferece uma...