Here we bring a code example showing inheritance/delegation in an object hierarchy as well as an illustration of how to put bitmap images onto the canvas.
<!doctype html>
<html>
<head>
<meta charset='utf-8'/>
<title id='title'></title>
<style>
canvas {
margin: 1em;
}
</style>
<script type='module' src='./ShapeExec.js'></script>
</head>
<body>
<h1 id='h1'></h1>
<canvas id='foo'></canvas>
<canvas id='bar'></canvas>
<canvas id='baz'></canvas>
</body>
</html>
Shape.js
'use strict';
/**
* Shape object, parent
*/
export class Shape {
constructor(cv, x, y, color) {
this.ctx = cv.getContext('2d');
this.x = x;
this.y = y;
this.color = color;
}
draw() {
this.ctx.beginPath();
this.ctx.moveTo(this.x, this.y);
this.ctx.rect(this.x, this.y, this.width, this.height);
this.ctx.closePath();
this.ctx.lineWidth = 2;
this.ctx.fillStyle = this.color;
this.ctx.fill();
this.ctx.stroke();
}
};
export class Rect extends Shape {
constructor(cv, x, y, width, height, color) {
super(cv, x, y, color);
this.width = width;
this.height = height;
}
};
export class Circle extends Shape {
constructor(cv, x, y, r, startangle, endangle, color, dir) {
super(cv, x, y, color);
this.r = r;
this.startangle = startangle;
this.endangle = endangle;
this.direct = dir;
}
draw() {
this.ctx.beginPath();
this.ctx.moveTo(this.x, this.y);
this.ctx.arc(this.x, this.y, this.r, this.startangle, this.endangle, this.direct);
this.ctx.closePath();
this.ctx.lineWidth = 2;
this.ctx.fillStyle = this.color;
this.ctx.fill();
this.ctx.stroke();
}
};
export class Ellipse extends Shape {
constructor(cv, x, y, r, startangle, endangle, color, dir, scx, scy, rot) {
super(cv, x, y, color);
this.r = r;
this.startangle = startangle;
this.endangle = endangle;
this.direct = dir;
this.scalex = scx;
this.scaley = scy;
this.rotate = rot;
}
draw() {
this.ctx.save();
this.ctx.scale(this.scalex, this.scaley);
this.ctx.beginPath();
this.ctx.moveTo(this.x, this.y);
this.ctx.arc(this.x, this.y, this.r, this.startangle, this.endangle, this.direct);
this.ctx.closePath();
this.ctx.restore();
this.ctx.rotate(this.rotate);
this.ctx.lineWidth = 2;
this.ctx.fillStyle = this.color;
this.ctx.fill();
this.ctx.stroke();
}
};
Here you see
draw method as a default.
Its intention is to draw
rectangles assuming most shapes will be rectangles.
init to set values of the
rectangle object. Notice that it supplies values for
width, and height. Its
coordinates and color is given to the inititalization
method of shape.
After the definition of the object you see a statement
linking this object to the prototype of
Shape. Because the Shape object
can draw rectangles we do not need a method for drawing
here.
Circle object, also linked
to the prototype of Shape. Its initializing
method provides values for the circle specific
parameters, while the common values are send to the
Shape constructor. Here we have a method
draw method overriding the namesake in
Shape. This is useful and standard OOP
style when we work with inheritance. The rectangle
object inherited the drawing method from shape, but
drawing a circle requires special code, so this object
has to override the standard drawing method.
Ellipse object also inherits from
Shape. Like Circle it has separate
parameters giving values to its specific properties,
coordinates and color passed on to Shape.
Drawing an ellipse requires its own drawing method so
again we override the one from Shape. It
is also different from drawing a circle because we
employ scaling and rotation. Both are activities we
for perhaps obvious reasons do not need on circles.
ShapeExec.js
'use strict';
import {Canvas} from './nmlCanvas.js';
import {Rect, Circle, Ellipse} from './Shape.js';
import {$} from './nQuery.js';
const cv1 = function(cv, color) {
let myCanvas = new Canvas(cv, color);
let c = new Circle(myCanvas, 75, 75, 50, Math.PI*1/4, Math.PI*7/4, 'red', false);
c.draw();
let r = new Rect(myCanvas, 150, 15, 110, 45, 'yellow');
r.draw();
r = new Rect(myCanvas, 150, 85, 110, 45, 'green');
r.draw();
}
const cv2 = function(cv, color) {
let myCanvas = new Canvas(cv, color);
let c = new Ellipse(myCanvas, 75, 75, 50, Math.PI*1/4, Math.PI*7/4, 'red', false, 1.5, 1, 0);
c.draw();
let r = new Rect(myCanvas, 210, 15, 45, 45, 'teal');
r.draw();
r = new Rect(myCanvas, 210, 85, 45, 45, 'blue');
r.draw();
}
const cv3 = function(cv, color) {
let myCanvas = new Canvas(cv, color);
let img1 = new Image();
img1.src = './outline-tran.2.gif'; // spawns load of image from net
img1.addEventListener('load', function() { // wait till its here
myCanvas.getContext().drawImage(img1, 10,10, 100, 100); // then draw
});
let img2 = document.createElement('img');
img2.src = './sit3-shine.7.gif';
img2.addEventListener('load', function() {
myCanvas.getContext().drawImage(img2, 200, 50, 100, 100);
});
}
const doThis = function() {
const T = document.createTextNode("JS Testing Inheritance + imageDraw");
$('title').appendChild(T);
$('h1').appendChild(T);
cv1('foo', 'silver');
cv2('bar', 'gray');
cv3('baz', '#eee');
}
window.addEventListener('load', doThis);
In the JavaScript of Shape.html we have included
a third canvas illustrating the code needed for putting
regular bitmapped graphic files onto the canvas. It is
done by the drawImage() method of the canvas
context. The only complication we face is that we need
to wait for an asynchroneous download of the image file
when it is referenced. This is the same behaviour as the
browser shows when rendering web pages in general. To
wait for the image we need to code a load
event handler on the image and let that handle the
actual drawing.