]> git.r.bdr.sh - rbdr/heart/blob - js/lib/heart_renderer.js
78ca182e96f4a38c061685ea66c255a488dc154c
[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._following = null; // The status of mouse follow.
55 this._center = null;
56 this._animating = false; // The status of the animation.
57 this._previousFrameTime = Date.now(); // The timestamp of the last frame for fps control
58 this._currentColor = { // The current color that will be painted
59 red: 100,
60 blue: 0,
61 green: 50
62 };
63
64 this.startFollowingMouse();
65
66 Object.assign(this, config);
67 }
68
69 /**
70 * Attaches the canvas to an HTML element
71 *
72 * @memberof HeartRenderer
73 * @function render
74 * @instance
75 * @param {HTMLElement} element the element where we will attach our canvas
76 */
77 render(element) {
78
79 element.appendChild(this.canvas);
80 this.resize();
81 }
82
83 /**
84 * Resizes the canvas
85 *
86 * @memberof HeartRenderer
87 * @function render
88 * @instance
89 */
90 resize() {
91
92 if (this.canvas.parentElement) {
93 this.canvas.width = this.canvas.parentElement.offsetWidth;
94 this.canvas.height = this.canvas.parentElement.offsetHeight;
95 }
96 }
97
98 /**
99 * Follows the mouse
100 *
101 * @memberof HeartRenderer
102 * @function startFollowingMouse
103 * @instance
104 */
105 startFollowingMouse() {
106
107 if (!this._following) {
108 console.log('Start Following Mouse');
109 this._following = this._setCenterFromMouse.bind(this);
110 this.canvas.addEventListener('mousemove', this._following);
111 }
112 }
113
114 /**
115 * Stop following the mouse
116 *
117 * @memberof HeartRenderer
118 * @function stopFollowingMouse
119 * @instance
120 */
121 stopFollowingMouse() {
122
123 if (this._following) {
124 console.log('Stop Following Mouse');
125 this.canvas.removeEventListener('mouseover', this._following);
126 this._following = null;
127 this._center = null;
128 }
129 }
130
131 /**
132 * Gets the context from the current canvas and starts the animation process
133 *
134 * @memberof HeartRenderer
135 * @function activate
136 * @instance
137 */
138 activate() {
139
140 const context = this.canvas.getContext('2d');
141 this._startAnimating(context);
142 }
143
144 /**
145 * Stops the animation process
146 *
147 * @memberof HeartRenderer
148 * @function deactivate
149 * @instance
150 */
151 deactivate() {
152
153 this._stopAnimating();
154 }
155
156 // Starts the animation loop
157 _startAnimating(context) {
158
159 this._frameDuration = 1000 / this.fps;
160 this._animating = true;
161
162 this._animate(context);
163 }
164
165 // Stops the animation on the next frame.
166 _stopAnimating() {
167
168 this._animating = false;
169 }
170
171 // Runs the animation step controlling the FPS
172 _animate(context) {
173
174 if (!this._animating) {
175 return;
176 }
177
178 window.requestAnimationFrame(this._animate.bind(this, context));
179
180 const currentFrameTime = Date.now();
181 const delta = currentFrameTime - this._previousFrameTime;
182
183 if (delta > this._frameDuration) {
184 this._previousFrameTime = Date.now();
185 this._animateStep(context, delta);
186 }
187 }
188
189 // The actual animation processing function.
190 _animateStep(context, delta) {
191
192 this._updateColor(delta);
193 this._drawHeart(context, delta);
194 }
195
196 // Updates the current color
197 _updateColor(delta) {
198
199 this._currentColor.red = Math.round(this._currentColor.red + delta * kRedSpeed) % kColorIteratorLimit;
200 this._currentColor.green = Math.round(this._currentColor.green + delta * kGreenSpeed) % kColorIteratorLimit;
201 this._currentColor.blue = Math.round(this._currentColor.blue + delta * kBlueSpeed) % kColorIteratorLimit;
202 }
203
204 // Draws a heart
205 _drawHeart(context, delta) {
206
207 const canvasHeight = this.canvas.height;
208 const canvasWidth = this.canvas.width;
209
210 const referenceDimension = canvasWidth < canvasHeight ? canvasWidth : canvasHeight;
211
212 const heartSize = Math.round(referenceDimension * this.heartSize * .01);
213 const radius = heartSize / 2;
214 let canvasCenterX = this._center ? this._center.x : Math.round(canvasWidth / 2);
215 let canvasCenterY = this._center ? this._center.y : Math.round(canvasHeight / 2);
216 const centerX = -radius;
217 const centerY = -radius;
218
219
220 // translate and rotate, adjusting for weight of the heart.
221 context.translate(canvasCenterX, canvasCenterY + radius / 4);
222 context.rotate(-45 * Math.PI / 180);
223
224 // Fill the ventricles of the heart
225 context.fillStyle = `rgb(${this._currentColor.red}, ${this._currentColor.green}, ${this._currentColor.blue})`;
226 context.fillRect(centerX, centerY, heartSize, heartSize);
227
228 // Left atrium
229 context.beginPath();
230 context.arc(centerX + radius, centerY, radius, 0, 2 * Math.PI, false);
231 context.fill();
232 context.closePath();
233
234 // Right atrium
235 context.beginPath();
236 context.arc(centerX + heartSize, centerY + radius, radius, 0, 2 * Math.PI, false);
237 context.fill();
238 context.closePath();
239
240 context.setTransform(1, 0, 0, 1, 0, 0);
241 }
242
243 // Sets the center from mouse
244 _setCenterFromMouse (event) {
245 this._center = this._center || {};
246
247 console.log('tracking');
248
249 this._center.x = event.offsetX;
250 this._center.y = event.offsetY;
251 }
252 };
253
254
255 window.HeartRenderer = HeartRenderer;
256 })(window);