Animaciones y Gestos
Volver a clases
Desarrollo Móvil●●Intermedio

Animaciones y Gestos

60 min

Material de consulta. Por qué las animaciones se sienten con tirones, cómo moverlas al hilo nativo, y cuándo NO animar.

Etiquetas

#animaciones#gestos#reanimated

📚 Material de consulta. No ocupa sesión de clase. Está aquí para cuando tu proyecto lo necesite.

Por qué las animaciones importan más en móvil

En una pantalla de teléfono cabe poco y todo cambia rápido. Sin movimiento, el usuario pierde el hilo: ¿esta pantalla es nueva o es la misma?, ¿de dónde salió esta tarjeta?, ¿mi toque hizo algo?

Las animaciones bien usadas no son decoración: son orientación. Le dicen al usuario de dónde viene y a dónde va lo que está viendo. Y tienen un efecto medible en la percepción: una operación con una transición suave se siente más rápida que la misma operación instantánea pero abrupta.

El concepto que lo explica todo: dos hilos

React Native corre tu JavaScript en un hilo y dibuja la interfaz en otro. Esa separación es lo que hace que la app siga respondiendo mientras tu código trabaja… y también la causa de las animaciones con tirones.

Sin useNativeDriver, cada fotograma de tu animación cruza el puente entre los dos hilos. Sesenta veces por segundo. Si el hilo de JS está ocupado —renderizando una lista, procesando datos—, los fotogramas llegan tarde y el usuario ve tirones.

Con el driver nativo, la animación se entrega completa al lado nativo desde el inicio: JS dice "lleva este valor de 0 a 1 en 300 ms" y se desentiende. La animación sigue fluida aunque el hilo de JS esté saturado.

Por eso la primera tabla es tan corta: solo opacity y transform pueden viajar al hilo nativo. Todo lo demás obliga a recalcular el layout en cada fotograma.

Antes de animar: que las cosas respondan

Si vas a invertir esfuerzo en un solo detalle de movimiento, que sea este: todo lo tocable debe reaccionar de inmediato.

En web existe :hover y el cursor cambia de forma; en móvil no hay nada de eso. El único modo que tiene el usuario de saber que su toque se registró es que algo cambie al instante. El tercer snippet lo resuelve con Pressable y sin ninguna librería — y arregla más de la percepción de calidad de tu app que cualquier animación elaborada.

Gestos, en una nota

Pressable cubre toques. Para gestos reales —deslizar para eliminar, arrastrar, pellizcar para hacer zoom— se usa react-native-gesture-handler, normalmente junto con Reanimated.

La razón de que sean dos bibliotecas separadas y no la API base: un gesto necesita responder al dedo en cada milisegundo, y pasar por el hilo de JS introduce retraso perceptible. Ambas bibliotecas existen para que el gesto y su animación ocurran del lado nativo, sin intermediarios.

Si tu proyecto necesita algo más que toques, ese es el camino. No intentes construir un swipe con PanResponder y Animated sin driver nativo: es el clásico que se siente pesado y no hay forma de arreglarlo después.

Cuándo NO animar

Tres casos donde la animación resta:

  • Acciones frecuentes. Una transición de 300 ms es elegante la primera vez y molesta la vigésima. Lo que el usuario hace mucho, que sea instantáneo.
  • Contenido crítico. No hagas esperar una animación para mostrar un error o un dato que el usuario está buscando.
  • Cuando el sistema pidió menos movimiento. El cuarto snippet muestra cómo detectarlo. Hay personas para quienes el movimiento en pantalla provoca mareo real; el sistema operativo les da un interruptor y tu app debe respetarlo.

La regla general: animaciones cortas (150–300 ms) y con propósito. Si no sabes qué le está comunicando al usuario, probablemente sobra.

Ejemplos de Código

4 ejemplos

Qué se puede animar en el hilo nativo

La lista es corta a propósito.

text
1✅ CON useNativeDriver (fluidas aunque JS esté ocupado)
2   opacity
3   transform: translateX, translateY, scale, scaleX, scaleY, rotate
4
5❌ SIN driver nativo (recalculan layout, dependen del hilo JS)
6   height, width
7   margin, padding
8   top, left, right, bottom
9   backgroundColor, borderRadius
10
11Regla práctica: si quieres animar tamaño, casi siempre puedes
12lograr el mismo efecto con scale o con translate + overflow hidden.

Animación básica de entrada

El patrón de fade + slide que cubre la mayoría de los casos.

javascript
1function TarjetaAnimada({ children }) {
2  const progreso = useRef(new Animated.Value(0)).current;
3
4  useEffect(() => {
5    Animated.timing(progreso, {
6      toValue: 1,
7      duration: 250,
8      useNativeDriver: true,
9    }).start();
10  }, []);
11
12  return (
13    <Animated.View
14      style={{
15        opacity: progreso,
16        transform: [{
17          translateY: progreso.interpolate({
18            inputRange: [0, 1],
19            outputRange: [20, 0],   // sube 20px al aparecer
20          }),
21        }],
22      }}
23    >
24      {children}
25    </Animated.View>
26  );
27}

Feedback táctil — lo mínimo indispensable

Antes de animaciones elaboradas, asegura que todo lo tocable responda.

jsx
1// Pressable ya trae estado de presionado sin librerías:
2<Pressable
3  onPress={onPress}
4  style={({ pressed }) => [
5    styles.boton,
6    pressed && { opacity: 0.6, transform: [{ scale: 0.98 }] },
7  ]}
8>
9  <Text>Guardar</Text>
10</Pressable>
11
12Por qué importa: en móvil no hay :hover ni cursor. El ÚNICO modo
13que tiene el usuario de saber que su toque se registró es que algo
14cambie de inmediato. Un botón que no responde al tacto se siente
15roto, aunque funcione.

Respetar a quien no quiere movimiento

Requisito de accesibilidad, no detalle opcional.

javascript
1import { AccessibilityInfo } from "react-native";
2
3const [reducirMovimiento, setReducir] = useState(false);
4
5useEffect(() => {
6  AccessibilityInfo.isReduceMotionEnabled().then(setReducir);
7  const sub = AccessibilityInfo.addEventListener(
8    "reduceMotionChanged",
9    setReducir
10  );
11  return () => sub.remove();
12}, []);
13
14// Y en la animación:
15duration: reducirMovimiento ? 0 : 300
16
17Hay personas con trastornos vestibulares para quienes el movimiento
18en pantalla provoca mareo real. El sistema operativo les permite
19pedir "menos movimiento"; tu app debe hacerle caso.

Recursos

2 recursos disponibles

Reto de Lectura

Reto de LecturaIntermedio30 min🔴 Sin IA

Reto de Lectura — La animación que da tirones

Se ve fluida en el emulador de tu laptop y a tirones en un teléfono de gama baja. Tres decisiones explican la diferencia.

Este menú desplegable "funciona": se abre y se cierra con animación. En un iPhone reciente se ve bien; en un Android de gama media va a tirones y al scrollear la lista de atrás se congela.

jsx
1function MenuDesplegable({ abierto }) {
2  const altura = useRef(new Animated.Value(0)).current;
3
4  useEffect(() => {
5    Animated.timing(altura, {
6      toValue: abierto ? 300 : 0,
7      duration: 300,
8    }).start();
9  }, [abierto]);
10
11  return (
12    <Animated.View style={{ height: altura, overflow: "hidden" }}>
13      {opciones.map((o) => (
14        <Opcion key={o.id} dato={o} />
15      ))}
16    </Animated.View>
17  );
18}

Preguntas:

  1. Falta una propiedad en la configuración de Animated.timing. ¿Cuál es y qué cambia?
  2. Se está animando height. ¿Por qué esa propiedad es especialmente cara comparada con mover o escalar?
  3. ¿Qué relación tiene el tirón con el hilo de JavaScript? ¿Por qué empeora justo cuando el usuario scrollea?
  4. Reescribe la animación para que corra en el hilo nativo.

Documentación Oficial

DocumentaciónPrincipiante15 min

Documentación de apoyo

Las bibliotecas y guías de esta clase.