Moti es una biblioteca de animaciones para React Native (y web) construida sobre Reanimated 3, diseñada para ofrecer control preciso y eficiente de efectos visuales durante la ejecución. Su gancho useDynamicAnimation permite manipular propiedades estilísticas de forma reactiva sin desencadenar re-renderizados innecesarios, logrando animaciones fluidas incluso bajo carga intensa del hilo JavaScript.
¿Qué hace useDynamicAnimation?
Este gancho expone un objeto animado cuyo estado se actualiza directamente en el hilo nativo, evitando el ciclo típico de actualización de estado de React. A diferencia de los hooks convencionales, no requiere useState ni useRef para almacenar valores intermedios: el estado animado reside en memoria nativa y se sincroniza de forma óptima con el renderizador.
Características clave
- Ejecución fuera del hilo principal: todas las interpolaciones y transiciones ocurran en el hilo nativo, garantizando 60 FPS independientemente de la carga de la UI.
- Actualizaciones sin re-render: al modificar propiedades animadas (como
opacity,scale,translateX), el componente no vuelve a renderizarse. - API funcional y predecible: su firma sigue patrones familiares — similar a
setState, pero con soporte para worklets y composición avanzada.
Ejemplo práctico
A continuación, un componente que ajusta su escala y opacidad en respuesta a eventos táctiles:
import { useDynamicAnimation } from 'moti';
function AnimatedCard() {
const visualState = useDynamicAnimation(() => ({
opacity: 0.8,
scale: 1,
rotateZ: '0deg',
}));
const handlePressIn = () => {
visualState.animateTo({
opacity: 1,
scale: 1.15,
rotateZ: '2deg',
transition: { type: 'spring', stiffness: 120 },
});
};
const handlePressOut = () => {
visualState.animateTo({
opacity: 0.8,
scale: 1,
rotateZ: '0deg',
transition: { type: 'spring', damping: 15 },
});
};
return (
<MotiView
style={visualState}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
/>
);
}
Integración con gestos
Cuando se combina con useAnimatedGestureHandler, permite vincular transformaciones directamente a movimientos del usuario:
const gestureHandler = useAnimatedGestureHandler({
onActive: (event) => {
visualState.animateTo({
translateX: event.translationX,
translateY: event.translationY,
});
},
onEnd: () => {
visualState.animateTo({
translateX: 0,
translateY: 0,
transition: { type: 'timing', duration: 250 },
});
},
});
Patrones avanzados
- Animaciones seceunciales: pase un array de valores para definir etapas progresivas:
visualState.animateTo({
opacity: [0.2, 0.7, { value: 1, delay: 300 }],
});
- Actualización basada en estado actual: use funciones para acceder al valor previo:
visualState.animateTo((prev) => ({
...prev,
scale: prev.scale > 1 ? 1 : 1.25,
}));
Recomendaciones de rendimiento
- No desestructure el objeto devuelto por
useDynamicAnimation; acceda sus propiedades directamente. - Utilice siempre
animateTodentro de worklets o callbacks gestionados por Reanimated (comoonActiveowithTiming). - Evite asignar valores animados a props no compatibles con el hilo nativo (por ejemplo,
zIndexodisplay).
La flexibilidad de useDynamicAnimation permite implementar desde microinteracciones sutiles hasta flujos complejos de transición, manteniendo un código limpio y altamente mantenible.