diff options
| author | Ben Beltran <ben@nsovocal.com> | 2016-11-10 00:49:23 -0600 |
|---|---|---|
| committer | Ben Beltran <ben@nsovocal.com> | 2016-11-10 00:49:23 -0600 |
| commit | fff45e3b9d371851d4e8c3e1655a5bf1509080f2 (patch) | |
| tree | 418234d8e6ddd394794639da197d14399fd841ed /js/lib | |
| parent | e8f6a707779aa341582b87c2a5ed345c7dd9069d (diff) | |
| parent | f28c4a44ce8403ea5eba9d49d3d46b7b8dae1d42 (diff) | |
Merge branch 'feature/draw-the-heart' into develop
Diffstat (limited to 'js/lib')
| -rw-r--r-- | js/lib/heart_renderer.js | 210 |
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); |