]>
Commit | Line | Data |
---|---|---|
910278aa BB |
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 | |
f895bdac | 28 | * @default A brand new canvas |
910278aa BB |
29 | */ |
30 | this.canvas = window.document.createElement('canvas'); | |
910278aa BB |
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 | ||
f895bdac BB |
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 | ||
910278aa BB |
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); | |
f895bdac BB |
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 | } | |
910278aa BB |
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 | ||
f895bdac BB |
156 | this._updateColor(delta); |
157 | this._drawHeart(context, delta); | |
158 | } | |
159 | ||
160 | // Updates the current color | |
161 | _updateColor(delta) { | |
162 | ||
910278aa BB |
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; | |
f895bdac BB |
166 | } |
167 | ||
168 | // Draws a heart | |
169 | _drawHeart(context, delta) { | |
170 | ||
171 | const canvasHeight = this.canvas.height; | |
172 | const canvasWidth = this.canvas.width; | |
910278aa | 173 | |
f895bdac BB |
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 | |
910278aa | 189 | context.fillStyle = `rgb(${this._currentColor.red}, ${this._currentColor.green}, ${this._currentColor.blue})`; |
f895bdac BB |
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); | |
910278aa BB |
205 | } |
206 | }; | |
207 | ||
208 | ||
209 | window.HeartRenderer = HeartRenderer; | |
210 | })(window); |