Experiment 3: Bouncy Balls

Sections in this Article:

An Alternative Engine

Just for a bit of fun and as a demonstration that the Ball and KeyValuePair classes can be re-used, here's an alternative implementation not using a Web Component or svg graphics...

As before, click or touch the 'screen' to launch a new ball!

The graphical elements here are entirely html. The 'screen' is a div element and each ball is a png image of a picture of a ball (with transparency).

Here's the complete source code for this example, the Ball and KeyValuePair classes are not shown and are exactly as before.

engine.js
1class Engine {
2
3   _targetElement = null;
4   _balls = new Array();   // array of balls
5   
6   _bounds = {
7         width: 400,
8         height: 300
9    }
10   
11   constructor(targetElement) {
12      this._targetElement = document.getElementById(targetElement);
13      this.newBall(this);
14      
15      // lexical scope of arrow functions, so we can reference the engine!
16      let self = this;
17      
18      // add a click handler to create a new ball
19      this._targetElement.addEventListener("click", () => {
20         self.newBall(self);
21      }); 
22      
23      // kick off a timer to run the animation
24      window.setInterval( () => {       
25               // now update all of the balls
26               self._balls.forEach( kvp => {
27                  // get the ball and corresponding svg element
28                  let theBall = kvp.value;
29                  let theImg = kvp.key;
30                  
31                  // re-calculate the bounds
32                  self._bounds.width = self._targetElement.offsetWidth;
33                  self._bounds.height = self._targetElement.offsetHeight;
34
35                  // update the ball position
36                  theBall.Update(self._bounds);
37                                 
38                  // now update position of the ball
39                  // the style is position in pixels, hence the "px"
40                  theImg.style.top = (theBall.y-16)+"px";
41                  theImg.style.left = (theBall.x-16)+"px";
42               });
43           }, 10 ); // timer interval
44
45   }
46   
47   newBall(self) {
48      // create a new ball image and add to the div
49      let elm = document.createElement('img');
50      
51      // link to the image for the ball, the ball image is 32, 32 pixels
52      elm.src = '/mobile/programming/experiments/experiment-003/ball.png';
53      
54      // bit more css to get it working, absolutely positioned and
55      // a little css hack to stop the balls from being selected when clicking
56      // the screen
57      elm.style = "position: absolute; user-select: none; top: -32px;";
58      
59      // add a new image to the host div element
60      self._targetElement.appendChild(elm);
61      
62      // now create an instance of a ball; all balls have a radius of 16 pixels
63      let ball = new Ball( 16, 0.1, 0.85, self._bounds);
64      
65      let kpv = new KeyValuePair(elm, ball);
66        self._balls.push(kpv);
67   }
68}
69
70window.addEventListener('load',  (event) => {
71   let engine = new Engine('ball-screen');
72});

This is a slightly simplified version of what's already been seen, mostly because this version does not handle any dynamic resizing of the 'screen'. Let's go through the key bits [not the entire listing as most of it should be pretty familar by now]...

The constructor on Line 11 takes 1 parameters, the Id of a targetElement , in the DOM where the animation will run.

Line 16 creates a local reference to the this pointer; basically to ensure the arrow functions can reference back to the engine through their lexical scope. There are otherways of doing this using bind() but not touched on in this experiment.

Lines 19-21 attach a click handler to the parent div element, so when its clicked, it will create a new ball!

Lines 32-33 get the inner dimensions of the host div element; this is required for detecting the edges of the 'screen'.

Lines 40-41 set the position for the corresponding img that represents the instance of the ball. Note that a quick calculation is performed as it's the position of the top-left corner of the image that is being positioned, not the centre hence of offset of 16! The units of "px" also need to be added!

Jumping into the newBall method, Line 49 creates an img element. Line 52 sets the src attribute of the element to the Url of the image of the ball, and Line 57 sets some stlying information for the ball to permit it to be absolutely positioned within the parent div element and also prevents it from being selected, as may happen if a user accidentally clicks on a ball!

The final difference is the creation of the ball on Line 63; in this example, all balls are created equal and experience the same gravity, are of the same radius and experience the same loss in energy with each bounce; these are the properties required to describe how the ball behaves.

To kick everything off, Lines 70-72 create an instance of the engine class on the window 'load' event.

So that's it, the Ball and KeyValuePair classes have been completely re-used without any modification, and in an entire different implementation of the bouncy balls simulation!

Continue on reading to Conclusions...