If you ever wondered how to animate snow, follow this tutorial to know how.
Table of contents
What We Will Be Building
We’re going to build an app that has an image of a Christmas Tree and snowfall. That’s how it’s going to look like.

For your reference, the final code for the app we’re building can be found in this GitHub repo.
Outlining the App Structure
It’s always a good idea to plan you app code structure in advance. Let’s list the files that we’re going to need to build all of the components for the app.
index.ios.js
orindex.android.js
. Entry points into the app for iOS or Android platforms respectively. Both are going to render just one component calledTree
.Tree.js
. A component that renders a background image and flakes using multiple instances ofFlake
component.Flake.js
. A component that renders a flake of given size and position.
Let’s Get Started
Let’s start off by creating a new app. Open Terminal App and run these commands to initialize a new project and run it in the simulator.
react-native init ChristmasTree;
cd ChristmasTree;
react-native run-ios;
Enable Hot Reloading
Once your app is up and running, press ⌘D and select Enable Hot Reloading. This will save you some time having to reload the app manually every time you make a change.
Flake Component
Let’s start off by creating Flake
component, which is a pure render component. It doesn’t have state and just renders a flake taking in radius
, density
and x
, y
coordinates as props.
Create a new file with the following code and call it Flake.js
.
import React, { Component } from 'react';
import {
Animated,
Dimensions,
Easing
} from 'react-native';
// Detect screen size
const { width, height } = Dimensions.get('window');
export default class Flake extends Component {
// Angle of falling flakes
angle = 0
componentWillMount() {
// Pull x and y out of props
const { x, y } = this.props;
// Initialize Animated.ValueXY with passed x and y coordinates for animation
this.setState({
position: new Animated.ValueXY({ x, y })
});
}
getStyle = ({ radius, x, y } = this.props) => ({
position: 'absolute',
backgroundColor: 'rgba(255,255,255,0.8)',
borderRadius: radius,
width: radius * 2,
height: radius * 2,
})
render() {
return <Animated.View style={[this.getStyle(), this.state.position.getLayout()]} />;
}
}
Index Files
Next, let’s update our index files. Since we’re going to re-use the same code for both, iOS and Android, so we don’t need two different index files. We’ll be using the same Tree
component in both index files.
Open index.ios.js
file and scrap all of the React Native boilerplate code to start from scratch. Do the same for index.android.js
. And add the following code to both of index files.
import { AppRegistry } from 'react-native';
import Tree from './Tree';
AppRegistry.registerComponent('ChristmasTree', () => Tree);
This code imports Tree
component from Tree.js
file and registers it as main app container. If you took at look at the simulator at this point, you would see an error screen. That’s because Tree.js
doesn’t exist yet, and therefore can’t be imported. So, let’s fix it.
Tree Component
Next, let’s build Tree
component. It takes in flakesCount
prop with the default value of 50
flakes on the screen and renders that amount of flakes distributed randomly on the screen. To render each flake, it uses Flake
component we built in the previous step. It also renders a background image we’re about to download.
Download Background Image
Create a new folder called assets
inside your project and download this image into that folder.
Create Tree.js
Create a new file with the following code and call it Tree.js
.
import React, { Component } from 'react';
import {
Dimensions,
Image,
StyleSheet,
View
} from 'react-native';
import Flake from './Flake';
// Detect screen size
const { width, height } = Dimensions.get('window');
export default class Tree extends Component {
static defaultProps = {
flakesCount: 50, // total number of flakes on the screen
}
render({ flakesCount } = this.props) {
return <View style={styles.container}>
{/* Christmas Tree background image */}
<Image
style={styles.image}
source={require('./assets/tree.jpg')}
>
{/* Render flakesCount number of flakes */}
{[...Array(flakesCount)].map((_, index) => <Flake
x={Math.random() * width} // x-coordinate
y={Math.random() * height} // y-coordinate
radius={Math.random() * 4 + 1} // radius
density={Math.random() * flakesCount} // density
key={index}
/>)}
</Image>
</View>;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
flex: 1,
resizeMode: 'cover',
width: width,
position: 'relative',
},
});
Check Out the Progress
Bring up the simulator window and see what we’ve got so far. We have our Christmas Tree and a bunch of snowflakes. If you press ⌘R you can see that they’re distributed randomly every time. That’s great, but snowflakes are static and do not move. Let’s make them fall in the next step.

Make Them Fall
To make the snowflakes fall, we’re going to need to add animate()
method that will update flake coordinates and use React Native core module Animated
to animate their falling.
So, open up Flake.js
file and add the following code inside Flake
class definition between export default class Flake extends Component {
and }
.
export default class Flake extends Component {
// ...exisiting code
componentDidMount() {
this.animate();
}
animate = () => {
// Animation duration
let duration = 500;
// Pull x and y out of Animated.ValueXY object in this.state.position
const { x: { _value: x }, y: { _value: y } } = this.state.position;
// Pull radius and density out of props
const { radius, density } = this.props;
this.angle += 0.02;
let newX = x + Math.sin(this.angle) * 10;
let newY = y + Math.cos(this.angle + density) + 10 + radius / 2;
// Send flakes back from the top once they exit the screen
if (x > width + radius * 2 || x < -(radius * 2) || y > height)
{
duration = 0; // no animation
newX = Math.random() * width; // random x
newY = -10; // above the screen top
// Send 2/3 of flakes back to the top
if (Math.floor(Math.random() * 3) + 1 > 1) {
newX = Math.random() * width;
newY = -10;
// Send the rest to either left or right
} else {
// If the flake is exiting from the right
if (Math.sin(this.angle) > 0) {
// Enter from the left
newX = -5;
newY = Math.random() * height;
} else {
// Enter from the right
newX = width + 5;
newY = Math.random() * height;
}
}
}
// Animate the movement
Animated.timing(this.state.position, {
duration,
easing: Easing.linear,
toValue: {
x: newX,
y: newY,
}
}).start(() => {
// Animate again after current animation finished
this.animate();
});
}
// ...exisiting code
}
And now they’re falling.
Wrapping Up
I hope you enjoyed the tutorial. If you have any questions or ideas for next tutorials, just leave a comment.