Jun 23, 2020

Bursting Colourful Crackers With react-native-fireworks

An npm package to burst crackers in your React native app.
Abhinandan Kushwaha
Abhinandan KushwahaSoftware Engineer
lines
 “Don’t let anyone ever dull your sparkle .” ― Unknown ✨✨

Hey there!

Ever felt a need to burst colourful fireworks ? in your mobile app, like when there’s something to celebrate? Here’s an npm package I created for that.

To use it in your React Native project, you can install it from https://www.npmjs.com/package/react-native-fireworks.

Using it as a component is as simple as -

Hire our Development experts.
import Fireworks from 'react-native-fireworks';
...
<Fireworks/>

This component accepts a total of 8 props, all of which are optional. Below is a demonstration that uses all the 8 props-

Hire our Development experts.
<Fireworks
  speed={3}
  density={8}
  colors={['#ff0','#ff3','#cc0','#ff4500','#ff6347']}
  iterations={5}
  height={150}
  width={100}
  zIndex={2}
  circular={true}
/>

 

Behind the scenes

This is my first package on npm. I created it using Animated class that comes with React Native. For creating firework-like objects, I designed colourful circles of small radii moving in all the directions randomly and fading away.

So basically, a single firecracker consists of some 30 small circles of different colours. These circles are initially positioned at the centre of a square of width 200.

Step 1. Drawing a single firecracker ?

We begin with a square box that will contain the colourful light balls:

Hire our Development experts.
<View style={styles.explosionBoundary}>
    {/*<code to draw the light balls>*/}
</View>

The box with style explosionBoundary has a fixed height and width of 200 each. The coloured light balls will move within this boundary.

Hire our Development experts.
const styles = StyleSheet.create({
explosionBoundary: {
position: 'absolute',
height: 200,
width: 200,
},
});

Next, we draw some balls, say 30 balls, and fill them with random colours. These balls are positioned at the centre of the square box that we made above.

Hire our Development experts.
class Fireworks extends Component {
  
  getRandom = (n) => {
    return Math.round(Math.random() * n);
  };
  
  render(){
    let balls = [],
        randomColors = [];
      for (let i = 0; i < 30; i++) {
        balls.push('');
        randomColors[i] =
          'rgb(' +
          this.getRandom(255) +
          ',' +
          this.getRandom(255) +
          ',' +
          this.getRandom(255) +
          ')';
      }

    return(
      <View style={styles.explosionBoundary}>
          {balls.map((ball, index) => {
            return (
              <View
                style={[
                  styles.ball,
                  {
                    top: 100,
                    left: 100,
                    backgroundColor: randomColors[index],
                  },
                ]}
              />
            );
          })}
        </View>);
  }
}


const styles = StyleSheet.create({
  explosionBoundary: {
    position: 'absolute',
    height: 200,
    width: 200,
  },
  ball: {
    position: 'absolute',
    height: 7,
    width: 7,
    borderRadius: 3,
  },
});

At this point, we can only see a small circle because all the balls are positioned at the same point and they all overlap. So, we need to move them in different directions to be able to see them.

Step 2. Bursting the cracker ?

To move the balls we will use Animated class from React Native. We start by creating two variables that will control the opacity and position of the light balls.

Hire our Development experts.
this.fadingOpacity = new Animated.Value(1);
this.movingBall = new Animated.Value(0);

Now we use Animated.timing with these variables and create functions that will actually animate the objects.

Hire our Development experts.
animateOpacity() {
  this.fadingOpacity.setValue(1);
  Animated.timing(this.fadingOpacity, {
    toValue: 0,
    duration: 700,
    easing: Easing.ease,
    useNativeDriver: false,
  }).start();
}
animateBall() {
  this.movingBall.setValue(0);
  Animated.timing(this.movingBall, {
    toValue: 1,
    duration: 700,
    easing: Easing.ease,
    useNativeDriver: false,
  }).start();
}

We use the variables this.fadingOpacity and this.movingBall to move the balls in all the directions and fade them simultaneously. At this point, our code looks like:

Hire our Development experts.
import React from 'react';
import { StyleSheet, View, Animated, Easing } from 'react-native';

export default class Fireworks extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            x: [],
            y: [],
        };
        this.fadingOpacity = new Animated.Value(1);
        this.movingBall = new Animated.Value(0);
    }

    componentDidMount() {
        this.animateOpacity();
        this.animateBall();
    }


    getRandom = (n) => {
        return Math.round(Math.random() * n);
    };

    animateOpacity() {
        this.fadingOpacity.setValue(1);
        Animated.timing(this.fadingOpacity, {
            toValue: 0,
            duration: 700,
            easing: Easing.ease,
            useNativeDriver: false,
        }).start(() => this.animateOpacity());
    }

    animateBall() {
        this.movingBall.setValue(0);
        Animated.timing(this.movingBall, {
            toValue: 1,
            duration: 700,
            easing: Easing.ease,
            useNativeDriver: false,
        }).start(() => this.animateBall());
    }


    render() {
        let balls = [],
            randomTops = [],
            randomLefts = [],
            randomColors = [];
        for (let i = 0; i < 30; i++) {
            balls.push('');
            randomTops[i] = this.movingBall.interpolate({
                inputRange: [0, 1],
                outputRange: [100, this.getRandom(200)],
            });
            randomLefts[i] = this.movingBall.interpolate({
                inputRange: [0, 1],
                outputRange: [100, this.getRandom(200)],
            });
            randomColors[i] =
                'rgb(' +
                this.getRandom(255) +
                ',' +
                this.getRandom(255) +
                ',' +
                this.getRandom(255) +
                ')';
        }
        let ballOpacity = this.fadingOpacity.interpolate({
            inputRange: [0, 1],
            outputRange: [0, 1],
        });

        return (
            <View style={styles.explosionBoundary}>
                {balls.map((ball, index) => {
                    return (
                        <Animated.View
                            style={[
                                styles.ball,
                                {
                                    top: randomTops[index],
                                    left: randomLefts[index],
                                    opacity: ballOpacity,
                                    backgroundColor: randomColors[index],
                                },
                            ]}
                        />
                    );
                })}
            </View>);
    }
}

const styles = StyleSheet.create({
    explosionBoundary: {
        position: 'absolute',
        height: 200,
        width: 200,
    },
    ball: {
        position: 'absolute',
        height: 7,
        width: 7,
        borderRadius: 3,
    },
});

The above code is enough to burst a single firecracker, and here it does!

Notice that the initial positions- top and left of all the balls are the same, i.e. 100, but the final positions are randomly set. So the distances travelled by each ball will be different. Also, the time taken by each ball to move from the initial position to the final position is the same. Different distances and same time result in different velocities. So the light balls move in all different directions with different velocities.

But a single firecracker is boring.

For celebrations, we would love to burst many crackers in a series. Also, at least 4 or 5 should burst simultaneously to cover up the empty space.

Step 3. Bursting many at a time ????

We use a variable called density that denotes the number of crackers that will burst simultaneously. The x and y coordinates (top and left positions) of each cracker are set randomly. We have two arrays x and y declared as state variables. Their values change at the end of every explosion, so every new explosion takes place at a new location on the screen.

Hire our Development experts.
import React from 'react';
import { Dimensions, StyleSheet, View, Animated, Easing } from 'react-native';

export default class Fireworks extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            x: [],
            y: [],
        };
        this.fadingOpacity = new Animated.Value(1);
        this.movingBall = new Animated.Value(0);
    }

    componentWillMount() {
        this.setExplosionSpots()
    }

    setExplosionSpots() {
        let density = 5,
            x = [],
            y = [];

        for (i = 0; i < density; i++) {
            x[i] = this.getRandom(Dimensions.get('window').width);
            y[i] = this.getRandom(Dimensions.get('window').height);
        }
        this.setState({ x, y }, () => {
            this.animateOpacity();
            this.animateBall();
        });
    }

    getRandom = (n) => {
        return Math.round(Math.random() * n);
    };

    animateOpacity() {
        this.fadingOpacity.setValue(1);
        Animated.timing(this.fadingOpacity, {
            toValue: 0,
            duration: 700,
            easing: Easing.ease,
            useNativeDriver: false,
        }).start(() => this.setExplosionSpots());
    }

    animateBall() {
        this.movingBall.setValue(0);
        Animated.timing(this.movingBall, {
            toValue: 1,
            duration: 700,
            easing: Easing.ease,
            useNativeDriver: false,
        }).start();
    }


    explosionBox() {
        let balls = [],
            randomTops = [],
            randomLefts = [],
            randomColors = [];
        for (let i = 0; i < 30; i++) {
            balls.push('');
            randomTops[i] = this.movingBall.interpolate({
                inputRange: [0, 1],
                outputRange: [100, this.getRandom(200)],
            });
            randomLefts[i] = this.movingBall.interpolate({
                inputRange: [0, 1],
                outputRange: [100, this.getRandom(200)],
            });
            randomColors[i] =
                'rgb(' +
                this.getRandom(255) +
                ',' +
                this.getRandom(255) +
                ',' +
                this.getRandom(255) +
                ')';
        }
        let ballOpacity = this.fadingOpacity.interpolate({
            inputRange: [0, 1],
            outputRange: [0, 1],
        });

        return (
            <View style={styles.explosionBoundary}>
                {balls.map((ball, index) => {
                    return (
                        <Animated.View
                            style={[
                                styles.ball,
                                {
                                    top: randomTops[index],
                                    left: randomLefts[index],
                                    opacity: ballOpacity,
                                    backgroundColor: randomColors[index],
                                },
                            ]}
                        />
                    );
                })}
            </View>);
    }

    render() {
        const { x, y } = this.state;
        return (
            <View>
                {x.map((item, index) => {
                    return (
                        <View
                            style={{
                                top: y[index],
                                left: x[index],
                            }}>
                            {this.explosionBox()}
                        </View>
                    )
                })}
            </View>)
    }
}

const styles = StyleSheet.create({
    explosionBoundary: {
        position: 'absolute',
        height: 200,
        width: 200,
    },
    ball: {
        position: 'absolute',
        height: 7,
        width: 7,
        borderRadius: 3,
    },
});

Voilà! An amazing fireworks animation is ready:

react-native-firework-animation-display

 


Bonus- Circular firecrackers

Crackers are more elegant when they burst into a circular path. The above code can be modified a bit to produce circular crackers.

We used random values between 0 and 200 for the final x and y positions of the light balls, putting the initial position at the centre (100,100) of the explosion box. This made the balls to move in random directions.

To make their path circular we need to calculate the x and y positions of the balls with the help of a simple equation x² +y² = r². This is the equation for a circle in the simplest form when the centre of the circle lies at the origin. In our case, we have a square box (200 x 200) where we will make the circular path of explosion.

We use the above illustration to draw the circular path. The radius of the circle is 100 and the centre lies at (100,100).

So our equation will now become:

(x-100)² + (y-100)² = 100².

Using this equation we plot some 20 points in a circular path. At these points, we will draw our light balls. Also, we make the balls bigger as they explode.

This results in:

Circular firecrackers in react native


 

The complete code with several customization props is available on Github at this link: https://github.com/Abhinandan-Kushwaha/Fireworks.

Thanks a lot for stopping by and being patient. ❤

Hire our Development experts.