One of my previous posts talked about implementing gravity in our canvas experiments. Eventually, we made a realistic bouncing ball. We’ll kind of extend that experiment to make a quick fountain explosion on canvas obeying gravity.
Demo
I have made a demo as a codecast.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Understanding the Logic
First, there’s all those basic code where we get the 2d context of canvas and set our gravity value along with particles count. We wrote a neat little Particle
constructor that we instantiate inside a for loop as many times as the particle_count
. The constructor holds properties for the x axis, y axis, radius, color, x velocity, y velocity and the method to draw the particle using the arc
method on the canvas context.
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext('2d'); var W = canvas.width = window.innerWidth; var H = canvas.height = window.innerHeight; // Let's set our gravity var gravity = 0.5; // Time to write a neat constructor for our // particles. // Lets initialize a random color to use for // our particles and also define the particle // count. var particle_count = 20; var particles = []; var random_color = 'rgb(' + parseInt(Math.random() * 255) + ',' + parseInt(Math.random() * 255) + ',' + parseInt(Math.random() * 255) + ')'; function Particle() { this.radius = 5; this.x = W / 2; this.y = H - this.radius; this.color = random_color; // Random Initial Velocities this.vx = Math.random() * 4 - 2; // vy should be negative initially // then only will it move upwards first // and then later come downwards when our // gravity is added to it. this.vy = Math.random() * -14 - 7; // Finally, the function to draw // our particle this.draw = function() { ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false); ctx.fill(); ctx.closePath(); }; } // Now lets quickly create our particle // objects and store them in particles array for (var i = 0; i < particle_count; i++) { var particle = new Particle(); particles.push(particle); }
The initial velocities on both axis are randomly generated.
// Random Initial Velocities this.vx = Math.random() * 4 - 2; this.vy = Math.random() * -14 - 7;
It is important to note that vy
must be negative initially. Then only will the particles move upwards when you add it to y coordinate, i.e., particle.y
.
Finally, let’s talk a bit about the main part of the code that does the animation along with other tasks like applying gravity, repositioning the particles when they move off the canvas and clearing off the canvas to prevent the particle trails from showing up.
// Finally, writing down the code to animate! (function renderFrame() { requestAnimationFrame(renderFrame); // Clearing screen to prevent trails ctx.clearRect(0, 0, W, H); particles.forEach(function(particle) { // The particles simply go upwards // It MUST come down, so lets apply gravity particle.vy += gravity; // Adding velocity to x and y axis particle.x += particle.vx; particle.y += particle.vy; // We're almost done! All we need to do now // is to reposition the particles as soon // as they move off the canvas. // We'll also need to re-set the velocities if ( // off the right side particle.x + particle.radius > W || // off the left side particle.x - particle.radius < 0 || // off the bottom particle.y + particle.radius > H ) { // If any of the above conditions are met // then we need to re-position the particles // on the base particle.x = W / 2; particle.y = H - particle.radius; // If we do not re-set the velocities then // the particles will stick to base // Velocity X particle.vx = Math.random() * 4 - 2; particle.vy = Math.random() * -14 - 7; } particle.draw(); }); }());
Initially, we clear the entire canvas in renderFrame
so that the particles do not leave trails when moving. Then we forEach
over our particles
array and do some work on each particle.
Firstly, we add gravity to the y velocity turning it positive from negative, hence moving downwards when added to particle.y
. The change to particle.x
occurs at it’s constant velocity, i.e., particle.vx
.
Now, as soon as the particle is off the left, right or bottom edges, it is brought back to it’s original position by re-setting the particle.x
and particle.y
.
particle.x = W / 2; particle.y = H - particle.radius;
Just re-setting the x and y positions is not enough. The velocities also need to be reset else the if
condition will keep on evaluating to true
, and the particles are going to stick at their initial position, i.e., at bottom.
// Velocity X particle.vx = Math.random() * 4 - 2; // Velocity Y particle.vy = Math.random() * -14 - 7;
Note, that the 4, 2, -14, 7 are all random integers to generate random negative and positive velocities. We could have also used Math.sin
or Math.cos
and multiplied the result by a similar integer to get a proper random negative/positive velocity. Just make sure that the Y velocity is always negative initially.
You can also think of these equations like, if you want a random number between Z
and -Z
, then you can multiple 2Z
to Math.random()
and subtract Z
from the product.
It is extremely important to understand the conditions that we’ve placed inside our if
statement, especially the reason behind why we add/subtract the radius to the x/y positions.
if ( // off the right side particle.x + particle.radius > W || // off the left side particle.x - particle.radius < 0 || // off the bottom particle.y + particle.radius > H ) {
Consider this condition – particle.x + particle.radius > W
. Why did we add particle.radius
? Because the x position is in the center of the ball, which means that if we just check for particle.x > W
then there will be times where the ball has already protruded beyond the right edge. Similarly if we check for particle.x < 0
instead of particle.x - particle.radius < 0
, then it'll hold true
when the ball has already popped out off the left surface by more than half of it's width/diameter.
Still confused ? I am sure this figure will be helpful:
Final Words
We have a good idea regarding implementing gravity and also re-positioning objects in our experiments by now. Using these basic concepts, we can build proper realistic interactions in our demos and games.
You can expand on this idea to add more features like positioning the fountain origin at mouse or touch positions or even create more amazing stuffs like fireworks, etc.