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...