UNPKG

20.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Particle = void 0;
4const Updater_1 = require("./Particle/Updater");
5const ParticlesOptions_1 = require("../Options/Classes/Particles/ParticlesOptions");
6const Shape_1 = require("../Options/Classes/Particles/Shape/Shape");
7const Enums_1 = require("../Enums");
8const Utils_1 = require("../Utils");
9const Infecter_1 = require("./Particle/Infecter");
10const Mover_1 = require("./Particle/Mover");
11const Vector_1 = require("./Particle/Vector");
12class Particle {
13 constructor(id, container, position, overrideOptions) {
14 var _a, _b, _c, _d, _e, _f, _g, _h, _j;
15 this.id = id;
16 this.container = container;
17 this.links = [];
18 this.fill = true;
19 this.close = true;
20 this.lastPathTime = 0;
21 this.destroyed = false;
22 this.unbreakable = false;
23 this.splitCount = 0;
24 this.misplaced = false;
25 this.loops = {
26 opacity: 0,
27 size: 0,
28 };
29 const pxRatio = container.retina.pixelRatio;
30 const options = container.actualOptions;
31 const particlesOptions = new ParticlesOptions_1.ParticlesOptions();
32 particlesOptions.load(options.particles);
33 const shapeType = particlesOptions.shape.type;
34 const reduceDuplicates = particlesOptions.reduceDuplicates;
35 this.shape = shapeType instanceof Array ? Utils_1.Utils.itemFromArray(shapeType, this.id, reduceDuplicates) : shapeType;
36 if (overrideOptions === null || overrideOptions === void 0 ? void 0 : overrideOptions.shape) {
37 if (overrideOptions.shape.type) {
38 const overrideShapeType = overrideOptions.shape.type;
39 this.shape =
40 overrideShapeType instanceof Array
41 ? Utils_1.Utils.itemFromArray(overrideShapeType, this.id, reduceDuplicates)
42 : overrideShapeType;
43 }
44 const shapeOptions = new Shape_1.Shape();
45 shapeOptions.load(overrideOptions.shape);
46 if (this.shape) {
47 const shapeData = shapeOptions.options[this.shape];
48 if (shapeData) {
49 this.shapeData = Utils_1.Utils.deepExtend({}, shapeData instanceof Array
50 ? Utils_1.Utils.itemFromArray(shapeData, this.id, reduceDuplicates)
51 : shapeData);
52 }
53 }
54 }
55 else {
56 const shapeData = particlesOptions.shape.options[this.shape];
57 if (shapeData) {
58 this.shapeData = Utils_1.Utils.deepExtend({}, shapeData instanceof Array ? Utils_1.Utils.itemFromArray(shapeData, this.id, reduceDuplicates) : shapeData);
59 }
60 }
61 if (overrideOptions !== undefined) {
62 particlesOptions.load(overrideOptions);
63 }
64 if (((_a = this.shapeData) === null || _a === void 0 ? void 0 : _a.particles) !== undefined) {
65 particlesOptions.load((_b = this.shapeData) === null || _b === void 0 ? void 0 : _b.particles);
66 }
67 this.fill = (_d = (_c = this.shapeData) === null || _c === void 0 ? void 0 : _c.fill) !== null && _d !== void 0 ? _d : this.fill;
68 this.close = (_f = (_e = this.shapeData) === null || _e === void 0 ? void 0 : _e.close) !== null && _f !== void 0 ? _f : this.close;
69 this.options = particlesOptions;
70 this.pathDelay = Utils_1.NumberUtils.getValue(this.options.move.path.delay) * 1000;
71 container.retina.initParticle(this);
72 const color = this.options.color;
73 const sizeOptions = this.options.size;
74 const sizeValue = Utils_1.NumberUtils.getValue(sizeOptions) * container.retina.pixelRatio;
75 const randomSize = typeof sizeOptions.random === "boolean" ? sizeOptions.random : sizeOptions.random.enable;
76 this.size = {
77 value: sizeValue,
78 };
79 this.direction = this.options.move.direction;
80 this.bubble = {
81 inRange: false,
82 };
83 this.initialVelocity = this.calculateVelocity();
84 this.velocity = this.initialVelocity.copy();
85 const rotateOptions = this.options.rotate;
86 this.rotate = {
87 value: (Utils_1.NumberUtils.getRangeValue(rotateOptions.value) * Math.PI) / 180,
88 };
89 let rotateDirection = rotateOptions.direction;
90 if (rotateDirection === Enums_1.RotateDirection.random) {
91 const index = Math.floor(Math.random() * 2);
92 rotateDirection = index > 0 ? Enums_1.RotateDirection.counterClockwise : Enums_1.RotateDirection.clockwise;
93 }
94 switch (rotateDirection) {
95 case Enums_1.RotateDirection.counterClockwise:
96 case "counterClockwise":
97 this.rotate.status = Enums_1.AnimationStatus.decreasing;
98 break;
99 case Enums_1.RotateDirection.clockwise:
100 this.rotate.status = Enums_1.AnimationStatus.increasing;
101 break;
102 }
103 const rotateAnimation = this.options.rotate.animation;
104 if (rotateAnimation.enable) {
105 this.rotate.velocity = (rotateAnimation.speed / 360) * container.retina.reduceFactor;
106 if (!rotateAnimation.sync) {
107 this.rotate.velocity *= Math.random();
108 }
109 }
110 const sizeAnimation = this.options.size.animation;
111 if (sizeAnimation.enable) {
112 this.size.status = Enums_1.AnimationStatus.increasing;
113 if (!randomSize) {
114 switch (sizeAnimation.startValue) {
115 case Enums_1.StartValueType.min:
116 this.size.value = sizeAnimation.minimumValue * pxRatio;
117 break;
118 case Enums_1.StartValueType.random:
119 this.size.value = Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(sizeAnimation.minimumValue * pxRatio, this.size.value));
120 break;
121 case Enums_1.StartValueType.max:
122 default:
123 this.size.status = Enums_1.AnimationStatus.decreasing;
124 break;
125 }
126 }
127 this.size.velocity =
128 (((_g = this.sizeAnimationSpeed) !== null && _g !== void 0 ? _g : container.retina.sizeAnimationSpeed) / 100) *
129 container.retina.reduceFactor;
130 if (!sizeAnimation.sync) {
131 this.size.velocity *= Math.random();
132 }
133 }
134 const hslColor = Utils_1.ColorUtils.colorToHsl(color, this.id, reduceDuplicates);
135 if (hslColor) {
136 this.color = {
137 h: {
138 value: hslColor.h,
139 },
140 s: {
141 value: hslColor.s,
142 },
143 l: {
144 value: hslColor.l,
145 },
146 };
147 const colorAnimation = this.options.color.animation;
148 this.setColorAnimation(colorAnimation.h, this.color.h);
149 this.setColorAnimation(colorAnimation.s, this.color.s);
150 this.setColorAnimation(colorAnimation.l, this.color.l);
151 }
152 this.position = this.calcPosition(this.container, position);
153 this.initialPosition = this.position.copy();
154 this.offset = Vector_1.Vector.create(0, 0);
155 const opacityOptions = this.options.opacity;
156 const randomOpacity = typeof opacityOptions.random === "boolean" ? opacityOptions.random : opacityOptions.random.enable;
157 this.opacity = {
158 value: Utils_1.NumberUtils.getValue(opacityOptions),
159 };
160 const opacityAnimation = opacityOptions.animation;
161 if (opacityAnimation.enable) {
162 this.opacity.status = Enums_1.AnimationStatus.increasing;
163 if (!randomOpacity) {
164 switch (opacityAnimation.startValue) {
165 case Enums_1.StartValueType.min:
166 this.opacity.value = opacityAnimation.minimumValue;
167 break;
168 case Enums_1.StartValueType.random:
169 this.opacity.value = Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(opacityAnimation.minimumValue, this.opacity.value));
170 break;
171 case Enums_1.StartValueType.max:
172 default:
173 this.opacity.status = Enums_1.AnimationStatus.decreasing;
174 break;
175 }
176 }
177 this.opacity.velocity = (opacityAnimation.speed / 100) * container.retina.reduceFactor;
178 if (!opacityAnimation.sync) {
179 this.opacity.velocity *= Math.random();
180 }
181 }
182 this.sides = 24;
183 let drawer = container.drawers.get(this.shape);
184 if (!drawer) {
185 drawer = Utils_1.Plugins.getShapeDrawer(this.shape);
186 if (drawer) {
187 container.drawers.set(this.shape, drawer);
188 }
189 }
190 const sideCountFunc = drawer === null || drawer === void 0 ? void 0 : drawer.getSidesCount;
191 if (sideCountFunc) {
192 this.sides = sideCountFunc(this);
193 }
194 const imageShape = this.loadImageShape(container, drawer);
195 if (imageShape) {
196 this.image = imageShape.image;
197 this.fill = imageShape.fill;
198 this.close = imageShape.close;
199 }
200 this.stroke =
201 this.options.stroke instanceof Array
202 ? Utils_1.Utils.itemFromArray(this.options.stroke, this.id, reduceDuplicates)
203 : this.options.stroke;
204 this.strokeWidth = this.stroke.width * container.retina.pixelRatio;
205 const strokeHslColor = (_h = Utils_1.ColorUtils.colorToHsl(this.stroke.color)) !== null && _h !== void 0 ? _h : this.getFillColor();
206 if (strokeHslColor) {
207 this.strokeColor = {
208 h: {
209 value: strokeHslColor.h,
210 },
211 s: {
212 value: strokeHslColor.s,
213 },
214 l: {
215 value: strokeHslColor.l,
216 },
217 };
218 const strokeColorAnimation = (_j = this.stroke.color) === null || _j === void 0 ? void 0 : _j.animation;
219 if (strokeColorAnimation && this.strokeColor) {
220 this.setColorAnimation(strokeColorAnimation.h, this.strokeColor.h);
221 this.setColorAnimation(strokeColorAnimation.s, this.strokeColor.s);
222 this.setColorAnimation(strokeColorAnimation.l, this.strokeColor.l);
223 }
224 }
225 const lifeOptions = particlesOptions.life;
226 this.lifeDelay = container.retina.reduceFactor
227 ? ((Utils_1.NumberUtils.getValue(lifeOptions.delay) * (lifeOptions.delay.sync ? 1 : Math.random())) /
228 container.retina.reduceFactor) *
229 1000
230 : 0;
231 this.lifeDelayTime = 0;
232 this.lifeDuration = container.retina.reduceFactor
233 ? ((Utils_1.NumberUtils.getValue(lifeOptions.duration) * (lifeOptions.duration.sync ? 1 : Math.random())) /
234 container.retina.reduceFactor) *
235 1000
236 : 0;
237 this.lifeTime = 0;
238 this.livesRemaining = particlesOptions.life.count;
239 this.spawning = this.lifeDelay > 0;
240 if (this.lifeDuration <= 0) {
241 this.lifeDuration = -1;
242 }
243 if (this.livesRemaining <= 0) {
244 this.livesRemaining = -1;
245 }
246 this.shadowColor = Utils_1.ColorUtils.colorToRgb(this.options.shadow.color);
247 this.updater = new Updater_1.Updater(container, this);
248 this.infecter = new Infecter_1.Infecter(container);
249 this.mover = new Mover_1.Mover(container, this);
250 if (drawer && drawer.particleInit) {
251 drawer.particleInit(container, this);
252 }
253 }
254 move(delta) {
255 this.mover.move(delta);
256 }
257 update(delta) {
258 this.updater.update(delta);
259 }
260 draw(delta) {
261 this.container.canvas.drawParticle(this, delta);
262 }
263 getPosition() {
264 return this.position.add(this.offset);
265 }
266 getRadius() {
267 return this.bubble.radius || this.size.value;
268 }
269 getMass() {
270 const radius = this.getRadius();
271 return (Math.pow(radius, 2) * Math.PI) / 2;
272 }
273 getFillColor() {
274 var _a;
275 return (_a = this.bubble.color) !== null && _a !== void 0 ? _a : Utils_1.ColorUtils.getHslFromAnimation(this.color);
276 }
277 getStrokeColor() {
278 var _a, _b;
279 return (_b = (_a = this.bubble.color) !== null && _a !== void 0 ? _a : Utils_1.ColorUtils.getHslFromAnimation(this.strokeColor)) !== null && _b !== void 0 ? _b : this.getFillColor();
280 }
281 destroy(override) {
282 this.destroyed = true;
283 this.bubble.inRange = false;
284 this.links = [];
285 if (this.unbreakable) {
286 return;
287 }
288 this.destroyed = true;
289 this.bubble.inRange = false;
290 for (const [, plugin] of this.container.plugins) {
291 if (plugin.particleDestroyed) {
292 plugin.particleDestroyed(this, override);
293 }
294 }
295 if (override) {
296 return;
297 }
298 const destroyOptions = this.options.destroy;
299 if (destroyOptions.mode === Enums_1.DestroyMode.split) {
300 this.split();
301 }
302 }
303 reset() {
304 this.loops.opacity = 0;
305 this.loops.size = 0;
306 }
307 split() {
308 const splitOptions = this.options.destroy.split;
309 if (splitOptions.count >= 0 && this.splitCount++ > splitOptions.count) {
310 return;
311 }
312 const rate = Utils_1.NumberUtils.getRangeValue(splitOptions.rate.value);
313 for (let i = 0; i < rate; i++) {
314 this.container.particles.addSplitParticle(this);
315 }
316 }
317 setColorAnimation(colorAnimation, colorValue) {
318 if (colorAnimation.enable) {
319 colorValue.velocity = (colorAnimation.speed / 100) * this.container.retina.reduceFactor;
320 if (colorAnimation.sync) {
321 return;
322 }
323 colorValue.status = Enums_1.AnimationStatus.increasing;
324 colorValue.velocity *= Math.random();
325 if (colorValue.value) {
326 colorValue.value *= Math.random();
327 }
328 }
329 else {
330 colorValue.velocity = 0;
331 }
332 }
333 calcPosition(container, position, tryCount = 0) {
334 var _a, _b;
335 for (const [, plugin] of container.plugins) {
336 const pluginPos = plugin.particlePosition !== undefined ? plugin.particlePosition(position, this) : undefined;
337 if (pluginPos !== undefined) {
338 return Vector_1.Vector.create(pluginPos.x, pluginPos.y);
339 }
340 }
341 const pos = Vector_1.Vector.create((_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : Math.random() * container.canvas.size.width, (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : Math.random() * container.canvas.size.height);
342 const outMode = this.options.move.outMode;
343 if (Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounce) || Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounceHorizontal)) {
344 if (pos.x > container.canvas.size.width - this.size.value * 2) {
345 pos.x -= this.size.value;
346 }
347 else if (pos.x < this.size.value * 2) {
348 pos.x += this.size.value;
349 }
350 }
351 if (Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounce) || Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounceVertical)) {
352 if (pos.y > container.canvas.size.height - this.size.value * 2) {
353 pos.y -= this.size.value;
354 }
355 else if (pos.y < this.size.value * 2) {
356 pos.y += this.size.value;
357 }
358 }
359 if (this.checkOverlap(pos, tryCount)) {
360 return this.calcPosition(container, undefined, tryCount + 1);
361 }
362 return pos;
363 }
364 checkOverlap(pos, tryCount = 0) {
365 const overlapOptions = this.options.collisions.overlap;
366 if (!overlapOptions.enable) {
367 const retries = overlapOptions.retries;
368 if (retries >= 0 && tryCount > retries) {
369 throw new Error("Particle is overlapping and can't be placed");
370 }
371 let overlaps = false;
372 for (const particle of this.container.particles.array) {
373 if (Utils_1.NumberUtils.getDistance(pos, particle.position) < this.size.value + particle.size.value) {
374 overlaps = true;
375 break;
376 }
377 }
378 return overlaps;
379 }
380 return false;
381 }
382 calculateVelocity() {
383 const baseVelocity = Utils_1.NumberUtils.getParticleBaseVelocity(this.direction);
384 const res = baseVelocity.copy();
385 const moveOptions = this.options.move;
386 let rad;
387 let radOffset = Math.PI / 4;
388 if (typeof moveOptions.angle === "number") {
389 rad = (Math.PI / 180) * moveOptions.angle;
390 }
391 else {
392 rad = (Math.PI / 180) * moveOptions.angle.value;
393 radOffset = (Math.PI / 180) * moveOptions.angle.offset;
394 }
395 const range = {
396 left: Math.sin(radOffset + rad / 2) - Math.sin(radOffset - rad / 2),
397 right: Math.cos(radOffset + rad / 2) - Math.cos(radOffset - rad / 2),
398 };
399 if (!moveOptions.straight || moveOptions.random) {
400 res.x += Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(range.left, range.right)) / 2;
401 res.y += Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(range.left, range.right)) / 2;
402 }
403 return res;
404 }
405 loadImageShape(container, drawer) {
406 var _a, _b, _c, _d, _e;
407 if (!(this.shape === Enums_1.ShapeType.image || this.shape === Enums_1.ShapeType.images)) {
408 return;
409 }
410 const imageDrawer = drawer;
411 const images = imageDrawer.getImages(container).images;
412 const imageData = this.shapeData;
413 const image = (_a = images.find((t) => t.source === imageData.src)) !== null && _a !== void 0 ? _a : images[0];
414 const color = this.getFillColor();
415 let imageRes;
416 if (!image) {
417 return;
418 }
419 if (image.svgData !== undefined && imageData.replaceColor && color) {
420 const svgColoredData = Utils_1.ColorUtils.replaceColorSvg(image, color, this.opacity.value);
421 const svg = new Blob([svgColoredData], { type: "image/svg+xml" });
422 const domUrl = URL || window.URL || window.webkitURL || window;
423 const url = domUrl.createObjectURL(svg);
424 const img = new Image();
425 imageRes = {
426 data: image,
427 loaded: false,
428 ratio: imageData.width / imageData.height,
429 replaceColor: (_b = imageData.replaceColor) !== null && _b !== void 0 ? _b : imageData.replace_color,
430 source: imageData.src,
431 };
432 img.addEventListener("load", () => {
433 if (this.image) {
434 this.image.loaded = true;
435 image.element = img;
436 }
437 domUrl.revokeObjectURL(url);
438 });
439 img.addEventListener("error", () => {
440 domUrl.revokeObjectURL(url);
441 Utils_1.Utils.loadImage(imageData.src).then((img2) => {
442 if (this.image && img2) {
443 image.element = img2.element;
444 this.image.loaded = true;
445 }
446 });
447 });
448 img.src = url;
449 }
450 else {
451 imageRes = {
452 data: image,
453 loaded: true,
454 ratio: imageData.width / imageData.height,
455 replaceColor: (_c = imageData.replaceColor) !== null && _c !== void 0 ? _c : imageData.replace_color,
456 source: imageData.src,
457 };
458 }
459 if (!imageRes.ratio) {
460 imageRes.ratio = 1;
461 }
462 const fill = (_d = imageData.fill) !== null && _d !== void 0 ? _d : this.fill;
463 const close = (_e = imageData.close) !== null && _e !== void 0 ? _e : this.close;
464 return {
465 image: imageRes,
466 fill,
467 close,
468 };
469 }
470}
471exports.Particle = Particle;