JavaScript rain effect

JavaScript project for beginners: JavaScript Rain effect (ES6)

Updated on 12/09/18
9 minute read

Disclosure

Webquestions.co is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for us to earn fees by linking to Amazon.com and affiliated sites. As an Amazon Associate I earn from qualifying purchases.

In this JavaScript project for beginners, I will show you how to create JavaScript rain effect using ES6, a little CSS, and HTML.

The project is focusing on the object-oriented approach. 

Since this project can be made in many ways, using only CSS for example, it is important to experiment with different approaches and solutions to create and develop JavaScript project. This will make you a better JavaScript developer. 

In this tutorial, I will go over the code, and for each technology, I will show the final file so you can choose to follow along or just copy the code from the files and try to reverse engineer the functionality. 

Subscribe:

 

Another project: Modular multiplication around a circle - JavaScript

Here we go:

The files and structure

It is important to arrange your files in folders and separate concerns. 

Each developer will suggest his or her way of doing so. 

Usually, if you are using an opinionated framework, it will set up the file structure for you. 

Here is the arrangement for this project, feel free to change it as you like:

 

The main folder is Rain_demo. Inside of it, at the first level, there is the JS folder (contains the JavaScript files), the main and only CSS file and the main and only HTML page. 

Inside the JS folder, there are 2 files. 

You can go ahead and create a different structure. You can put all the code in one file. Whatever you find practical for you. 

 

The HTML

The HTML part of the project is pretty simple. 

Here is the complete HTML file.

<!-- COMPLETE HTML FILE -->

<!doctype html>

<html lang="en">
<head>
	<meta charset="utf-8">

	<link rel="stylesheet" href="rain.css">

</head>

<body>
	<div class="btn-section">
		<span>
			<b>Drops:
				<span id="dropsNum">

				</span>
			</b>
		</span>
		<a class="drop-btn" onclick="changeNumDrops(100);">100</a>
		<a class="drop-btn" onclick="changeNumDrops(500);">500</a>
		<a class="drop-btn" onclick="changeNumDrops(800);">800</a>
		<a class="drop-btn" onclick="changeNumDrops(1300);">1300</a>
	</div>

	<div id="drops-section"></div>

	<script src="js/drop.js"></script>
	<script src="js/rain.js"></script>
</body>
</html>

 

Let's go over its main parts.

<div class="btn-section">
		<span>
			<b>Drops:
				<span id="dropsNum">

				</span>
			</b>
		</span>
		<a class="drop-btn" onclick="changeNumDrops(100);">100</a>
		<a class="drop-btn" onclick="changeNumDrops(500);">500</a>
		<a class="drop-btn" onclick="changeNumDrops(800);">800</a>
		<a class="drop-btn" onclick="changeNumDrops(1300);">1300</a>
</div>

This part holds the drops number buttons section. Each button has a click event attached to it.

There is also a <span> element that shows the number of drops chosen.

 

<div id="drops-section"></div>

This is the section of the page that the drops will be created in. 

Since all the drops are generated and added to the page by JavaScript, I separated a special <div> for them.

 

Besides those parts, there are the <script> tags for adding the JavaScript and a <link> tag for the CSS.

 

The CSS

CSS in this project is really minimal. It is all about the JavaScript.

Here is the complete CSS file:

//COMPLETE CSS FILE 

body {
	margin: 0;
	padding: 0;
	overflow: hidden;
}

.rainDrop {
	position: absolute;
	background: linear-gradient(180deg, white , black 90%, black);
	border-radius: 100% 100% 80% 80%;
}

.btn-section {
	position: absolute;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: space-around;
	right: 50px;
	top: 50px;
	padding: 20px 10px;
	background: #c9d0db;
	z-index: 101;
	border-radius: 10px;
	box-shadow: 10px 10px 10px -6px rgba(0,0,0,0.75);
}

.drop-btn {
	display:block;
	width:50px;
	height:50px;
	line-height:50px;
	border: 2px solid #f5f5f5;
	border-radius: 50%;
	color:#f5f5f5;
	text-align:center;
	text-decoration:none;
	background: #464646;
	box-shadow: 0 0 3px gray;
	font-size:20px;
	font-weight:bold;
	cursor: pointer;
	margin: 5px;
	
}
.drop-btn:hover {
	background: #262626;
}

 

Let's go over the selectors.

 

.btn-section {
	position: absolute;
    
    /* .....  */

	box-shadow: 10px 10px 10px -6px rgba(0,0,0,0.75);
}

.drop-btn {
	display:block;

    /* .....  */

	margin: 5px;
	
}
.drop-btn:hover {
	background: #262626;
}

 

These 3 selectors are defining the style of the buttons on the page. 

You can go ahead and change them as you like. It doesn't really matter for the purpose of this exercise.

 

body {
	margin: 0;
	padding: 0;
	overflow: hidden;
}

The body selector - just making sure the rain will fill all the page by making sure there is no padding or margin. 

Also, I disabled the scroll bar on the page with overflow: hidden;

 

.rainDrop {
	position: absolute;
	background: linear-gradient(180deg, white , black 90%, black);
	border-radius: 100% 100% 80% 80%;
}

 

That is the style for the drop.

the position is set to absolute. I am going to place the drops randomly on the page so I can let the browser decide where to put them. 

For the background, I used a linear gradient so the drop will have a more realistic shape. 

To complete the shape, I gave the drop a border-radius value.

The drop has other properties but we will set them from the JavaScript code. 

Feel free to change those values and see the effect on the result.

 

The JavaScript files

Let's start with defining the drop.

The raindrop

Here is the complete file for the raindrop.

// COMPLETE DROP FILE

class Drop {
	constructor(xPosition, yPosition, dropSpeed, dropWidth, dropHeight) {
		this.xPosition = xPosition;
		this.yPosition = yPosition;
		this.dropSpeed = dropSpeed;
		this.dropWidth = dropWidth;
		this.dropHeight = dropHeight;
		this.element;
	}

	show() {
		this.element = document.createElement("div");
		this.element.className += "rainDrop";
		this.element.style.top = this.yPosition + "px";
		this.element.style.left = this.xPosition + "px";
		this.element.style.width = this.dropWidth + "px";
		this.element.style.height = this.dropHeight + "px";

		let el = document.getElementById("drops-section");
		el.appendChild(this.element);
	}

	fall() {
		const makeItRain = () => {
			this.yPosition = this.yPosition + this.dropSpeed;
			this.element.style.top = this.yPosition +"px";

			if(this.yPosition < window.innerHeight) {
				requestAnimationFrame(makeItRain);
			} else {
				this.yPosition = -10;
				requestAnimationFrame(makeItRain);
			}

		}

		requestAnimationFrame(makeItRain);
	}
}

 

Here we have a class named "Drop". 

The class will be our blueprint for each drop. Notice the class definition holds a constructor and methods. 

Everything is within the class' curly braces.

Let's break the file into parts.

 

class Drop {
	constructor(xPosition, yPosition, dropSpeed, dropWidth, dropHeight) {
		this.xPosition = xPosition;
		this.yPosition = yPosition;
		this.dropSpeed = dropSpeed;
		this.dropWidth = dropWidth;
		this.dropHeight = dropHeight;
		this.element;
	}

 
    // ... methods ... 

}

 

Look at the class constructor. You can see it contains the properties for the drop.

Every instance of the class, i.e. a drop, will be created with those properties.

A drop has:

  1. xPosition - the location of the drop on the x-axis.
  2. yPosition - the location of the drop on the y-axis.
  3. dropSpeed - the speed at which the drop rains down.
  4. dropWidth - the width of the drop.
  5. dropHeight - the height of the drop.
  6. element - the element that will be placed on the page.

Notice that drops can be made different in size and shape. We will use that later to create many different drops so the rain will look a little more realistic.

 

class Drop {

    // .. constructor ..

	show() {
		this.element = document.createElement("div");
		this.element.className += "rainDrop";
		this.element.style.top = this.yPosition + "px";
		this.element.style.left = this.xPosition + "px";
		this.element.style.width = this.dropWidth + "px";
		this.element.style.height = this.dropHeight + "px";

		let el = document.getElementById("drops-section");
		el.appendChild(this.element);
	}

    //..fall() method ..
}

 

The show() method is responsible for:

  1. create an element that will be the drop.
  2. assign a class to that element.
  3. set the position of the element on the page.
  4. set the size (width and height) of the element.
  5. append the element to the "drops section" on the page.

 

class Drop {

    // .. constructor ..

    //..show() method ..

	fall() {
		const makeItRain = () => {
			this.yPosition = this.yPosition + this.dropSpeed;
			this.element.style.top = this.yPosition +"px";

			if(this.yPosition < window.innerHeight) {
				requestAnimationFrame(makeItRain);
			} else {
				this.yPosition = -10;
				requestAnimationFrame(makeItRain);
			}

		}

		requestAnimationFrame(makeItRain);
	}
}

 

The fall() method is responsible for changing the position of the drop. From the top of the page to the bottom.

 

const makeItRain = () => {

    // code

}

 

Since I am using requestAnimationFrame() to change the position of the drops, I needed to create a function to call each time I want the fall animation to run. 

Here you can find out more about requestAnimationFrame().

The method I created is an arrow function.

Why did I use arrow function?

makeItRain() is inside a class method, when invoked, this reference would be set to undefined or window (depends on other factors).

I want the function to point to the calling object so that is what arrow functions are here to solve. 

 

//.... inside makeItRain() function
this.yPosition = this.yPosition + this.dropSpeed;
this.element.style.top = this.yPosition +"px";

 

In the 2 line above, the new Y position is created by adding the drop speed to the current Y position. 

The greater the speed, the greater the fall increments, thus, the drop will look like it falls faster.

After creating the new Y position, I assign it to the element. That will cause it to change on the browser page.

 

if(this.yPosition < window.innerHeight) {
	requestAnimationFrame(makeItRain);
} else {
	this.yPosition = -10;
	requestAnimationFrame(makeItRain);
}

 

This code section does the following: IF the Y position od the drop is smaller than the window height, change to Y position again (invoke makeItrain()). ELSE, reset the position of the drop, and invoke makeItRain() again.

Finally, we call makeItRain at the bottom the first time. 

 

The main JavaScript file (rain.js)

Here is the complete file:

 

// THE COMPLETE RAIN.JS FILE

const pageWidth = window.innerWidth;
const pageHeight = window.innerHeight;
const defaultDropNum = 100;


function makeItRain (num) {

	let elements = document.getElementById("drops-section");

	while (elements.hasChildNodes()) {
		elements.removeChild(elements.lastChild);
	}


	for (let i = 0 ; i < num ; i ++) {
		let randomX = Math.floor(Math.random() * (pageWidth));
		let randomY = Math.floor(Math.random() * (pageHeight));
		let dropSpeed = Math.floor(Math.random() * (25 - 5)) + 5;
		let dropWidth = Math.floor(Math.random() * (dropSpeed/10 - 1)) + 1;
		let dropHeight = Math.floor(Math.random() * (dropSpeed*2 - 3)) + 3;
		let d = new Drop(randomX, randomY, dropSpeed, dropWidth, dropHeight);

		d.show();
		d.fall();

	}

}

function updateNumberInView (num) {
	let el = document.getElementById("dropsNum").firstChild;
	el.nodeValue = num;
}

function changeNumDrops (num) {
	updateNumberInView(num);
	makeItRain(num);
}

updateNumberInView(defaultDropNum);
makeItRain(defaultDropNum);

 

This is the last file for the project.

This file instantiates the drops and handles the button clicks.

 

const pageWidth = window.innerWidth;
const pageHeight = window.innerHeight;
const defaultDropNum = 100;

 

We start by declaring variables using the const keyword.

 

function makeItRain (num) {

	let elements = document.getElementById("drops-section");

	while (elements.hasChildNodes()) {
		elements.removeChild(elements.lastChild);
	}


	for (let i = 0 ; i < num ; i ++) {
		let randomX = Math.floor(Math.random() * (pageWidth));
		let randomY = Math.floor(Math.random() * (pageHeight));
		let dropSpeed = Math.floor(Math.random() * (25 - 5)) + 5;
		let dropWidth = Math.floor(Math.random() * (dropSpeed/10 - 1)) + 1;
		let dropHeight = Math.floor(Math.random() * (dropSpeed*2 - 3)) + 3;
		let d = new Drop(randomX, randomY, dropSpeed, dropWidth, dropHeight);

		d.show();
		d.fall();

	}

}

 

Now we declare a function that:

  1. removes any old drops if exists (useful for when we click on a button to change the number of drops).
  2. FOR LOOP - create random values for position, speed, height etc.
  3. instantiates a new Drop() with the random values we created.
  4. call the show() and fall() methods on every drop.

The function receives an argument num, that sets the number of loop iteration, i.e. setting the drops number on the page.

 

function updateNumberInView (num) {
	let el = document.getElementById("dropsNum").firstChild;
	el.nodeValue = num;
}

 

This function updates the drops number presented in the view. 

 

function changeNumDrops (num) {
	updateNumberInView(num);
	makeItRain(num);
}

 

This function is attached to the click handler when a button is clicked on the page. 

It receives a num argument that is sent from the click action.

 

That is all. 

Not really complicated and. 

 

Final thoughts

JavaScript rain effect is easy to create using ES6 classes.

Though may not be the best performing version, still fun and educational JavaScript project for beginners.

Try changing the code, the values, maybe add other button and angles to the rain. Experiment so you can develop your skills. 

Another idea is to add a lightning for example. Also with Lightning class.

Hope you enjoyed this short JavaScript tutorial.