aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorBen Beltran <ben@nsovocal.com>2016-11-10 00:52:24 -0600
committerBen Beltran <ben@nsovocal.com>2016-11-10 00:52:24 -0600
commita1c6d8138d774cf9b9e6eee7950e2e4ae18376aa (patch)
treea2c8846e54ae73281af05327915eef728e9ac68c /js
parente8f6a707779aa341582b87c2a5ed345c7dd9069d (diff)
parentaab4b2e7f48c73ec0a0ba2f609748c24b7e84fdc (diff)
Merge branch 'release/1.0.0'
Diffstat (limited to 'js')
-rw-r--r--js/app.js33
-rw-r--r--js/lib/heart_renderer.js210
2 files changed, 243 insertions, 0 deletions
diff --git a/js/app.js b/js/app.js
new file mode 100644
index 0000000..12065b2
--- /dev/null
+++ b/js/app.js
@@ -0,0 +1,33 @@
+'use strict';
+
+// Sets up the application
+
+((window) => {
+
+ const internals = {};
+
+ internals.onLoad = () => {
+
+ const mainElement = window.document.getElementById('heart-app-entry-point');
+ const heartRenderer = new HeartRenderer();
+
+ heartRenderer.render(mainElement);
+ heartRenderer.activate();
+
+ window.addEventListener('resize', heartRenderer.resize.bind(heartRenderer));
+
+ /**
+ * Exported global object. This will contain the instance of the heart
+ * renderer being used. It is set up on load.
+ *
+ * @name App
+ * @type Object
+ * @property {HeartRenderer} heartRenderer The instance of the heart renderer being used.
+ */
+ window.App = {
+ heartRenderer
+ };
+ };
+
+ window.addEventListener('load', internals.onLoad);
+})(window);
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);