Snowfall effect in React Native

Cuong Le
4 min readDec 31, 2016

--

Developers have their own style when it comes to most things, so naturally they’d have a special way of celebrating this holiday season. My question is: What can we do that encapsulates the spirit and true meaning of Christmas? Let’s get started

Make the Snow

A snowflake is actually is a circle. One option is to use an <Image/> or you can style CSS for a <View/>. If you haven’t seen The Shapes of CSS. I recommend checking it out. For basics shapes CSS will work great, and in our case a circle is a simple shape. Using CSS gives us control over the color more easily, the sizing, and anything else you can do with a simple view.

//Style
snow: {
backgroundColor: '#FFFFFF',
width: 100,
height: 100,
borderRadius: 50;
}

Make the container

First we create a component which will contain all the snowflakes. We need to measure the size of the container using React Native’s built-in onLayout prop to relocate the snow when it goes out of the container. We render the snow when the container is measured. For optimization, I use PureComponent because it’s unnecessary to re-render the container when its children are drawn over and over again.

export default class AnimatedSnow extends PureComponent {

constructor(props) {
super(props);
this.state = {
width: 0,
height: 0,
}
}


render() {

const snow = [];
if (this.state.width > 0 && this.state.height > 0) {
for (let i = 0; i < 40; i++) {
snow.push(
<Snow
key={i}
width={this.state.width}
height={this.state.height}
/>)
}
}

return (
<View {...this.props}
onLayout={(e) => {
const {width, height} = e.nativeEvent.layout;
this.setState ({
width: width,
height: height
});
}}>
{snow}
</View>
)
}

}

Let it fall

The SnowFlake code is very loosely based upon the snowfall algorithm by Samuel Arbesman:

export default class Snow extends Component {

constructor(props) {
super(props);

this.x = Random.getRandomInt(this.props.width);
this.y = Random.getRandomInt(this.props.height);

this.angle = Random.getRandomFloat(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;
this.increment = Random.getRandom(INCREMENT_LOWER, INCREMENT_UPPER);
this.flakeSize = Random.getRandom(FLAKE_SIZE_LOWER, FLAKE_SIZE_UPPER);
this.opacity = Math.random();

}

componentDidMount(){
this.updateInterval = setInterval(() => {
this.move(this.props.width, this.props.height);
this.forceUpdate();
},100);
}

componentWillUnmount(){
clearInterval(this.updateInterval);
}

move(width, height) {

const x = this.x + (this.increment * Math.cos(this.angle));
const y = this.y + (this.increment * Math.sin(this.angle));

this.angle += Random.getRandom(-ANGLE_SEED, ANGLE_SEED) / ANGLE_DIVISOR;

this.x = x;
this.y = y;

if (!this.isInside(width, height)) {
this.reset(width);
}

}

isInside(width, height) {
const x = this.x;
const y = this.y;
const flakeSize = this.flakeSize;
return x >= -flakeSize - 1 && x + flakeSize <= width && y >= -flakeSize - 1 && y - flakeSize < height;
}

reset(width) {
const x = Random.getRandomInt(width);
const y = (-this.flakeSize - 1);
const angle = Random.getRandomFloat(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;

this.x = x;
this.y = y;
this.angle = angle;
}

getPosition() {
return {
top: this.y,
left: this.x,
width: this.flakeSize,
height: this.flakeSize,
borderRadius: this.flakeSize / 2,
opacity: this.opacity
}
}


render() {
const snowShape = this.getPosition();

return (
<View {...this.props} style={[styles.snow, snowShape]}/>
)
}
};

When each Snow is initialized it is placed in a random position on the Container. This is to ensure that when the snow is first drawn it appears that the snowfall is already in progress rather than starting if we were to start all of the flakes from the top. When a flake goes out of the Container bounds, it is re-positioned to a random horizontal location at the top — so we re-cycle our flakes to avoid unnecessary object creation.

When each frame is drawn forcibly after positioning the snow by forceUpdate() every 100ms, we add some random elements to simulate small gusts of wind which could cause individual flakes to change direction slightly. We then perform our in bounds check (and move it back to the top, if necessary) before actually drawing the Snow on the Container.

All of the constant values were tweaked until I found a snowfall simulation that I was content with.

Voilà

On Android

On iOS

Wrapping up

After all this, we got a “Snowfall View” thats can run on both Android and iOS. However, it’s acceptable that React Native can draw a small amount of snowflake. Of course, it’s not the most performant way of rendering stuff like this compared to native platforms. I’m open up to hear your solutions to make it work better. At the time of this writing, the year of 2016 is almost over. Hopefully, lots of great thing will come in 2017. Happy New Year!

The source code for this post is available here.

Part of this code is based upon “Snowfall” by Sam Arbesman, licensed under Creative Commons Attribution-Share Alike 3.0 and GNU GPL license.
Work: http://openprocessing.org/visuals/?visualID= 84771

--

--

Cuong Le
Cuong Le

Responses (2)