diff options
| author | Ben Beltran <ben@nsovocal.com> | 2016-11-10 00:52:24 -0600 |
|---|---|---|
| committer | Ben Beltran <ben@nsovocal.com> | 2016-11-10 00:52:24 -0600 |
| commit | a1c6d8138d774cf9b9e6eee7950e2e4ae18376aa (patch) | |
| tree | a2c8846e54ae73281af05327915eef728e9ac68c /js | |
| parent | e8f6a707779aa341582b87c2a5ed345c7dd9069d (diff) | |
| parent | aab4b2e7f48c73ec0a0ba2f609748c24b7e84fdc (diff) | |
Merge branch 'release/1.0.0'
Diffstat (limited to 'js')
| -rw-r--r-- | js/app.js | 33 | ||||
| -rw-r--r-- | js/lib/heart_renderer.js | 210 |
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); |