Creating a cascade of falling balls with a single click: Here's how!

I'm currently working on a project where I have a ball dropping from the cursor location and redropping when the cursor moves to another position. However, I want to be able to create a new ball every time I click the mouse. I attempted the following code:

canvas.addEventListener('click', function(event) {
  ball.draw();
});

Unfortunately, this doesn't seem to work as expected. Is there a way to draw a brand new ball upon each click rather than just redrawing the same one repeatedly?

Below is the complete code snippet:

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d");
var W = window.innerWidth,
    H = window.innerHeight;
var running = false;

canvas.height = H; 
canvas.width = W;

var ball = {},
    gravity = .5,
    bounceFactor = .7;

ball = {
  x: W,
  y: H,
  radius: 15,
  color: "BLUE",
  vx: 0,
  vy: 1,

  draw: function() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
  }
};

function clearCanvas() {
  ctx.clearRect(0, 0, W, H);
}

function update() {
  clearCanvas();
  ball.draw();

  ball.y += ball.vy;

  ball.vy += gravity;
  if(ball.y + ball.radius > H) {
    ball.y = H - ball.radius;
    ball.vy *= -bounceFactor;
  }
}

canvas.addEventListener("mousemove", function(e){
  ball.x = e.clientX;
  ball.y = e.clientY;
  ball.draw();
});

setInterval(update, 1000/60);

ball.draw();

Answer №1

Make the necessary changes to turn the ball object into something that can be instantiated:

function Ball(W, H) {
  this.x = W;
  this.y = H;
  this.radius = 15;
  this.color = "blue";
  this.vx = 0;
  this.vy = 1;

}

Move the methods into prototypes so they can be shared among instances. Also, include an update method for localized updates:

Ball.prototype = {
  draw: function() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
  },

  update: function() {
    this.y += this.vy;
    this.vy += gravity;
    if(this.y + this.radius > H) {
      this.y = H - this.radius;
      this.vy *= -bounceFactor;
    }
  }
};

In the click event, make sure to rename the array to plural form for clarity. Adjust the mouse position to match the canvas coordinates before creating a new instance of the ball:

var balls = [];                // define an array to hold the balls

To adjust the mouse position for the click event and create a new instance at that location:

canvas.addEventListener('click', function(event) {
  var rect = this.getBoundingClientRect(),  
      x = event.clientX - rect.left,
      y = event.clientY - rect.top;

  balls.push(new Ball(x, y));               
});

In the main animation loop, iterate over the array to draw and update each ball:

function update() {
  clearCanvas();

  for(var i = 0, ball; ball = balls[i]; i++) {
    ball.draw();    
    ball.update();  
  }

  requestAnimationFrame();
}

Live example

If you put these together you will get:

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    W = canvas.width, 
    H = canvas.height,
    gravity = .5,
    bounceFactor = .7;

function Ball(x, y) {
  this.x = x;
  this.y = y;
  this.radius = 15;
  this.color = "blue";
  this.vx = 0;
  this.vy = 1
}

Ball.prototype = {
  draw: function() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
  },

  update: function() {
    this.y += this.vy;
    this.vy += gravity; 
    if (this.y + this.radius > H) {
      this.y = H - this.radius;
      this.vy *= -bounceFactor;
    }
  }
};

function clearCanvas() {
  ctx.clearRect(0, 0, W, H);
}

var balls = []; 

canvas.addEventListener('click', function(event) {
  var rect = this.getBoundingClientRect(),  
      x = event.clientX - rect.left,
      y = event.clientY - rect.top;
  balls.push(new Ball(x, y));              
});

(function update() {
  clearCanvas();

  for (var i = 0, ball; ball = balls[i]; i++) {
    ball.draw(); 
    ball.update(); 
  }

  requestAnimationFrame(update);
})();
canvas {background:#aaa}
<canvas id="canvas" width=600 height=400></canvas>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue with ThreeJS CSG when trying to intersect extruded geometries

element, I'm facing a challenge with extracting multiple views and intersecting them to form a final polygon. The issue arises when there are floating extra parts in the result that are unexpected. My goal is to find a solution to detect these extrane ...

Automated algorithm inspecting a variety of hyperlinks

Recently, I've developed an innovative anti-invite feature for my bot. However, there seems to be a minor glitch where the bot fails to remove any links sent within the enabled guild when the command is triggered. This issue specifically occurs in ver ...

Issue with TypeORM @BeforeInsert causing a field in Entity not to be populated with value

Currently, I am facing an issue where I am attempting to update or insert into a token field before the record is saved. However, when utilizing the @BeforeInsert hook, I encounter the following error: "error": "Cannot read property 'co ...

Unable to modify the active property of the specified object as it is read-only

Presented here is the interface: export interface ProductCommand extends ProductDetailsCommand { } This is the ProductDetailsCommand interface: export interface ProductDetailsCommand { id: string; active: boolean; archive: boolean; title: ...

After receiving a response from the .ajax function, the message will be added to the specified

After a successful AJAX request, the returned data will include a status parameter. If the status is false (indicating no AJAX failure), the data will contain an array of element names along with their corresponding error messages. The objective here is to ...

In Vue.js, you can utilize one property to access additional properties within the same element

Is it possible to access other properties of the same element using a different property in Vue.js? For example: Rather than writing it like this: <kpi title="some_kpi" v-if="displayed_kpi==='some_kpi'"></kpi> I ...

Is my input file in Javascript valid and does it exist? Here's how to check

In my Javascript code, I am retrieving strings from a text file. When the user inputs an incorrect or invalid file name, I want to display a message. For example: console.log("Your input is invalid"); Here is the code snippet that reads the text file and ...

"Using the Google Maps directive inside a separate modal directive in Angular results in a blank map display

As a newcomer to Angular, I have encountered a hurdle while attempting to incorporate a 'google maps' directive inside another directive. The following code showcases a 'modal-view' directive that loads a form: angular.module(&apo ...

Tips on changing the outline color by clicking

I'm working on a simple code where I need to change the outline color when a user clicks on a text field. <input type="text" id="box1" /> <input type="password" id="box2" /> <input type="email" id="box3" /> <input type="submit" ...

ts-jest should replace the character '@' with the '/src' folder in the map configuration

I have set up a node project using TypeScript and configured various paths in my tsconfig.json file, such as: "paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl' ...

The transfer of information through HTTP

I have been exploring the inner workings of HTTP servers and clients to better understand how data is transmitted. Despite reading numerous articles on the topic, I still have some lingering questions that remain unanswered. I would like to walk through th ...

The result of filtering multiple data using checkboxes in Vuetify is not displaying as expected

I am currently working on developing a straightforward task scheduler that includes filtering options using checkboxes. Below is the snippet from my vue file: Within my templates section, <fieldset> <legend>TASK STATUS</legend> ...

Trouble keeping HTML/Javascript/CSS Collapsible Menu closed after refreshing the page

My issue is that the collapsible menu I have created does not remain closed when the page is refreshed. Upon reloading the page, the collapsible menu is always fully expanded, even if it was collapsed before the refresh. This creates a problem as there is ...

What is the best way to alter the order of the .map function in JavaScript to display in ascending or descending order?

I currently have 25 songs in my Spotify playlist, with the possibility of more being added in the future. The songs are numbered from 1 to 25. However, the map() function I use only displays the top 10 songs (1 to 10). When a new song is added, it is assig ...

Tips for enabling auto-scroll feature in MuiList

Currently, I am working on a chat window component that utilizes Material UI for styling. I expected that setting a height or max-height on either the MuiList or MuiBox encapsulating the list would automatically scroll to the new message when it's sen ...

An error occurred stating "No matching closing tag found for "<%" when attempting to include the file

While attempting to include other .ejs files using the same syntax, everything works perfectly except when including my _show.ejs file. I am unsure where the issue lies, whether it is in the index.ejs or _show.ejs file. This is my index.ejs File <!-- i ...

Retrieve every hour between two specific timestamps

I am attempting to retrieve all dates between two timestamps that fall on a specific day of the week. I have a start date represented by 'start1' and an end date represented by 'end1'. Additionally, I have a list of days with correspon ...

What sets apart jQuery's `click`, `bind`, `live`, `delegate`, `trigger`, and `on` functions, and how can they be utilized differently in coding

I have gone through the documentation for each function provided on the official jQuery website, but I couldn't find any comparison listings for the following functions: $().click(fn) $().bind('click',fn) $().live('click',fn) $().d ...

Mapbox is capable of managing several GEOJSON files by utilizing the loadURL function

I am in the process of creating a map that is designed to load multiple layers from various sources based on a configuration file called config.json. Each layer should trigger a popup when clicked, but oddly enough, I am only able to see the popup for the ...

What is the best way to format specific text as bold within an input text field?

I am attempting to make certain text bold within an input text field. However, I'm uncertain about how to achieve this because HTML code is not recognized inside a text field. Therefore, using <b> will not be effective. Is there a way to bold sp ...