Mamun Labs Learn
JourneyGlossary
esc
  • Try “map”, “flexbox” or “state”.
Log in
Start learning
Phase 7 · 3 / 5Simple animation
Phase 7Games
5 topics · 3–4 wks
  1. 1The game loop
  2. 2Touch input
  3. 3Simple animation
  4. 4Collisions and score
  5. 5Sound and saving the best score
JourneyPhase 7Simple animation
01 / 09Overview

Simple animation

You'll learn to make the jump smooth with React Native's Animated: a value that moves from one number to another over time.

Why it mattersSmooth movement is what makes a game feel alive. The same tool fades in messages and slides in menus in normal apps.
about 90 min·needs: Touch input
02 / 09

Watch & read

Start with the required ones. The others are there when you need them.

Docs
Animationsreactnative.dev · official docs · 20 min · English
MMamun's noteRead the start of "Animated API" with the FadeInView example, then "Configuring animations" and "Composing animations". Read "Using the native driver" to see why we set it to false. Skip interpolation and gestures for now.
Video
Introduction to Animated API (React Native)YouTube · Reactiive · 9 min · English
MMamun's noteWatch 0:00–8:30: setup, timing animations, useNativeDriver and spring animations. He animates one square, so it is easy to follow. He also mentions Reanimated names in comments; you can ignore those. Stop at "Interpolate".
DocsOptional
Animated (API reference)reactnative.dev · official docs · 10 min · English
MMamun's noteThe full list. Look up timing(), spring() and sequence() when you want to know every option. You do not need to read it from top to bottom.
03 / 09

Explanation

Right now the square teleports: one tap and it is at the top. Real things do not move like that. A cricket ball rises, slows down, and then comes back down.

Think of a lift in a building. You press 5, and the lift does not appear at floor 5. It passes 2, 3 and 4 on the way. Animated is like that: you say where to go and how long to take, and it fills in all the numbers in between.

An Animated.Value is a number that Animated can move. Keep it in useRef, so the same value stays for the whole life of the screen:

const playerY = useRef(new Animated.Value(0)).current;

To jump, go up to JUMP_HEIGHT in 350 ms, then back to 0 in 350 ms. Animated.sequence runs them one after the other:

Animated.sequence([
Animated.timing(playerY, { toValue: JUMP_HEIGHT, duration: 350, useNativeDriver: false }),
Animated.timing(playerY, { toValue: 0, duration: 350, useNativeDriver: false }),
]).start();

Then draw the square with <Animated.View style={{ bottom: playerY }}>. A normal View cannot read an Animated.Value; it must be an Animated.View.

Animated.spring is the other way to move a value. Instead of a time, it acts like a spring, so the square can bounce a little when it lands.

useNativeDriver: false is needed here because we move bottom, which changes layout. The faster native driver only works for transform and opacity.

playerY is now not in game any more. Animated moves the square without rendering the component again, so the jump stays smooth even while the game loop is busy.

People often write new Animated.Value(0) without useRef. Then every render makes a brand new value at 0, and the jump snaps back to the ground in the middle.

Big games in React Native use a library called Reanimated. It runs animations on their own thread and can do much more. The idea is the same: a value, a target, and a time. Start with Animated.

Words to recognise
AnimatedAnimated.ValueAnimated.timingAnimated.springAnimated.sequenceuseRefReanimated
04 / 09

Code tour

Click a line number with a ring to read the note for that line.

App.jsx5 notes
const playerY = useRef(new Animated.Value(0)).current;
Mentor note · line 1
One Animated.Value, kept in useRef so it survives every render. It starts on the ground, at 0.
2const jumping = useRef(false);
3
4function jump() {
if (jumping.current) return;
6 jumping.current = true;
7 Animated.sequence([
Animated.timing(playerY, { toValue: JUMP_HEIGHT, duration: 350, useNativeDriver: false }),
9 Animated.timing(playerY, { toValue: 0, duration: 350, useNativeDriver: false }),
]).start(() => {
11 jumping.current = false;
12 });
13}
14// ...and in the return:
<Animated.View style={[styles.player, { bottom: playerY }]} />
05 / 09

Practice

Short drills. Take your time and read the code before you answer.

DrillPredict1 / 3
1function Jumper() {
2 const playerY = useRef(new Animated.Value(0)).current;
3 console.log("render");
4
5 function jump() {
6 Animated.timing(playerY, { toValue: 150, duration: 350, useNativeDriver: false }).start();
7 }
8
9 return (
10 <Pressable style={{ flex: 1 }} onPressIn={jump}>
11 <Animated.View style={{ position: "absolute", bottom: playerY, width: 40, height: 40 }} />
12 </Pressable>
13 );
14}
15
16// The screen appears, then you tap once and the square rises to 150.
17// Count in the real app (not the extra development check).
How many times is "render" printed?
DrillFind the bug2 / 3
jumpHeightAt(ms) says how high the square is ms milliseconds after the tap, for a straight-line jump (what Easing.linear would give). It goes up to 150 in 350 ms, back down to 0 by 700 ms, and then stays on the ground. This is the math Animated.timing does for you. One part is wrong: after the jump it goes under the ground. Fix it.
1
2
3
4
5
6
7
8
9
10
Try 1 of 3 · solution unlocks after 3
DrillA smooth jump3 / 3
The jump now rises and falls smoothly with Animated. The game loop only moves the obstacle. Tap to jump and compare it with the jump from the last topic.
1import { useEffect, useRef, useState } from "react";
2import { Animated, Pressable, StyleSheet, Text, View } from "react-native";
3
4const SPEED = 10;
5const SCREEN_WIDTH = 400;
6const OBSTACLE_WIDTH = 30;

Runs in the browser. Choose “My Device” in Snack to try it on your phone with Expo Go.

Working whenTap and watch the square rise, slow down at the top, and come back down.Change both duration values to 1000. The jump becomes slow, like on the moon. Put them back to 350.Change the second Animated.timing(...) to Animated.spring(playerY, { toValue: 0, useNativeDriver: false }). The square now bounces a little when it lands.
Check each goal yourself, then mark it.
06 / 09

Quick check

3 questions. Each one explains itself after you answer.

  1. 1 / 3What does Animated.timing(playerY, { toValue: 150, duration: 350, useNativeDriver: false }).start() do?
  2. 2 / 3Why is the value made with useRef(new Animated.Value(0)).current?
  3. 3 / 3You write <View style={{ bottom: playerY }}> with an Animated.Value. What goes wrong?
07 / 09

Explain it back

Only you can see this. Log in to keep it.0 words
08 / 09

How confident are you with Simple animation?

09 / 09

Done

You can now
Read an Animated.timing call and say where the value goes, how long it takes, and what it moves on screen.
Next: Collisions and score Back to Phase 7
On this page
  1. 01Simple animation
  2. 02Watch & read
  3. 03Explanation
  4. 04Code tour
  5. 05Practice
  6. 06Quick check
  7. 07Explain it back
  8. 08Confidence
  9. 09Done
Up next · 02 / 09Watch & read
Mamun Labs LearnFound a mistake on this page? Tell Mamun.
Mamun Labs Learn
JourneyGlossary
esc
  • Try “map”, “flexbox” or “state”.
Log in
Start learning
Log in to save your progress. Everything here works without an account.Log in to save progressLog in
Phase 7Games
5 topics · 3–4 wks
  1. 1The game loop
  2. 2Touch input
  3. 3Simple animation
  4. 4Collisions and score
  5. 5Sound and saving the best score
JourneyPhase 7Simple animation
01 / 09Overview

Simple animation

You'll learn to make the jump smooth with React Native's Animated: a value that moves from one number to another over time.

Why it mattersSmooth movement is what makes a game feel alive. The same tool fades in messages and slides in menus in normal apps.
about 90 min·needs: Touch input
02 / 09

Watch & read

Start with the required ones. The others are there when you need them.

Docs
Animationsreactnative.dev · official docs · 20 min · English
MMamun's noteRead the start of "Animated API" with the FadeInView example, then "Configuring animations" and "Composing animations". Read "Using the native driver" to see why we set it to false. Skip interpolation and gestures for now.
Video
Introduction to Animated API (React Native)YouTube · Reactiive · 9 min · English
MMamun's noteWatch 0:00–8:30: setup, timing animations, useNativeDriver and spring animations. He animates one square, so it is easy to follow. He also mentions Reanimated names in comments; you can ignore those. Stop at "Interpolate".
DocsOptional
Animated (API reference)reactnative.dev · official docs · 10 min · English
MMamun's noteThe full list. Look up timing(), spring() and sequence() when you want to know every option. You do not need to read it from top to bottom.
03 / 09

Explanation

Right now the square teleports: one tap and it is at the top. Real things do not move like that. A cricket ball rises, slows down, and then comes back down.

Think of a lift in a building. You press 5, and the lift does not appear at floor 5. It passes 2, 3 and 4 on the way. Animated is like that: you say where to go and how long to take, and it fills in all the numbers in between.

An Animated.Value is a number that Animated can move. Keep it in useRef, so the same value stays for the whole life of the screen:

const playerY = useRef(new Animated.Value(0)).current;

To jump, go up to JUMP_HEIGHT in 350 ms, then back to 0 in 350 ms. Animated.sequence runs them one after the other:

Animated.sequence([
Animated.timing(playerY, { toValue: JUMP_HEIGHT, duration: 350, useNativeDriver: false }),
Animated.timing(playerY, { toValue: 0, duration: 350, useNativeDriver: false }),
]).start();

Then draw the square with <Animated.View style={{ bottom: playerY }}>. A normal View cannot read an Animated.Value; it must be an Animated.View.

Animated.spring is the other way to move a value. Instead of a time, it acts like a spring, so the square can bounce a little when it lands.

useNativeDriver: false is needed here because we move bottom, which changes layout. The faster native driver only works for transform and opacity.

playerY is now not in game any more. Animated moves the square without rendering the component again, so the jump stays smooth even while the game loop is busy.

People often write new Animated.Value(0) without useRef. Then every render makes a brand new value at 0, and the jump snaps back to the ground in the middle.

Big games in React Native use a library called Reanimated. It runs animations on their own thread and can do much more. The idea is the same: a value, a target, and a time. Start with Animated.

Words to recognise
AnimatedAnimated.ValueAnimated.timingAnimated.springAnimated.sequenceuseRefReanimated
04 / 09

Code tour

Click a line number with a ring to read the note for that line.

App.jsx5 notes
const playerY = useRef(new Animated.Value(0)).current;
Mentor note · line 1
One Animated.Value, kept in useRef so it survives every render. It starts on the ground, at 0.
2const jumping = useRef(false);
3
4function jump() {
if (jumping.current) return;
6 jumping.current = true;
7 Animated.sequence([
Animated.timing(playerY, { toValue: JUMP_HEIGHT, duration: 350, useNativeDriver: false }),
9 Animated.timing(playerY, { toValue: 0, duration: 350, useNativeDriver: false }),
]).start(() => {
11 jumping.current = false;
12 });
13}
14// ...and in the return:
<Animated.View style={[styles.player, { bottom: playerY }]} />
05 / 09

Practice

Short drills. Take your time and read the code before you answer.

DrillPredict1 / 3
1function Jumper() {
2 const playerY = useRef(new Animated.Value(0)).current;
3 console.log("render");
4
5 function jump() {
6 Animated.timing(playerY, { toValue: 150, duration: 350, useNativeDriver: false }).start();
7 }
8
9 return (
10 <Pressable style={{ flex: 1 }} onPressIn={jump}>
11 <Animated.View style={{ position: "absolute", bottom: playerY, width: 40, height: 40 }} />
12 </Pressable>
13 );
14}
15
16// The screen appears, then you tap once and the square rises to 150.
17// Count in the real app (not the extra development check).
How many times is "render" printed?
DrillFind the bug2 / 3
jumpHeightAt(ms) says how high the square is ms milliseconds after the tap, for a straight-line jump (what Easing.linear would give). It goes up to 150 in 350 ms, back down to 0 by 700 ms, and then stays on the ground. This is the math Animated.timing does for you. One part is wrong: after the jump it goes under the ground. Fix it.
1
2
3
4
5
6
7
8
9
10
Try 1 of 3 · solution unlocks after 3
DrillA smooth jump3 / 3
The jump now rises and falls smoothly with Animated. The game loop only moves the obstacle. Tap to jump and compare it with the jump from the last topic.
1import { useEffect, useRef, useState } from "react";
2import { Animated, Pressable, StyleSheet, Text, View } from "react-native";
3
4const SPEED = 10;
5const SCREEN_WIDTH = 400;
6const OBSTACLE_WIDTH = 30;

Runs in the browser. Choose “My Device” in Snack to try it on your phone with Expo Go.

Working whenTap and watch the square rise, slow down at the top, and come back down.Change both duration values to 1000. The jump becomes slow, like on the moon. Put them back to 350.Change the second Animated.timing(...) to Animated.spring(playerY, { toValue: 0, useNativeDriver: false }). The square now bounces a little when it lands.
Check each goal yourself, then mark it.
06 / 09

Quick check

3 questions. Each one explains itself after you answer.

  1. 1 / 3What does Animated.timing(playerY, { toValue: 150, duration: 350, useNativeDriver: false }).start() do?
  2. 2 / 3Why is the value made with useRef(new Animated.Value(0)).current?
  3. 3 / 3You write <View style={{ bottom: playerY }}> with an Animated.Value. What goes wrong?
07 / 09

Explain it back

Only you can see this. Log in to keep it.0 words
08 / 09

How confident are you with Simple animation?

09 / 09

Done

You can now
Read an Animated.timing call and say where the value goes, how long it takes, and what it moves on screen.
Next: Collisions and score Back to Phase 7
On this page
  1. 01Simple animation
  2. 02Watch & read
  3. 03Explanation
  4. 04Code tour
  5. 05Practice
  6. 06Quick check
  7. 07Explain it back
  8. 08Confidence
  9. 09Done
Up next · 02 / 09Watch & read

Right now the square teleports: one tap and it is at the top. Real things do not move like that. A cricket ball rises, slows down, and then comes back down.

Think of a lift in a building. You press 5, and the lift does not appear at floor 5. It passes 2, 3 and 4 on the way. Animated is like that: you say where to go and how long to take, and it fills in all the numbers in between.

An Animated.Value is a number that Animated can move. Keep it in useRef, so the same value stays for the whole life of the screen:

const playerY = useRef(new Animated.Value(0)).current;

To jump, go up to JUMP_HEIGHT in 350 ms, then back to 0 in 350 ms. Animated.sequence runs them one after the other:

Animated.sequence([
Animated.timing(playerY, { toValue: JUMP_HEIGHT, duration: 350, useNativeDriver: false }),
Animated.timing(playerY, { toValue: 0, duration: 350, useNativeDriver: false }),
]).start();

Then draw the square with <Animated.View style={{ bottom: playerY }}>. A normal View cannot read an Animated.Value; it must be an Animated.View.

Animated.spring is the other way to move a value. Instead of a time, it acts like a spring, so the square can bounce a little when it lands.

useNativeDriver: false is needed here because we move bottom, which changes layout. The faster native driver only works for transform and opacity.

playerY is now not in game any more. Animated moves the square without rendering the component again, so the jump stays smooth even while the game loop is busy.

People often write new Animated.Value(0) without useRef. Then every render makes a brand new value at 0, and the jump snaps back to the ground in the middle.

Big games in React Native use a library called Reanimated. It runs animations on their own thread and can do much more. The idea is the same: a value, a target, and a time. Start with Animated.

Words to recognise
AnimatedAnimated.ValueAnimated.timingAnimated.springAnimated.sequenceuseRefReanimated

Right now the square teleports: one tap and it is at the top. Real things do not move like that. A cricket ball rises, slows down, and then comes back down.

Think of a lift in a building. You press 5, and the lift does not appear at floor 5. It passes 2, 3 and 4 on the way. Animated is like that: you say where to go and how long to take, and it fills in all the numbers in between.

An Animated.Value is a number that Animated can move. Keep it in useRef, so the same value stays for the whole life of the screen:

const playerY = useRef(new Animated.Value(0)).current;

To jump, go up to JUMP_HEIGHT in 350 ms, then back to 0 in 350 ms. Animated.sequence runs them one after the other:

Animated.sequence([
Animated.timing(playerY, { toValue: JUMP_HEIGHT, duration: 350, useNativeDriver: false }),
Animated.timing(playerY, { toValue: 0, duration: 350, useNativeDriver: false }),
]).start();

Then draw the square with <Animated.View style={{ bottom: playerY }}>. A normal View cannot read an Animated.Value; it must be an Animated.View.

Animated.spring is the other way to move a value. Instead of a time, it acts like a spring, so the square can bounce a little when it lands.

useNativeDriver: false is needed here because we move bottom, which changes layout. The faster native driver only works for transform and opacity.

playerY is now not in game any more. Animated moves the square without rendering the component again, so the jump stays smooth even while the game loop is busy.

People often write new Animated.Value(0) without useRef. Then every render makes a brand new value at 0, and the jump snaps back to the ground in the middle.

Big games in React Native use a library called Reanimated. It runs animations on their own thread and can do much more. The idea is the same: a value, a target, and a time. Start with Animated.

Words to recognise
AnimatedAnimated.ValueAnimated.timingAnimated.springAnimated.sequenceuseRefReanimated