UNPKG

15.9 kBJavaScriptView Raw
1import { clear, drawParticle, drawParticlePlugin, drawPlugin, paintBase, paintImage } from "../Utils/CanvasUtils";
2import { deepExtend, isSsr } from "../Utils/Utils";
3import { getStyleFromHsl, getStyleFromRgb, rangeColorToHsl, rangeColorToRgb } from "../Utils/ColorUtils";
4import { generatedAttribute } from "./Utils/Constants";
5function 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}
12export 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}