<h2>Entendendo Hooks para Animações no React</h2>
<p>Animações são fundamentais para criar interfaces modernas e responsivas. No universo React, hooks especializados como <code>useSpring</code>, <code>useAnimate</code> e conceitos de física de movimento revolucionaram a forma como desenvolvemos transições fluidas. Diferente de CSS puro ou bibliotecas genéricas, esses hooks permitem sincronizar estado com animações de forma declarativa, respeitando o ciclo de vida dos componentes.</p>
<p>O grande diferencial desses hooks é que eles não apenas aplicam estilos visuais, mas integram-se profundamente com a lógica de componente. Você controla quando uma animação começa, para ou inverte com base em mudanças de estado reais. Além disso, eles lidam automaticamente com cancelamento de animações quando componentes desmontam, evitando memory leaks — um problema comum em implementações ingênuas.</p>
<h2>useSpring: Animações Simples e Diretas</h2>
<h3>O Conceito Fundamental</h3>
<p><code>useSpring</code> é um hook da biblioteca React Spring que transforma valores de um estado inicial para um final com uma animação suave. Pense nele como um interpolador inteligente: você fornece os valores iniciais e finais, e ele calcula todos os frames intermediários baseado em uma configuração física de primavera (spring).</p>
<p>A "primavera" não é metafórica. Internamente, <code>useSpring</code> usa simulação de física: quanto mais rígida a spring (maior <code>tension</code>), mais rápido ela chega ao alvo; quanto maior o <code>friction</code>, mais ela desacelera. Isso cria movimentos naturais que respeitam leis da física, não frames arbitrários.</p>
<h3>Exemplo Prático: Animação de Opacidade</h3>
<pre><code class="language-javascript">import { useSpring, animated } from '@react-spring/web';
function FadeInBox() {
const [isVisible, setIsVisible] = React.useState(false);
const springProps = useSpring({
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0px)' : 'translateY(20px)',
config: {
tension: 280,
friction: 60,
},
});
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle
</button>
<animated.div
style={springProps}
className="box"
>
Conteúdo que aparece com suavidade
</animated.div>
</div>
);
}</code></pre>
<p>Note que não escrevemos CSS transitions. O componente <code>animated.div</code> recebe um objeto estilizado que é recalculado a cada frame. Quando <code>isVisible</code> muda, <code>useSpring</code> automaticamente anima a transição entre os valores antigos e novos.</p>
<h3>Configurações de Física</h3>
<p>A configuração <code>config</code> aceita presets ou valores customizados. Os presets (<code>molasses</code>, <code>gentle</code>, <code>wobbly</code>, <code>stiff</code>) são atalhos para combinações pré-testadas de <code>tension</code> e <code>friction</code>. Para controle fino:</p>
<pre><code class="language-javascript">const springProps = useSpring({
value: targetValue,
config: {
tension: 170, // rigidez: 1-300
friction: 26, // amortecimento: 1-100
mass: 1, // peso do objeto: 1-10
clamp: false, // previne overshoot
},
});</code></pre>
<p>Aumentar <code>tension</code> sem ajustar <code>friction</code> causa "bouncing" — a animação ultrapassa o alvo e volta. Use <code>clamp: true</code> se quiser evitar esse efeito.</p>
<h2>useAnimate: Controle Sequencial e Completo</h2>
<h3>Diferenciação: Por Que Não Só useSpring?</h3>
<p>Enquanto <code>useSpring</code> é excelente para transições simples, <code>useAnimate</code> oferece controle narrativo. Você pode executar múltiplas animações em sequência, paralelo, com delays específicos, e até parar/resumir programaticamente. É o hook certo quando sua animação é mais "ação" que "transição de estado".</p>
<p><code>useAnimate</code> pertence à mesma família React Spring, mas sua API é imperativa — você chama funções para disparar animações, não apenas passa valores finais. Isso é poderoso para tutoriais, onboardings, ou qualquer sequência complexa.</p>
<h3>Exemplo: Sequência de Animações</h3>
<pre><code class="language-javascript">import { useAnimate, stagger } from '@react-spring/web';
import React from 'react';
function SequentialAnimation() {
const [scope, animate] = useAnimate();
const handleAnimateSequence = async () => {
await animate(
'div.item',
{ opacity: 1, y: 0 },
{
duration: 0.6,
delay: stagger(0.1), // 100ms entre cada elemento
}
);
};
React.useEffect(() => {
// Começar com itens invisíveis
animate(
'div.item',
{ opacity: 0, y: 20 },
{ duration: 0 }
);
}, []);
return (
<div ref={scope}>
<button onClick={handleAnimateSequence}>
Animar Lista
</button>
<div className="item">Item 1</div>
<div className="item">Item 2</div>
<div className="item">Item 3</div>
</div>
);
}</code></pre>
<p>O padrão aqui é: criar referência com <code>useAnimate</code>, usar <code>animate()</code> para modificar elementos selecionados via CSS, e <code>stagger()</code> para aplicar delays progressivos. Cada chamada <code>await animate()</code> representa um passo na sequência.</p>
<h3>Animações Condicionais e Controle</h3>
<pre><code class="language-javascript">function AnimatedForm() {
const [scope, animate] = useAnimate();
const [submitted, setSubmitted] = React.useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
// Animar erro ou sucesso baseado em validação
if (!isValid()) {
await animate(scope.current, {
x: [0, -10, 10, -10, 0],
}, {
duration: 0.4,
});
return;
}
// Sucesso: fade out
await animate(scope.current, {
opacity: 0,
scale: 0.8,
}, {
duration: 0.5,
});
setSubmitted(true);
};
return (
<form ref={scope} onSubmit={handleSubmit}>
<input type="email" />
<button>Enviar</button>
</form>
);
}</code></pre>
<p>Aqui vemos <code>useAnimate</code> ser condicional: a animação de erro (shake) só ocorre se validação falhar. Isso é impossível com <code>useSpring</code> puro, que sempre anima quando estado muda.</p>
<h2>Física de Movimento: Além das Primaveras</h2>
<h3>Princípios de Física Aplicados</h3>
<p>Qualquer animação convincente obedece leis da física. Aceleração, velocidade, inércia — essas propriedades criam movimentos que parecem reais porque nossos olhos evoluíram para detectá-las. <code>useSpring</code> simula uma primavera ideal, mas existe mais no arsenal.</p>
<p>A física de movimento inclui:</p>
<ul>
<li><strong>Momentum</strong>: quando você arrasta algo, ele continua se movendo mesmo após soltar (inércia)</li>
<li><strong>Damping</strong>: resistência do ar ou atrito que desacelera movimento</li>
<li><strong>Overshoot</strong>: excesso além do alvo antes de estabilizar</li>
<li><strong>Easing</strong>: aplicar funções matemáticas para controlar aceleração/desaceleração</li>
</ul>
<h3>Usando useSpring com Física Realística</h3>
<pre><code class="language-javascript">import { useSpring, animated, config } from '@react-spring/web';
function DragBox() {
const [{ x, y }, api] = useSpring(() => ({
x: 0,
y: 0,
config: config.molasses, // pré-configurado para movimento pesado
}));
const handleMouseMove = (e) => {
const rect = e.currentTarget.getBoundingClientRect();
api.start({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
};
return (
<animated.div
onMouseMove={handleMouseMove}
style={{
width: '100px',
height: '100px',
background: 'blue',
transform: x.to((val) => translate(${val}px, ${y.get()}px)),
}}
/>
);
}</code></pre>
<p><code>config.molasses</code> é mais lento e pesado que <code>config.stiff</code>. O mouse se move para uma posição, mas o box "segue" com inércia, criando sensação de peso. Isso é feito variando <code>friction</code> e <code>tension</code> — não há mágica, apenas matemática.</p>
<h3>Implementando Momentum Customizado</h3>
<p>Para efeitos mais avançados como scroll momentum ou drag-to-dismiss, você pode combinar <code>useSpring</code> com detecção de velocidade:</p>
<pre><code class="language-javascript">import { useSpring, animated } from '@react-spring/web';
import { useGesture } from '@use-gesture/react';
function SwipeCard() {
const [{ x }, api] = useSpring(() => ({ x: 0 }));
const bind = useGesture({
onDrag: ({ offset: [ox], direction: [dx], velocity: [vx] }) => {
const isFlicked = Math.abs(vx) > 0.5; // velocidade suficiente
api.start({
x: isFlicked && Math.abs(ox) > 50
? dx > 0
? 500
: -500
: 0,
config: { velocity: vx, tension: 200, friction: 20 },
});
},
});
return (
<animated.div
{...bind()}
style={{ x, touchAction: 'none' }}
className="card"
>
Deslize para descartar
</animated.div>
);
}</code></pre>
<p>Aqui, capturamos a velocidade do gesto e a passamos diretamente para <code>useSpring</code>. O hook continua o movimento com a inércia capturada, em vez de começar do zero. Resultado: movimento que sente-se natural e responsivo.</p>
<h2>Combinando Tudo: Exemplo Completo de Interface Animada</h2>
<h3>Construindo um Menu Animado com Física</h3>
<pre><code class="language-javascript">import React from 'react';
import { useSpring, useTrail, animated, config } from '@react-spring/web';
function AnimatedMenu() {
const [open, setOpen] = React.useState(false);
// Animação do botão hamburger
const hamburgerSpring = useSpring({
transform: open ? 'rotate(90deg)' : 'rotate(0deg)',
config: config.wobbly,
});
// Animação do background
const bgSpring = useSpring({
opacity: open ? 1 : 0,
pointerEvents: open ? 'auto' : 'none',
config: config.default,
});
// Items do menu com trail (animação escalonada)
const items = ['Home', 'Sobre', 'Projetos', 'Contato'];
const trail = useTrail(items.length, {
from: { opacity: 0, x: -40 },
to: { opacity: open ? 1 : 0, x: open ? 0 : -40 },
config: config.gentle,
});
return (
<>
<animated.button
style={hamburgerSpring}
onClick={() => setOpen(!open)}
>
☰
</animated.button>
<animated.div
style={bgSpring}
onClick={() => setOpen(false)}
className="menu-backdrop"
/>
<animated.nav
style={{
opacity: open ? 1 : 0,
y: open ? 0 : -20,
}}
className="menu"
>
{trail.map((style, index) => (
<animated.a
key={index}
style={style}
href="#"
>
{items[index]}
</animated.a>
))}
</animated.nav>
</>
);
}</code></pre>
<p>Este exemplo combina três técnicas: <code>useSpring</code> para o botão, outro <code>useSpring</code> para o backdrop, e <code>useTrail</code> (que é um array de springs) para itens do menu. Cada item entra em sequência suave. Quando o menu fecha, tudo volta inversamente. Tudo sincronizado via estado React.</p>
<h2>Conclusão</h2>
<p>Três aprendizados centrais ficam claros após dominar esses hooks:</p>
<ol>
<li><strong>Hooks de animação não são decoração</strong> — são ferramentas de controle de estado visual que integram-se ao ciclo de vida React, evitando bugs de sincronização e memory leaks que animações CSS puras frequentemente causam.</li>
</ol>
<ol>
<li><strong>Física de movimento real cria experiências credíveis</strong> — usar <code>config.molasses</code> ou ajustar <code>tension</code> e <code>friction</code> não é capricho, é fundamental; movimentos que respeitam inércia e damping parecem "vivos" para nossos olhos, enquanto animações lineares parecem robóticas.</li>
</ol>
<ol>
<li><strong>Escolha a ferramenta certa</strong> — <code>useSpring</code> para transições reativas a estado, <code>useAnimate</code> para sequências narrativas e controle imperativo, e sempre considere velocidade de gesto (momentum) ao trabalhar com interações baseadas em arraste.</li>
</ol>
<h2>Referências</h2>
<ul>
<li><a href="https://www.react-spring.dev/" target="_blank" rel="noopener noreferrer">React Spring Official Documentation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API" target="_blank" rel="noopener noreferrer">MDN: Web Animations API</a></li>
<li><a href="https://www.framer.com/motion/" target="_blank" rel="noopener noreferrer">Framer Motion Documentation</a></li>
<li><a href="https://www.sarahdrasnerdesign.com/" target="_blank" rel="noopener noreferrer">The Art of UI Animation - Sarah Drasner</a></li>
<li><a href="https://www.interaction-design.org/" target="_blank" rel="noopener noreferrer">Interaction Design: Beyond Human-Computer Interaction - Ixchel Reyes (capítulo sobre animação)</a></li>
</ul>
<p><!-- FIM --></p>