The Nature of Motion on the Canvas, Final Step, Organizing Code

The last step, after testing, is organizing the code. The JavaScript in this example comes in three categories:

  1. The Canvas object is project agnostic. Works with every project needing a canvas. We put it into a separate JavaScript library file CanvasObject.js.
  2. The UFO object is project agnostic. It needs a canvas. It is not needed on every canvas, only when we play with balls with some random characteristics. We put it into a separate JavaScript library file UFObject.js.
  3. The rest of the JavaScript is page specific, and thus belongs in the page. It might be in the HTML5 file, but generally it is good practice to split it into a separate file for the same reasons as you would split CSS into separate files. So …
Example F.12. mystyles.css
/*
    minimal css for animation masterclass
*/
#canvas { border: 2px solid blue; }

Example F.13. CanvasObject.js
'use strict';

let Canvas = {
    init(canvasId, color) {     // note new syntax
        this.canvas = $(canvasId);
        this.context = this.canvas.getContext("2d");
        this.color = color;
        this.context.fillStyle = color;
        this.context.fillRect(0, 0, this.canvas.width, 
                              this.canvas.height);
    },
    getContext() {
        return this.context;
    },
    clear() {
        this.context.clearRect(0, 0, this.canvas.width, 
                               this.canvas.height);
    },
    prep() {
        this.context.fillStyle = this.color;
        this.context.fillRect(0, 0, this.canvas.width, 
                              this.canvas.height);
    },
    getHeight() {
        return this.canvas.height;
    },
    getWidth() {
        return this.canvas.width;
    }
}

Example F.14. UFObject.js
'use strict';

let UFO = {
    init(canvas, color) {
        this.canvas = canvas;
        this.x = Math.random() * this.canvas.getWidth();
        this.y = Math.random() * this.canvas.getHeight();
        this.r = Math.random() * 9 + 3;
        this.dx = Math.random() * 5;
        this.dy = Math.random() * 5;
        this.color = color;
        this.draw();
    },
    draw() {
        this.canvas.getContext().beginPath();
        this.canvas.getContext().fillStyle = this.color;
        this.canvas.getContext().arc(this.x, this.y, this.r,
                                     0, Math.PI * 2,
                                     false);
        this.canvas.getContext().fill();
        this.canvas.getContext().closePath();
    },
    getArea() {
        return Math.PI * this.getR() * this.getR();
    },
    getX() {
        return this.x;
    },
    getY() {
        return this.y;
    },
    getR() {
        return this.r;
    },
    setR(r) {
        this.r = r;
    },
    move() {
        if (this.x + this.dx > this.canvas.getWidth()
                || this.x + this.dx < 0)    // at edge?
              this.dx = -this.dx;           // change direction
        if (this.y + this.dy > this.canvas.getHeight()
                || this.y + this.dy < 0)    // at edge
              this.dy = -this.dy;           // change direction
        this.x += this.dx;  // move object horizontally
        this.y += this.dy;  // move vertically
    },
    hits(arr, me) {
        for (var ob of arr) {               // iterate over balls
            if (Object.is(ob, me)) {        // myself?
                continue;                   // uninteresting
            }
            if (Math.sqrt(                  // dist < r1 + r2 => hit
                Math.pow(ob.getX() - me.getX(), 2) +
                Math.pow(ob.getY() - me.getY(), 2))
                <= (ob.getR() + me.getR())) {
                    console.log("Ball " + ob.color + " hit ball " + me.color);
                    // this print is for test only
                    // a game would invent an explosion here
                    // or one object swallows another, or ...
                    if (ob.getR() > me.getR()) {
                        console.log(ob.color + ' is bigger');
                        //this.growth(ob, me);
                    } else {
                        console.log(me.color + ' is bigger');
                        //this.growth(me, ob);
                    }
            }
        }
    },
    growth(me, ob, arr) {
        let da = me.getArea() + ob.getArea();
        let dr = Math.sqrt(da / Math.PI);
        console.log('radial increment: ' + dr);
        me.setR(me.getR() + dr);
        console.log('radius after: ' + me.getR());
    }
}

Example F.15. animatei.js
'use strict';

let motor = function (cv, obs) {
    setInterval(function() {            // animate
        cv.clear();
        cv.prep();
        for (let obj of obs) {
            obj.move();                 // iterate over
            obj.hits(obs, obj);
            obj.draw();                 // all array values
        }
    }, 10);
}

let initialize = function () {
    let canvas = Object.create(Canvas); // create canvas
    canvas.init('canvas', '#ccc');
    let ufos = [];                      // create array
    let ufo0 = Object.create(UFO);      // create ufo
    ufo0.init(canvas, 'black');
    ufos.push(ufo0);                    // add ufo to array
    ufo0 = Object.create(UFO);          // create ufo          
    ufo0.init(canvas, 'green');
    ufos.push(ufo0);                    // add ufo to array
    ufo0 = Object.create(UFO);          // create ufo
    ufo0.init(canvas, 'blue');
    ufos.push(ufo0);                    // add ufo to array
/*
    ufo0 = Object.create(UFO);          // create ufo          
    ufo0.init(canvas, '#560');
    ufos.push(ufo0);                    // add ufo to array
    ufo0 = Object.create(UFO);          // create ufo
    ufo0.init(canvas, '#056');
    ufos.push(ufo0);                    // add ufo to array
*/
    motor(canvas, ufos);                // start motor 
}

window.addEventListener('load', initialize, false);

Example F.16. animatei.html
<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Canvas MasterClass on Animation. Organizing Code</title>
        <link rel='stylesheet' href='css/mystyles.css'/>
        <script src='js/nQuery.js'></script>
        <script src='js/CanvasObject.js'></script>
        <script src='js/UFObject.js'></script>
        <script src='js/animatei.js'></script>
    </head>
    <body>
        <h1>Hello World!</h1>
        <h2>Let's Play!</h2>
        <section>
            <canvas id='canvas' width='400' height='400'>
                If you see this text long enough to read it,
                your browser is very old.
            </canvas>
        </section>
    </body>
</html>

Test it!