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

Utilizing Javascript for a Stopwatch/Countdown in the Format: 00:00:00

I am currently working with this block of code: function startStopwatch() { vm.lastTickTime = new Date(); $interval.cancel(vm.timerPromise); vm.timerPromise = $interval(function() { var tickTime = new Date(); ...

Implementing Multiple Identification using JavaScript and PHP

I need to complete a simple task. Here is the code snippet: echo' <div class="col-sm-12" id="recensioni_titolo"> <form role="form" id="review-form" method="post" action="php\insert_comment.php"> ...

Development with Node JS, express, Mongoose, and intricate nested queries

I'm struggling with a group of interconnected queries using express/mongoose, structured like this: app.get(..., function(...) { Schema1.query(..., function(..., res1) { for ( var key in res1 ) { Schema2.query(..., function(..., ...

Error 404: This page seems to have gone missing. Django and react-router

Struggling to integrate reactjs and react-router (1.x) with my Django project. I'm finding it challenging to make everything work together seamlessly. For more details, you can check out the project on GitHub: https://github.com/liondancer/django-che ...

When using jQuery AJAX to Like/Dislike, a 500 (Internal Server Error) is returned, but the functionality works correctly upon reloading the

My website has a feature where users can press a Like Status button that uses AJAX to send data to the controller. When the button is clicked, it changes from "like" to "dislike" and all associated classes and form actions are updated accordingly. The is ...

"Encountering a Prisma type error while attempting to add a new record

I am currently utilizing Prisma with a MySQL database. Whenever I attempt to create a new record (School), an error of type pops up in the console. Additionally, I am implementing a framework called Remix.run, although it does not seem to be causing the is ...

Exploring the complexities of object scope in Javascript

I'm having trouble accessing certain variables from a function. Here's the issue I'm facing: var PTP = function (properties) { this.errorsArray = [] } PTP.prototype.initHTMLItems = function () { $('input[data-widget-type="dat ...

Exploring the power of Vue i18n for prop translations in Vue applications

My goal is to translate the titles passed to my component through props. However, it seems that these strings are not being translated like the rest of my code due to being passed as props. Below are the two components I am currently working with: Parent ...

Error encountered with Firebase Modular SDK V9.0.0+: Issue with undefined firebase property 'apps' causing TypeError

Encountered an error while attempting to run my next.js app. Despite trying various methods, I have been unable to resolve it. The version of Firebase I am using is 9.0.1 Server Error TypeError: Cannot read property 'apps' of undefined The error ...

How can I append a hash to a URL using JavaScript without causing the page to scroll?

Is it possible to add a hash to the URL without scrolling the page using JavaScript? I navigate to the page I scroll down I click on a link that adds a hash (for example: http://www.example.com/#test) The page should not scroll back to the top. What is ...

Gulp encountered an issue - TypeError: When attempting to call the 'match' method, it was found to be undefined

Currently, I'm attempting to utilize Gulp alongside BrowserSync for a website that is being hosted on MAMP and proxied through localhost:8888. Unfortunately, upon running gulp, I encounter the following error: [17:38:48] Starting 'browser-sync& ...

Error: Express JS custom module cannot be located in the root directory's modules folder

The file structure of my express js app resembles this I'm attempting to load a modules folder from the root directory. routes/users.js var express = require('express'); var router = express.Router(); var md=require('./modules') ...

Highcharts displaying black color after AJAX refresh

Struggling to implement real-time data visualization using Highcharts, I found myself tired of sifting through documentation and feeling confused. My solution involves using AJAX refresh. When the page initially reloads, my chart renders properly. However, ...

The v-select menu in Vuetify conceals the text-field input

How can I prevent the menu from covering the input box in Vuetify version 2.3.18? I came across a potential solution here, but it didn't work for me: https://codepen.io/jrast/pen/NwMaZE?editors=1010 I also found an issue on the Vuetify github page t ...

How can you determine the class of an element that was clicked within an iframe?

Is it possible to retrieve the class of an element that is clicked within an iframe? Here is the HTML code: <input id="tag" type="text"> <iframe id="framer" src="SameDomainSamePort.html"></iframe> This is the JavaScript code: $(docum ...

Tips for stopping Angular from rendering a directive that is producing incorrect results after an error is thrown

My directives have a template and a compile function that modifies the template. Occasionally, Angular fails to recognize jQuery (and defaults to using jqLite), causing my compile to encounter errors. Despite this, Angular continues to render the directive ...

Update your content dynamically by refreshing it with JQuery/AJAX following the usage of an MVC partial view

Implementing the following JQuery/AJAX function involves calling a partial view when a selection is made in a combobox labeled "ReportedIssue" that resides within the same partial view. The div containing the table is named "tableContent". <script type ...

I encountered a "TypeError: Unable to access property 'name' of undefined" error when attempting to export a redux reducer

UPDATE: I encountered an issue where the namePlaceholder constant was returning undefined even though I was dispatching actions correctly. When attempting to export my selector function, I received an error: Here is the component code: import React, { ...

Conceal the Tab Bar in Stack Navigator Excluding the tabBarVisible Setting

I discovered some solutions using outdated versions of navigation, specifically involving the "tabBarVisible" option in Tab Navigator. However, this option is no longer available, so I am seeking guidance on how to hide the Tab Bar on specific screens with ...

Fill a .js script with moustache.js

I need help with organizing some JavaScript code that needs to access server-side data. I want to keep this code in a separate .js file, but I'm having issues populating it with the necessary server information using moustache. Here is my current setu ...