How to Create a Matrix Rain Effect with JavaScript
We all remember the iconic raining code from The Matrix. If you have ever wanted to create a similar effect in your own project, this tutorial will show you step by step how to create it.
This tutorial is an oversimplified version of my rain-fall library, which is fully customizable with fonts, colors, size, and more, and is fully responsive. Here is a link to the GitHub repository where you can find the complete source code, examples, and a demo.
The
rain-char
library demo is available in m-sarabi.ir/rain-char
Getting Started
First, let's set up our environment. We will create an HTML file where we'll include a canvas element for rendering the rain effect:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RainChar Effect</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
background-color: black;
}
</style>
</head>
<body>
<canvas id="rain-effect"></canvas>
<script src="script.js"></script>
</body>
</html>
In this HTML setup, we have a full-screen canvas where our rain effect will be rendered.
The RainChar Class
Let's now walk through the core functionality of our RainChar class.
1. Constructor
In the constructor, we first define the parameters of our rain effect: font, size, characters, background color, and font color.
Next, we set up the canvas, determining its size and filling in the background color.
Finally, we create an array to store each character's position and size.
class RainChar {
constructor(font, charSize, chars, bg, fg) {
// Defining the parameters
this.font = font;
this.charSize = charSize;
this.chars = chars;
this.bg = bg;
this.fg = fg;
// Setting up the canvas
const canvas = document.getElementById('rain-effect');
this.context = canvas.getContext('2d');
this.size = [canvas.offsetWidth, canvas.offsetHeight];
canvas.width = this.size[0];
canvas.height = this.size[1];
this.context.fillStyle = this.bg;
this.context.fillRect(0, 0, ...this.size);
// Creating the particles array
this.particles = [];
const particleCount = this.size[0] * this.size[1] / (this.charSize ** 2) / 10;
for (let i = 0; i < particleCount; i++) {
// We will create an array of particles using the newParticle method that we define later in the code.
this.particles.push(this.newParticle());
}
}
}
The number of characters is roughly one-tenth of the area of the canvas divided by the size of the character.
2. Creating New Particles
We represent each particle as an object with a random position and size.
newParticle() {
return {
x: Math.random() * this.size[0],
y: -Math.random() * this.size[1] * 2,
size: Math.floor(Math.random() * (this.charSize * 2 - this.charSize / 2) + this.charSize / 2),
};
}
Particle size is a random value between half and double the character size.
Each particle will be positioned somewhere outside the canvas at the top, later we animate them downward.
3. Drawing Particles
Now that we have an array of particles (we created the array of particles in the constructor
using the newParticle
method), We have to render each one on the canvas. We define the drawParticles
method to handle the rendering:
drawParticles() {
this.context.fillStyle = this.fg;
this.particles.forEach(particle => {
this.context.font = `${particle.size}px ${this.font}`;
const randomChar = this.chars[Math.floor(Math.random() * this.chars.length)];
this.context.fillText(randomChar, particle.x, particle.y);
});
}
We set the fill style to the font color.
For each particle, we set the font size and get a random character from the
chars
.Lastly, we render the particle on the canvas with the
fillText
method.
4. Updating Particles
For each particle, after we draw it on the canvas, we need to move it downward by the particle size, simulating the falling effect.
updateParticles() {
this.particles.forEach(particle => {
if (particle.y > this.size[1]) {
Object.assign(particle, this.newParticle());
} else {
particle.y += particle.size;
}
});
}
We check if the particle has reached the bottom of the canvas. If it has, we reset it by assigning it a new particle using the newParticle
method.
5. Clearing the Canvas
Before redrawing the particles, we need to clear the canvas. We do this by partially erasing the previous frame, creating a smooth trail effect.
clearCanvas() {
this.context.globalAlpha = 0.25;
this.context.fillStyle = this.bg;
this.context.fillRect(0, 0, ...this.size);
this.context.globalAlpha = 1;
}
With the globalAlpha
property we control how strong the erasing is. It can control the length of the trail.
6. The Play Method
Finally, we need to create the animation by continuously clearing the canvas, drawing particles, and updating their positions.
play() {
this.clearCanvas();
this.drawParticles();
this.updateParticles();
setTimeout(() => {
this.play();
}, 50);
}
The play
method calls the clearCanvas
, drawParticles
, and updateParticles
methods in a loop, using the setTimeout
to call the play
method every 50 milliseconds for roughly 20 frames per second, with higher frame rates resulting in faster animation.
Putting It All Together
We can now create an instance of the RainChar class and start the animation by calling the play
method:
const chars = 'ABCDEFGHIJKLMNOPRSTUVWXYZ';
const rain = new RainChar('monospace', 20, chars, 'black', 'lime');
rain.play();
We have created an instance of the RainChar
class and called the play
method to start the animation.
You can see the code of this tutorial in action in the Codepen below:
Conclusion
With RainChar, you can easily add a dynamic and eye-catching rain effect to your web projects. The library is fully customizable, allowing you to experiment with different fonts, colors, and character sets, and is responsive to resizing. Try it out, and see what stunning visuals you can create!
Feel free to explore the GitHub repository for more examples and customization options. If you have any questions or improvements, don’t hesitate to drop a comment below! If you liked it please share it with your friends!
Subscribe to my newsletter
Read articles from Mohammad Sarabi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mohammad Sarabi
Mohammad Sarabi
I am a data analyst and frontend dev from Iran.