aboutsummaryrefslogtreecommitdiff
path: root/js/lib
diff options
context:
space:
mode:
authorBen Beltran <ben@nsovocal.com>2016-11-10 00:49:23 -0600
committerBen Beltran <ben@nsovocal.com>2016-11-10 00:49:23 -0600
commitfff45e3b9d371851d4e8c3e1655a5bf1509080f2 (patch)
tree418234d8e6ddd394794639da197d14399fd841ed /js/lib
parente8f6a707779aa341582b87c2a5ed345c7dd9069d (diff)
parentf28c4a44ce8403ea5eba9d49d3d46b7b8dae1d42 (diff)
Merge branch 'feature/draw-the-heart' into develop
Diffstat (limited to 'js/lib')
-rw-r--r--js/lib/heart_renderer.js210
1 files changed, 210 insertions, 0 deletions
diff --git a/js/lib/heart_renderer.js b/js/lib/heart_renderer.js
new file mode 100644
index 0000000..fd4adf6
--- /dev/null
+++ b/js/lib/heart_renderer.js
@@ -0,0 +1,210 @@
+'use strict';
+
+((window) => {
+
+ const kColorIteratorLimit = 256;
+ const kRedSpeed = 0.1;
+ const kGreenSpeed = 0.2;
+ const kBlueSpeed = 0.15;
+
+ /**
+ * Renders a Heart, has its own canvas, which will be placed in the element
+ * set by calling #render.
+ *
+ * @class HeartRenderer
+ * @param {object} config configuration, extends any member of the renderer
+ */
+ const HeartRenderer = class HeartRenderer {
+
+ constructor(config) {
+
+ /**
+ * The instance of the heart renderer being used
+ *
+ * @memberof HeartRenderer
+ * @instance
+ * @name canvas
+ * @type HTMLCanvasElement
+ * @default A brand new canvas
+ */
+ this.canvas = window.document.createElement('canvas');
+
+ /**
+ * The maximum fps that will be used
+ *
+ * @memberof HeartRenderer
+ * @instance
+ * @name fps
+ * @type Number
+ * @default 60
+ */
+ this.fps = 60;
+
+ /**
+ * The size of the heart as a percentage of the canvas smallest dimension
+ *
+ * @memberof HeartRenderer
+ * @instance
+ * @name heartSize
+ * @type Number
+ * @default 50
+ */
+ this.heartSize = 50;
+
+ this._animating = false; // The status of the animation.
+ this._previousFrameTime = Date.now(); // The timestamp of the last frame for fps control
+ this._currentColor = { // The current color that will be painted
+ red: 100,
+ blue: 0,
+ green: 50
+ };
+
+ Object.assign(this, config);
+ }
+
+ /**
+ * Attaches the canvas to an HTML element
+ *
+ * @memberof HeartRenderer
+ * @function render
+ * @instance
+ * @param {HTMLElement} element the element where we will attach our canvas
+ */
+ render(element) {
+
+ element.appendChild(this.canvas);
+ this.resize();
+ }
+
+ /**
+ * Resizes the canvas
+ *
+ * @memberof HeartRenderer
+ * @function render
+ * @instance
+ * @param {HTMLElement} element the element where we will attach our canvas
+ */
+ resize() {
+
+ if (this.canvas.parentElement) {
+ this.canvas.width = this.canvas.parentElement.offsetWidth;
+ this.canvas.height = this.canvas.parentElement.offsetHeight;
+ }
+ }
+
+ /**
+ * Gets the context from the current canvas and starts the animation process
+ *
+ * @memberof HeartRenderer
+ * @function activate
+ * @instance
+ */
+ activate() {
+
+ const context = this.canvas.getContext('2d');
+ this._startAnimating(context);
+ }
+
+ /**
+ * Stops the animation process
+ *
+ * @memberof HeartRenderer
+ * @function deactivate
+ * @instance
+ */
+ deactivate() {
+
+ this._stopAnimating();
+ }
+
+ // Starts the animation loop
+ _startAnimating(context) {
+
+ this._frameDuration = 1000 / this.fps;
+ this._animating = true;
+
+ this._animate(context);
+ }
+
+ // Stops the animation on the next frame.
+ _stopAnimating() {
+
+ this._animating = false;
+ }
+
+ // Runs the animation step controlling the FPS
+ _animate(context) {
+
+ if (!this._animating) {
+ return;
+ }
+
+ window.requestAnimationFrame(this._animate.bind(this, context));
+
+ const currentFrameTime = Date.now();
+ const delta = currentFrameTime - this._previousFrameTime;
+
+ if (delta > this._frameDuration) {
+ this._previousFrameTime = Date.now();
+ this._animateStep(context, delta);
+ }
+ }
+
+ // The actual animation processing function.
+ _animateStep(context, delta) {
+
+ this._updateColor(delta);
+ this._drawHeart(context, delta);
+ }
+
+ // Updates the current color
+ _updateColor(delta) {
+
+ this._currentColor.red = Math.round(this._currentColor.red + delta * kRedSpeed) % kColorIteratorLimit;
+ this._currentColor.green = Math.round(this._currentColor.green + delta * kGreenSpeed) % kColorIteratorLimit;
+ this._currentColor.blue = Math.round(this._currentColor.blue + delta * kBlueSpeed) % kColorIteratorLimit;
+ }
+
+ // Draws a heart
+ _drawHeart(context, delta) {
+
+ const canvasHeight = this.canvas.height;
+ const canvasWidth = this.canvas.width;
+
+ const referenceDimension = canvasWidth < canvasHeight ? canvasWidth : canvasHeight;
+
+ const heartSize = Math.round(referenceDimension * this.heartSize * .01);
+ const radius = heartSize / 2;
+ const canvasCenterX = Math.round(canvasWidth / 2);
+ const canvasCenterY = Math.round(canvasHeight / 2);
+ const centerX = -radius;
+ const centerY = -radius;
+
+
+ // translate and rotate, adjusting for weight of the heart.
+ context.translate(canvasCenterX, canvasCenterY + radius / 4);
+ context.rotate(-45 * Math.PI / 180);
+
+ // Fill the ventricles of the heart
+ context.fillStyle = `rgb(${this._currentColor.red}, ${this._currentColor.green}, ${this._currentColor.blue})`;
+ context.fillRect(centerX, centerY, heartSize, heartSize);
+
+ // Left atrium
+ context.beginPath();
+ context.arc(centerX + radius, centerY, radius, 0, 2 * Math.PI, false);
+ context.fill();
+ context.closePath();
+
+ // Right atrium
+ context.beginPath();
+ context.arc(centerX + heartSize, centerY + radius, radius, 0, 2 * Math.PI, false);
+ context.fill();
+ context.closePath();
+
+ context.setTransform(1, 0, 0, 1, 0, 0);
+ }
+ };
+
+
+ window.HeartRenderer = HeartRenderer;
+})(window);