]> git.r.bdr.sh - rbdr/heart/blob - js/lib/heart_renderer.js
fd4adf6452e511e7090b1b0e7519dd467ef6829b
[rbdr/heart] / js / lib / heart_renderer.js
1 'use strict';
2
3 ((window) => {
4
5 const kColorIteratorLimit = 256;
6 const kRedSpeed = 0.1;
7 const kGreenSpeed = 0.2;
8 const kBlueSpeed = 0.15;
9
10 /**
11 * Renders a Heart, has its own canvas, which will be placed in the element
12 * set by calling #render.
13 *
14 * @class HeartRenderer
15 * @param {object} config configuration, extends any member of the renderer
16 */
17 const HeartRenderer = class HeartRenderer {
18
19 constructor(config) {
20
21 /**
22 * The instance of the heart renderer being used
23 *
24 * @memberof HeartRenderer
25 * @instance
26 * @name canvas
27 * @type HTMLCanvasElement
28 * @default A brand new canvas
29 */
30 this.canvas = window.document.createElement('canvas');
31
32 /**
33 * The maximum fps that will be used
34 *
35 * @memberof HeartRenderer
36 * @instance
37 * @name fps
38 * @type Number
39 * @default 60
40 */
41 this.fps = 60;
42
43 /**
44 * The size of the heart as a percentage of the canvas smallest dimension
45 *
46 * @memberof HeartRenderer
47 * @instance
48 * @name heartSize
49 * @type Number
50 * @default 50
51 */
52 this.heartSize = 50;
53
54 this._animating = false; // The status of the animation.
55 this._previousFrameTime = Date.now(); // The timestamp of the last frame for fps control
56 this._currentColor = { // The current color that will be painted
57 red: 100,
58 blue: 0,
59 green: 50
60 };
61
62 Object.assign(this, config);
63 }
64
65 /**
66 * Attaches the canvas to an HTML element
67 *
68 * @memberof HeartRenderer
69 * @function render
70 * @instance
71 * @param {HTMLElement} element the element where we will attach our canvas
72 */
73 render(element) {
74
75 element.appendChild(this.canvas);
76 this.resize();
77 }
78
79 /**
80 * Resizes the canvas
81 *
82 * @memberof HeartRenderer
83 * @function render
84 * @instance
85 * @param {HTMLElement} element the element where we will attach our canvas
86 */
87 resize() {
88
89 if (this.canvas.parentElement) {
90 this.canvas.width = this.canvas.parentElement.offsetWidth;
91 this.canvas.height = this.canvas.parentElement.offsetHeight;
92 }
93 }
94
95 /**
96 * Gets the context from the current canvas and starts the animation process
97 *
98 * @memberof HeartRenderer
99 * @function activate
100 * @instance
101 */
102 activate() {
103
104 const context = this.canvas.getContext('2d');
105 this._startAnimating(context);
106 }
107
108 /**
109 * Stops the animation process
110 *
111 * @memberof HeartRenderer
112 * @function deactivate
113 * @instance
114 */
115 deactivate() {
116
117 this._stopAnimating();
118 }
119
120 // Starts the animation loop
121 _startAnimating(context) {
122
123 this._frameDuration = 1000 / this.fps;
124 this._animating = true;
125
126 this._animate(context);
127 }
128
129 // Stops the animation on the next frame.
130 _stopAnimating() {
131
132 this._animating = false;
133 }
134
135 // Runs the animation step controlling the FPS
136 _animate(context) {
137
138 if (!this._animating) {
139 return;
140 }
141
142 window.requestAnimationFrame(this._animate.bind(this, context));
143
144 const currentFrameTime = Date.now();
145 const delta = currentFrameTime - this._previousFrameTime;
146
147 if (delta > this._frameDuration) {
148 this._previousFrameTime = Date.now();
149 this._animateStep(context, delta);
150 }
151 }
152
153 // The actual animation processing function.
154 _animateStep(context, delta) {
155
156 this._updateColor(delta);
157 this._drawHeart(context, delta);
158 }
159
160 // Updates the current color
161 _updateColor(delta) {
162
163 this._currentColor.red = Math.round(this._currentColor.red + delta * kRedSpeed) % kColorIteratorLimit;
164 this._currentColor.green = Math.round(this._currentColor.green + delta * kGreenSpeed) % kColorIteratorLimit;
165 this._currentColor.blue = Math.round(this._currentColor.blue + delta * kBlueSpeed) % kColorIteratorLimit;
166 }
167
168 // Draws a heart
169 _drawHeart(context, delta) {
170
171 const canvasHeight = this.canvas.height;
172 const canvasWidth = this.canvas.width;
173
174 const referenceDimension = canvasWidth < canvasHeight ? canvasWidth : canvasHeight;
175
176 const heartSize = Math.round(referenceDimension * this.heartSize * .01);
177 const radius = heartSize / 2;
178 const canvasCenterX = Math.round(canvasWidth / 2);
179 const canvasCenterY = Math.round(canvasHeight / 2);
180 const centerX = -radius;
181 const centerY = -radius;
182
183
184 // translate and rotate, adjusting for weight of the heart.
185 context.translate(canvasCenterX, canvasCenterY + radius / 4);
186 context.rotate(-45 * Math.PI / 180);
187
188 // Fill the ventricles of the heart
189 context.fillStyle = `rgb(${this._currentColor.red}, ${this._currentColor.green}, ${this._currentColor.blue})`;
190 context.fillRect(centerX, centerY, heartSize, heartSize);
191
192 // Left atrium
193 context.beginPath();
194 context.arc(centerX + radius, centerY, radius, 0, 2 * Math.PI, false);
195 context.fill();
196 context.closePath();
197
198 // Right atrium
199 context.beginPath();
200 context.arc(centerX + heartSize, centerY + radius, radius, 0, 2 * Math.PI, false);
201 context.fill();
202 context.closePath();
203
204 context.setTransform(1, 0, 0, 1, 0, 0);
205 }
206 };
207
208
209 window.HeartRenderer = HeartRenderer;
210 })(window);