Hello everyone! Welcome to this tutorial where we're going to dive into the world of creating fun bubbles in code by using HTML canvas and JavaScript. The best part? We'll achieve all of this using just a touch of HTML and all JavaScript, no CSS.
Unveiling the Concepts
Today, we're going to master the following concepts:
- Creating circles using the
arc
method of the canvas context. - Utilizing the
requestAnimationFrame
function for smooth circle animations. - Harnessing the power of JavaScript classes to create multiple circles without repeating code.
- Adding stroke styles and fill styles to our circles for a 3D bubble effect.
You can follow along with me, or use the final codepen if you want to take a look at the source code
If you prefer to learn in a video format, follow along this video
Getting Started
First things first, we need an HTML5 Canvas element. Canvas is a powerful element for creating shapes, images and graphics. This is what the bubbles will be created on. Let’s set it up -
<canvas id="canvas"></canvas>
In order to do anything meaningful with canvas, we need to have access to it’s context
. Context provides a interface to render objects on the canvas and draw shapes.
Let’s get access to canvas and it's context.
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
We will set up our canvas to use the entire window height and width -
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Let's give our canvas a nice soothing light blue background by adding some css. This is the only CSS we are going to use. You can also do this with JavaScript if you wish.
#canvas {
background: #00b4ff;
}
Time to Create Bubbles!
Let’s get to the fun part. We are going to create bubbles by clicking on the canvas. To achieve this, we'll start by creating a click event handler:
canvas.addEventListener('click', handleDrawCircle);
Since we need to know where we clicked on our canvas, we are going to keep track of it in our handleDrawCircle
function and use the event’s coordinates -
//We are adding x and y here because we will need it later.
let x, y
const handleDrawCircle = (event) => {
x = event.pageX;
y = event.pageY;
// Draw a bubble!
drawCircle(x, y);
};
Drawing circles using arc method
To create circles, we are going to utilize the arc
method available on canvas’s context. Arc
method accepts x
and y
- the center of the circle, a radius and a start angle and end angle which for us will be 0
and 2* Math.PI
since we are creating a full circle.
const drawCircle = (x, y) => {
context.beginPath();
context.arc(x, y, 50, 0, 2 * Math.PI);
context.strokeStyle = 'white';
context.stroke();
};
Moving circles using requestAnimationFrame method
Now that we have circles, let's make them move because....
Remember, that when we created circle, we used the arc
method which accepted x
and y
coordinates - the center of the circle. If we move the x
and y
coordinate of our circle really fast, it will give an impression that the circles are moving. Let’s try that!
//Define a speed by which to increment to the x and y coordinates
const dx = Math.random() * 3;
const dy = Math.random() * 7;
//Incremenet the center of the circle with this speed
x = x + dx;
y = y - dy;
We can move this inside a function -
let x, y;
const move = () => {
const dx = Math.random() * 3;
const dy = Math.random() * 7;
x = x + dx;
y = y - dy;
};
To give our circle a seemless movement, we are going to create an animate
function and use browser's requestAnimationFrame
method to create a moving circle.
const animate = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
move();
drawCircle(x,y);
requestAnimationFrame(animate);
};
//Don't forget to call animate at the bottom
animate();
Creating Particles: Introducing the Particle Class
Now that we have created one circle, it’s time to create multiple circles!
But before we create multiple circles, let's prepare our code.
In order to avoid repeating our code, we are going to use classes and introduce Particle class. Particles are the building blocks of our dynamic artwork and animation. Each bubble is a particle with its own position, size, movement, and color attributes. Let's define a Particle
class to encapsulate these properties:
class Particle {
constructor(x = 0, y = 0) {}
draw() {
// Drawing the particle as a colored circle
// ...
}
move() {
// Implementing particle movement
// ...
}
}
Let’s move some of the constants we had set up to the Particle class -
class Particle {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
this.radius = Math.random() * 50;
this.dx = Math.random() * 3;
this.dy = Math.random() * 7;
}
draw() {
// Drawing the particle as a colored circle
// ...
}
move() {
// Implementing particle movement
// ...
}
}
The draw
method will be responsible for rendering the particle on the canvas. We have already implemented this functionality in drawCircle
, so let’s move it in our class and update variables to be class variables
class Particle {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
this.radius = Math.random() * 50;
this.dx = Math.random() * 3;
this.dy = Math.random() * 7;
this.color = 'white';
}
draw() {
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
context.strokeStyle = this.color;
context.stroke();
context.fillStyle = this.color;
context.fill();
}
move() {}
}
Similarly, let’s move the move function within the class-
move() {
this.x = this.x + this.dx;
this.y = this.y - this.dy;
}
Now, we need to make sure that in our event handler, we are calling the class Particle.
const handleDrawCircle = (event) => {
const x = event.pageX;
const y = event.pageY;
const particle = new Particle(x, y);
};
canvas.addEventListener('click', handleDrawCircle);
Since we need to access this particle in our animate function in order to call the move
method on it, we will store this particle in an array called particleArray
. This array will also be helpful when create lots of particles. Here’s the updated code to reflect this -
const particleArray = [];
const handleDrawCircle = (event) => {
const x = event.pageX;
const y = event.pageY;
const particle = new Particle(x, y);
particleArray.push(particle);
};
canvas.addEventListener('click', handleDrawCircle);
Remember to update the animate
function too -
At this point, you will see this particle on your screen-
Awesome! Now, to the fun part! Let's creates lots of circles and style them to make them look like bubbles.
To create lots of bubbles, we are going to create particles using for
loop and add them to the particleArray
we had created here.
const handleDrawCircle = (event) => {
const x = event.pageX;
const y = event.pageY;
for (let i = 0; i < 50; i++) {
const particle = new Particle(x, y);
particleArray.push(particle);
}
};
canvas.addEventListener('click', handleDrawCircle);
In the animate function, we'll continuously update the canvas by clearing it and redrawing the particles in their new positions. This will give an illusion of the circle moving.
const animate = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
particleArray.forEach((particle) => {
particle?.move();
particle?.draw();
});
requestAnimationFrame(animate);
};
animate();
Now that we have bubbles moving, it’s time to add color to them to make them look like bubbles!
We will do this by adding a gradient fill to bubbles. This can be done using the context.createRadialGradient
method.
const gradient = context.createRadialGradient(
this.x,
this.y,
1,
this.x + 0.5,
this.y + 0.5,
this.radius
);
gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.3)');
gradient.addColorStop(0.95, '#e7feff');
context.fillStyle = gradient;
Here’s the final codepen if you want to take a look at the source code
Wrap Up
Congratulations! You've just created something super fun using only HTML Canvas and JavaScript. You've learned how to use the arc
method, leverage requestAnimationFrame
, harness the power of JavaScript classes, and style your bubbles using gradients for the 3D bubble effect.
Feel free to experiment with colors, speeds, and sizes to make your animations truly unique.
I hope you had as much fun following this tutorial as I did creating it. Now, it's your turn to experiment. I would love to see if you tried this out and what you created. Share with me your code link and I would love to check it out.
And now a #DevJoke -
Question - Who won the debate for the best name for loop variable?
Answer - i won.
If you enjoyed this article, share it with someone who will benefit from it.
If you are interested in articles like this and front-end articles on JavaScript, React, GraphQL or Accessibility and career advice from a. Staff Engineer, sign up for my newsletter and get these directly in your inbox.
Top comments (0)