1 | import { clear, drawParticle, drawParticlePlugin, drawPlugin, paintBase, paintImage } from "../Utils/CanvasUtils";
|
2 | import { deepExtend, isSsr } from "../Utils/Utils";
|
3 | import { getStyleFromHsl, getStyleFromRgb, rangeColorToHsl, rangeColorToRgb } from "../Utils/ColorUtils";
|
4 | import { generatedAttribute } from "./Utils/Constants";
|
5 | function setTransformValue(factor, newFactor, key) {
|
6 | var _a;
|
7 | const newValue = newFactor[key];
|
8 | if (newValue !== undefined) {
|
9 | factor[key] = ((_a = factor[key]) !== null && _a !== void 0 ? _a : 1) * newValue;
|
10 | }
|
11 | }
|
12 | export class Canvas {
|
13 | constructor(container) {
|
14 | this.container = container;
|
15 | this.size = {
|
16 | height: 0,
|
17 | width: 0,
|
18 | };
|
19 | this._context = null;
|
20 | this._generated = false;
|
21 | this._preDrawUpdaters = [];
|
22 | this._postDrawUpdaters = [];
|
23 | this._resizePlugins = [];
|
24 | this._colorPlugins = [];
|
25 | this._mutationObserver =
|
26 | !isSsr() && typeof MutationObserver !== "undefined"
|
27 | ? new MutationObserver((records) => {
|
28 | for (const record of records) {
|
29 | if (record.type === "attributes" && record.attributeName === "style") {
|
30 | this._repairStyle();
|
31 | }
|
32 | }
|
33 | })
|
34 | : undefined;
|
35 | }
|
36 | get _fullScreen() {
|
37 | return this.container.actualOptions.fullScreen.enable;
|
38 | }
|
39 | clear() {
|
40 | const options = this.container.actualOptions, trail = options.particles.move.trail, trailFill = this._trailFill;
|
41 | if (options.backgroundMask.enable) {
|
42 | this.paint();
|
43 | }
|
44 | else if (trail.enable && trail.length > 0 && trailFill) {
|
45 | if (trailFill.color) {
|
46 | this._paintBase(getStyleFromRgb(trailFill.color, trailFill.opacity));
|
47 | }
|
48 | else if (trailFill.image) {
|
49 | this._paintImage(trailFill.image, trailFill.opacity);
|
50 | }
|
51 | }
|
52 | else {
|
53 | this.draw((ctx) => {
|
54 | clear(ctx, this.size);
|
55 | });
|
56 | }
|
57 | }
|
58 | destroy() {
|
59 | var _a, _b;
|
60 | (_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
61 | if (this._generated) {
|
62 | (_b = this.element) === null || _b === void 0 ? void 0 : _b.remove();
|
63 | }
|
64 | else {
|
65 | this._resetOriginalStyle();
|
66 | }
|
67 | this.draw((ctx) => {
|
68 | clear(ctx, this.size);
|
69 | });
|
70 | this._preDrawUpdaters = [];
|
71 | this._postDrawUpdaters = [];
|
72 | this._resizePlugins = [];
|
73 | this._colorPlugins = [];
|
74 | }
|
75 | draw(cb) {
|
76 | if (!this._context) {
|
77 | return;
|
78 | }
|
79 | return cb(this._context);
|
80 | }
|
81 | drawParticle(particle, delta) {
|
82 | var _a;
|
83 | if (particle.spawning || particle.destroyed) {
|
84 | return;
|
85 | }
|
86 | const radius = particle.getRadius();
|
87 | if (radius <= 0) {
|
88 | return;
|
89 | }
|
90 | const pfColor = particle.getFillColor(), psColor = (_a = particle.getStrokeColor()) !== null && _a !== void 0 ? _a : pfColor;
|
91 | let [fColor, sColor] = this._getPluginParticleColors(particle);
|
92 | if (!fColor) {
|
93 | fColor = pfColor;
|
94 | }
|
95 | if (!sColor) {
|
96 | sColor = psColor;
|
97 | }
|
98 | if (!fColor && !sColor) {
|
99 | return;
|
100 | }
|
101 | this.draw((ctx) => {
|
102 | var _a, _b, _c, _d;
|
103 | const options = this.container.actualOptions, zIndexOptions = particle.options.zIndex, zOpacityFactor = (1 - particle.zIndexFactor) ** zIndexOptions.opacityRate, opacity = (_c = (_a = particle.bubble.opacity) !== null && _a !== void 0 ? _a : (_b = particle.opacity) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 1, strokeOpacity = (_d = particle.strokeOpacity) !== null && _d !== void 0 ? _d : opacity, zOpacity = opacity * zOpacityFactor, zStrokeOpacity = strokeOpacity * zOpacityFactor, transform = {}, colorStyles = {
|
104 | fill: fColor ? getStyleFromHsl(fColor, zOpacity) : undefined,
|
105 | };
|
106 | colorStyles.stroke = sColor ? getStyleFromHsl(sColor, zStrokeOpacity) : colorStyles.fill;
|
107 | this._applyPreDrawUpdaters(ctx, particle, radius, zOpacity, colorStyles, transform);
|
108 | drawParticle({
|
109 | container: this.container,
|
110 | context: ctx,
|
111 | particle,
|
112 | delta,
|
113 | colorStyles,
|
114 | backgroundMask: options.backgroundMask.enable,
|
115 | composite: options.backgroundMask.composite,
|
116 | radius: radius * (1 - particle.zIndexFactor) ** zIndexOptions.sizeRate,
|
117 | opacity: zOpacity,
|
118 | shadow: particle.options.shadow,
|
119 | transform,
|
120 | });
|
121 | this._applyPostDrawUpdaters(particle);
|
122 | });
|
123 | }
|
124 | drawParticlePlugin(plugin, particle, delta) {
|
125 | this.draw((ctx) => {
|
126 | drawParticlePlugin(ctx, plugin, particle, delta);
|
127 | });
|
128 | }
|
129 | drawPlugin(plugin, delta) {
|
130 | this.draw((ctx) => {
|
131 | drawPlugin(ctx, plugin, delta);
|
132 | });
|
133 | }
|
134 | async init() {
|
135 | var _a;
|
136 | this.resize();
|
137 | this._initStyle();
|
138 | this._initCover();
|
139 | try {
|
140 | await this._initTrail();
|
141 | }
|
142 | catch (e) {
|
143 | console.error(e);
|
144 | }
|
145 | this.initBackground();
|
146 | if (this.element) {
|
147 | (_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.observe(this.element, { attributes: true });
|
148 | }
|
149 | this.initUpdaters();
|
150 | this.initPlugins();
|
151 | this.paint();
|
152 | }
|
153 | initBackground() {
|
154 | const options = this.container.actualOptions, background = options.background, element = this.element, elementStyle = element === null || element === void 0 ? void 0 : element.style;
|
155 | if (!elementStyle) {
|
156 | return;
|
157 | }
|
158 | if (background.color) {
|
159 | const color = rangeColorToRgb(background.color);
|
160 | elementStyle.backgroundColor = color ? getStyleFromRgb(color, background.opacity) : "";
|
161 | }
|
162 | else {
|
163 | elementStyle.backgroundColor = "";
|
164 | }
|
165 | elementStyle.backgroundImage = background.image || "";
|
166 | elementStyle.backgroundPosition = background.position || "";
|
167 | elementStyle.backgroundRepeat = background.repeat || "";
|
168 | elementStyle.backgroundSize = background.size || "";
|
169 | }
|
170 | initPlugins() {
|
171 | this._resizePlugins = [];
|
172 | for (const [, plugin] of this.container.plugins) {
|
173 | if (plugin.resize) {
|
174 | this._resizePlugins.push(plugin);
|
175 | }
|
176 | if (plugin.particleFillColor || plugin.particleStrokeColor) {
|
177 | this._colorPlugins.push(plugin);
|
178 | }
|
179 | }
|
180 | }
|
181 | initUpdaters() {
|
182 | this._preDrawUpdaters = [];
|
183 | this._postDrawUpdaters = [];
|
184 | for (const updater of this.container.particles.updaters) {
|
185 | if (updater.afterDraw) {
|
186 | this._postDrawUpdaters.push(updater);
|
187 | }
|
188 | if (updater.getColorStyles || updater.getTransformValues || updater.beforeDraw) {
|
189 | this._preDrawUpdaters.push(updater);
|
190 | }
|
191 | }
|
192 | }
|
193 | loadCanvas(canvas) {
|
194 | var _a, _b;
|
195 | if (this._generated) {
|
196 | (_a = this.element) === null || _a === void 0 ? void 0 : _a.remove();
|
197 | }
|
198 | this._generated =
|
199 | canvas.dataset && generatedAttribute in canvas.dataset
|
200 | ? canvas.dataset[generatedAttribute] === "true"
|
201 | : this._generated;
|
202 | this.element = canvas;
|
203 | this.element.ariaHidden = "true";
|
204 | this._originalStyle = deepExtend({}, this.element.style);
|
205 | this.size.height = canvas.offsetHeight;
|
206 | this.size.width = canvas.offsetWidth;
|
207 | this._context = this.element.getContext("2d");
|
208 | (_b = this._mutationObserver) === null || _b === void 0 ? void 0 : _b.observe(this.element, { attributes: true });
|
209 | this.container.retina.init();
|
210 | this.initBackground();
|
211 | }
|
212 | paint() {
|
213 | const options = this.container.actualOptions;
|
214 | this.draw((ctx) => {
|
215 | if (options.backgroundMask.enable && options.backgroundMask.cover) {
|
216 | clear(ctx, this.size);
|
217 | this._paintBase(this._coverColorStyle);
|
218 | }
|
219 | else {
|
220 | this._paintBase();
|
221 | }
|
222 | });
|
223 | }
|
224 | resize() {
|
225 | if (!this.element) {
|
226 | return;
|
227 | }
|
228 | const container = this.container, pxRatio = container.retina.pixelRatio, size = container.canvas.size, newSize = {
|
229 | width: this.element.offsetWidth * pxRatio,
|
230 | height: this.element.offsetHeight * pxRatio,
|
231 | };
|
232 | if (newSize.height === size.height &&
|
233 | newSize.width === size.width &&
|
234 | newSize.height === this.element.height &&
|
235 | newSize.width === this.element.width) {
|
236 | return;
|
237 | }
|
238 | const oldSize = Object.assign({}, size);
|
239 | this.element.width = size.width = this.element.offsetWidth * pxRatio;
|
240 | this.element.height = size.height = this.element.offsetHeight * pxRatio;
|
241 | if (this.container.started) {
|
242 | this.resizeFactor = {
|
243 | width: size.width / oldSize.width,
|
244 | height: size.height / oldSize.height,
|
245 | };
|
246 | }
|
247 | }
|
248 | async windowResize() {
|
249 | if (!this.element) {
|
250 | return;
|
251 | }
|
252 | this.resize();
|
253 | const container = this.container, needsRefresh = container.updateActualOptions();
|
254 | container.particles.setDensity();
|
255 | this._applyResizePlugins();
|
256 | if (needsRefresh) {
|
257 | await container.refresh();
|
258 | }
|
259 | }
|
260 | _applyPostDrawUpdaters(particle) {
|
261 | var _a;
|
262 | for (const updater of this._postDrawUpdaters) {
|
263 | (_a = updater.afterDraw) === null || _a === void 0 ? void 0 : _a.call(updater, particle);
|
264 | }
|
265 | }
|
266 | _applyPreDrawUpdaters(ctx, particle, radius, zOpacity, colorStyles, transform) {
|
267 | var _a;
|
268 | for (const updater of this._preDrawUpdaters) {
|
269 | if (updater.getColorStyles) {
|
270 | const { fill, stroke } = updater.getColorStyles(particle, ctx, radius, zOpacity);
|
271 | if (fill) {
|
272 | colorStyles.fill = fill;
|
273 | }
|
274 | if (stroke) {
|
275 | colorStyles.stroke = stroke;
|
276 | }
|
277 | }
|
278 | if (updater.getTransformValues) {
|
279 | const updaterTransform = updater.getTransformValues(particle);
|
280 | for (const key in updaterTransform) {
|
281 | setTransformValue(transform, updaterTransform, key);
|
282 | }
|
283 | }
|
284 | (_a = updater.beforeDraw) === null || _a === void 0 ? void 0 : _a.call(updater, particle);
|
285 | }
|
286 | }
|
287 | _applyResizePlugins() {
|
288 | for (const plugin of this._resizePlugins) {
|
289 | if (plugin.resize) {
|
290 | plugin.resize();
|
291 | }
|
292 | }
|
293 | }
|
294 | _getPluginParticleColors(particle) {
|
295 | let fColor, sColor;
|
296 | for (const plugin of this._colorPlugins) {
|
297 | if (!fColor && plugin.particleFillColor) {
|
298 | fColor = rangeColorToHsl(plugin.particleFillColor(particle));
|
299 | }
|
300 | if (!sColor && plugin.particleStrokeColor) {
|
301 | sColor = rangeColorToHsl(plugin.particleStrokeColor(particle));
|
302 | }
|
303 | if (fColor && sColor) {
|
304 | break;
|
305 | }
|
306 | }
|
307 | return [fColor, sColor];
|
308 | }
|
309 | _initCover() {
|
310 | const options = this.container.actualOptions, cover = options.backgroundMask.cover, color = cover.color, coverRgb = rangeColorToRgb(color);
|
311 | if (coverRgb) {
|
312 | const coverColor = {
|
313 | r: coverRgb.r,
|
314 | g: coverRgb.g,
|
315 | b: coverRgb.b,
|
316 | a: cover.opacity,
|
317 | };
|
318 | this._coverColorStyle = getStyleFromRgb(coverColor, coverColor.a);
|
319 | }
|
320 | }
|
321 | _initStyle() {
|
322 | const element = this.element, options = this.container.actualOptions;
|
323 | if (!element) {
|
324 | return;
|
325 | }
|
326 | if (this._fullScreen) {
|
327 | this._originalStyle = deepExtend({}, element.style);
|
328 | this._setFullScreenStyle();
|
329 | }
|
330 | else {
|
331 | this._resetOriginalStyle();
|
332 | }
|
333 | for (const key in options.style) {
|
334 | if (!key || !options.style) {
|
335 | continue;
|
336 | }
|
337 | const value = options.style[key];
|
338 | if (!value) {
|
339 | continue;
|
340 | }
|
341 | element.style.setProperty(key, value, "important");
|
342 | }
|
343 | }
|
344 | async _initTrail() {
|
345 | const options = this.container.actualOptions, trail = options.particles.move.trail, trailFill = trail.fill;
|
346 | if (!trail.enable) {
|
347 | return;
|
348 | }
|
349 | if (trailFill.color) {
|
350 | const fillColor = rangeColorToRgb(trailFill.color);
|
351 | if (!fillColor) {
|
352 | return;
|
353 | }
|
354 | const trail = options.particles.move.trail;
|
355 | this._trailFill = {
|
356 | color: Object.assign({}, fillColor),
|
357 | opacity: 1 / trail.length,
|
358 | };
|
359 | }
|
360 | else {
|
361 | await new Promise((resolve, reject) => {
|
362 | if (!trailFill.image) {
|
363 | return;
|
364 | }
|
365 | const img = document.createElement("img");
|
366 | img.addEventListener("load", () => {
|
367 | this._trailFill = {
|
368 | image: img,
|
369 | opacity: 1 / trail.length,
|
370 | };
|
371 | resolve();
|
372 | });
|
373 | img.addEventListener("error", (evt) => {
|
374 | reject(evt.error);
|
375 | });
|
376 | img.src = trailFill.image;
|
377 | });
|
378 | }
|
379 | }
|
380 | _paintBase(baseColor) {
|
381 | this.draw((ctx) => {
|
382 | paintBase(ctx, this.size, baseColor);
|
383 | });
|
384 | }
|
385 | _paintImage(image, opacity) {
|
386 | this.draw((ctx) => {
|
387 | paintImage(ctx, this.size, image, opacity);
|
388 | });
|
389 | }
|
390 | _repairStyle() {
|
391 | var _a, _b;
|
392 | const element = this.element;
|
393 | if (!element) {
|
394 | return;
|
395 | }
|
396 | (_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
397 | this._initStyle();
|
398 | this.initBackground();
|
399 | (_b = this._mutationObserver) === null || _b === void 0 ? void 0 : _b.observe(element, { attributes: true });
|
400 | }
|
401 | _resetOriginalStyle() {
|
402 | const element = this.element, originalStyle = this._originalStyle;
|
403 | if (!(element && originalStyle)) {
|
404 | return;
|
405 | }
|
406 | element.style.position = originalStyle.position;
|
407 | element.style.zIndex = originalStyle.zIndex;
|
408 | element.style.top = originalStyle.top;
|
409 | element.style.left = originalStyle.left;
|
410 | element.style.width = originalStyle.width;
|
411 | element.style.height = originalStyle.height;
|
412 | }
|
413 | _setFullScreenStyle() {
|
414 | const element = this.element;
|
415 | if (!element) {
|
416 | return;
|
417 | }
|
418 | const priority = "important";
|
419 | element.style.setProperty("position", "fixed", priority);
|
420 | element.style.setProperty("z-index", this.container.actualOptions.fullScreen.zIndex.toString(10), priority);
|
421 | element.style.setProperty("top", "0", priority);
|
422 | element.style.setProperty("left", "0", priority);
|
423 | element.style.setProperty("width", "100%", priority);
|
424 | element.style.setProperty("height", "100%", priority);
|
425 | }
|
426 | }
|