@basmilius/sparkle 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +306 -459
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1106 -848
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/src/aurora/index.ts +9 -3
- package/src/aurora/layer.ts +57 -29
- package/src/balloons/index.ts +9 -3
- package/src/balloons/layer.ts +50 -19
- package/src/bubbles/index.ts +9 -3
- package/src/bubbles/layer.ts +30 -17
- package/src/canvas.ts +12 -0
- package/src/color.ts +11 -2
- package/src/confetti/index.ts +15 -3
- package/src/confetti/layer.ts +8 -5
- package/src/confetti/particle.ts +12 -11
- package/src/donuts/consts.ts +2 -2
- package/src/donuts/index.ts +9 -3
- package/src/donuts/layer.ts +43 -12
- package/src/effect.ts +107 -0
- package/src/fade.ts +87 -0
- package/src/fireflies/index.ts +9 -3
- package/src/fireflies/layer.ts +26 -9
- package/src/fireflies/particle.ts +2 -2
- package/src/firepit/index.ts +9 -3
- package/src/firepit/layer.ts +26 -7
- package/src/fireworks/create-explosion.ts +237 -0
- package/src/fireworks/explosion.ts +1 -1
- package/src/fireworks/index.ts +15 -3
- package/src/fireworks/layer.ts +55 -304
- package/src/fireworks/spark.ts +2 -2
- package/src/fireworks/types.ts +2 -2
- package/src/glitter/index.ts +9 -4
- package/src/glitter/layer.ts +15 -7
- package/src/glitter/types.ts +10 -0
- package/src/index.ts +3 -4
- package/src/lanterns/index.ts +9 -4
- package/src/lanterns/layer.ts +22 -10
- package/src/lanterns/types.ts +8 -0
- package/src/layer.ts +13 -11
- package/src/leaves/index.ts +9 -4
- package/src/leaves/layer.ts +21 -14
- package/src/leaves/types.ts +9 -0
- package/src/lightning/index.ts +9 -4
- package/src/lightning/layer.ts +4 -4
- package/src/lightning/system.ts +3 -3
- package/src/lightning/types.ts +10 -2
- package/src/matrix/index.ts +9 -4
- package/src/matrix/layer.ts +15 -7
- package/src/matrix/types.ts +9 -0
- package/src/orbits/index.ts +9 -4
- package/src/orbits/layer.ts +51 -21
- package/src/orbits/types.ts +12 -1
- package/src/particles/index.ts +9 -3
- package/src/particles/layer.ts +55 -12
- package/src/petals/index.ts +9 -3
- package/src/petals/layer.ts +29 -13
- package/src/plasma/index.ts +9 -3
- package/src/plasma/layer.ts +21 -6
- package/src/rain/index.ts +9 -3
- package/src/rain/layer.ts +30 -8
- package/src/sandstorm/index.ts +9 -3
- package/src/sandstorm/layer.ts +26 -9
- package/src/scene.ts +201 -0
- package/src/shooting-stars/system.ts +26 -24
- package/src/shooting-stars/types.ts +2 -1
- package/src/simulation-canvas.ts +40 -4
- package/src/snow/index.ts +9 -3
- package/src/snow/layer.ts +24 -11
- package/src/sparklers/index.ts +13 -3
- package/src/sparklers/layer.ts +61 -15
- package/src/stars/index.ts +9 -3
- package/src/stars/layer.ts +28 -22
- package/src/streamers/index.ts +9 -3
- package/src/streamers/layer.ts +18 -6
- package/src/streamers/types.ts +1 -1
- package/src/waves/index.ts +9 -3
- package/src/waves/layer.ts +42 -45
- package/src/waves/types.ts +1 -0
- package/src/wormhole/index.ts +9 -3
- package/src/wormhole/layer.ts +22 -6
- package/src/aurora/simulation.ts +0 -19
- package/src/balloons/simulation.ts +0 -19
- package/src/bubbles/simulation.ts +0 -20
- package/src/confetti/simulation.ts +0 -27
- package/src/donuts/simulation.ts +0 -25
- package/src/fireflies/simulation.ts +0 -18
- package/src/firepit/simulation.ts +0 -17
- package/src/fireworks/simulation.ts +0 -18
- package/src/glitter/simulation.ts +0 -19
- package/src/lanterns/simulation.ts +0 -17
- package/src/layered.ts +0 -185
- package/src/leaves/simulation.ts +0 -18
- package/src/lightning/simulation.ts +0 -17
- package/src/matrix/simulation.ts +0 -18
- package/src/orbits/simulation.ts +0 -19
- package/src/particles/simulation.ts +0 -26
- package/src/petals/simulation.ts +0 -18
- package/src/plasma/simulation.ts +0 -17
- package/src/rain/simulation.ts +0 -21
- package/src/sandstorm/simulation.ts +0 -18
- package/src/snow/simulation.ts +0 -17
- package/src/sparklers/simulation.ts +0 -30
- package/src/stars/simulation.ts +0 -22
- package/src/streamers/simulation.ts +0 -16
- package/src/waves/simulation.ts +0 -18
- package/src/wormhole/simulation.ts +0 -17
package/src/simulation-canvas.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { LimitedFrameRateCanvas } from './canvas';
|
|
2
|
-
import
|
|
2
|
+
import { applyEdgeFade } from './fade';
|
|
3
|
+
import type { EdgeFade, SimulationLayer } from './layer';
|
|
3
4
|
|
|
4
5
|
export class SimulationCanvas extends LimitedFrameRateCanvas {
|
|
5
6
|
readonly #simulation: SimulationLayer;
|
|
7
|
+
readonly #contextOptions: CanvasRenderingContext2DSettings;
|
|
8
|
+
#offscreen: HTMLCanvasElement | null = null;
|
|
9
|
+
#offscreenCtx: CanvasRenderingContext2D | null = null;
|
|
6
10
|
|
|
7
11
|
constructor(canvas: HTMLCanvasElement, simulation: SimulationLayer, frameRate: number = 60, options: CanvasRenderingContext2DSettings = {colorSpace: 'display-p3'}) {
|
|
8
12
|
super(canvas, frameRate, options);
|
|
9
13
|
this.#simulation = simulation;
|
|
14
|
+
this.#contextOptions = options;
|
|
10
15
|
|
|
11
16
|
canvas.style.position = 'absolute';
|
|
12
17
|
canvas.style.top = '0';
|
|
@@ -15,6 +20,11 @@ export class SimulationCanvas extends LimitedFrameRateCanvas {
|
|
|
15
20
|
canvas.style.width = '100%';
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
withFade(fade: EdgeFade): this {
|
|
24
|
+
this.#simulation.fade = fade;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
start(): void {
|
|
19
29
|
this.#simulation.onMount(this.canvas);
|
|
20
30
|
super.start();
|
|
@@ -30,9 +40,18 @@ export class SimulationCanvas extends LimitedFrameRateCanvas {
|
|
|
30
40
|
this.canvas.width = this.width;
|
|
31
41
|
|
|
32
42
|
const ctx = this.context;
|
|
33
|
-
|
|
34
|
-
this.#simulation.
|
|
35
|
-
|
|
43
|
+
|
|
44
|
+
if (this.#simulation.fade) {
|
|
45
|
+
const offCtx = this.#getOffscreenCtx(this.width, this.height);
|
|
46
|
+
offCtx.clearRect(0, 0, this.width, this.height);
|
|
47
|
+
this.#simulation.draw(offCtx, this.width, this.height);
|
|
48
|
+
applyEdgeFade(offCtx, this.width, this.height, this.#simulation.fade);
|
|
49
|
+
ctx.drawImage(this.#offscreen!, 0, 0);
|
|
50
|
+
} else {
|
|
51
|
+
ctx.save();
|
|
52
|
+
this.#simulation.draw(ctx, this.width, this.height);
|
|
53
|
+
ctx.restore();
|
|
54
|
+
}
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
tick(): void {
|
|
@@ -42,6 +61,23 @@ export class SimulationCanvas extends LimitedFrameRateCanvas {
|
|
|
42
61
|
|
|
43
62
|
onResize(): void {
|
|
44
63
|
super.onResize();
|
|
64
|
+
|
|
65
|
+
if (this.#offscreen) {
|
|
66
|
+
this.#offscreen.width = this.width;
|
|
67
|
+
this.#offscreen.height = this.height;
|
|
68
|
+
}
|
|
69
|
+
|
|
45
70
|
this.#simulation.onResize(this.width, this.height);
|
|
46
71
|
}
|
|
72
|
+
|
|
73
|
+
#getOffscreenCtx(width: number, height: number): CanvasRenderingContext2D {
|
|
74
|
+
if (!this.#offscreen) {
|
|
75
|
+
this.#offscreen = document.createElement('canvas');
|
|
76
|
+
this.#offscreen.width = width;
|
|
77
|
+
this.#offscreen.height = height;
|
|
78
|
+
this.#offscreenCtx = this.#offscreen.getContext('2d', this.#contextOptions)!;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this.#offscreenCtx!;
|
|
82
|
+
}
|
|
47
83
|
}
|
package/src/snow/index.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Snow } from './layer';
|
|
2
|
+
import type { SnowConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createSnow(config?: SnowConfig): Effect<SnowConfig> {
|
|
6
|
+
return new Snow(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { SnowConfig };
|
package/src/snow/layer.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { parseColor } from '../color';
|
|
2
|
-
import {
|
|
2
|
+
import { Effect } from '../effect';
|
|
3
3
|
import { MULBERRY } from './consts';
|
|
4
|
-
import type { SnowSimulationConfig } from './simulation';
|
|
5
4
|
import type { Snowflake } from './snowflake';
|
|
6
5
|
|
|
6
|
+
export interface SnowConfig {
|
|
7
|
+
readonly fillStyle?: string;
|
|
8
|
+
readonly particles?: number;
|
|
9
|
+
readonly scale?: number;
|
|
10
|
+
readonly size?: number;
|
|
11
|
+
readonly speed?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
const SPRITE_SIZE = 64;
|
|
8
15
|
const SPRITE_CENTER = SPRITE_SIZE / 2;
|
|
9
16
|
const SPRITE_RADIUS = SPRITE_SIZE / 2;
|
|
10
17
|
|
|
11
|
-
export class
|
|
18
|
+
export class Snow extends Effect<SnowConfig> {
|
|
12
19
|
readonly #scale: number;
|
|
13
20
|
readonly #size: number;
|
|
14
|
-
|
|
21
|
+
#speed: number;
|
|
15
22
|
readonly #baseOpacity: number;
|
|
16
23
|
#maxParticles: number;
|
|
17
24
|
#time: number = 0;
|
|
@@ -20,7 +27,7 @@ export class SnowLayer extends SimulationLayer {
|
|
|
20
27
|
#sprites: HTMLCanvasElement[] = [];
|
|
21
28
|
#height: number = 540;
|
|
22
29
|
|
|
23
|
-
constructor(config:
|
|
30
|
+
constructor(config: SnowConfig = {}) {
|
|
24
31
|
super();
|
|
25
32
|
|
|
26
33
|
this.#scale = config.scale ?? 1;
|
|
@@ -42,6 +49,12 @@ export class SnowLayer extends SimulationLayer {
|
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
configure(config: Partial<SnowConfig>): void {
|
|
53
|
+
if (config.speed !== undefined) {
|
|
54
|
+
this.#speed = config.speed;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
onResize(_width: number, height: number): void {
|
|
46
59
|
this.#height = height;
|
|
47
60
|
}
|
|
@@ -54,8 +67,8 @@ export class SnowLayer extends SimulationLayer {
|
|
|
54
67
|
this.#time += 0.015 * speedFactor * dt;
|
|
55
68
|
|
|
56
69
|
const wind = Math.sin(this.#time * 0.7) * 0.5
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
+ Math.sin(this.#time * 1.9 + 3) * 0.25
|
|
71
|
+
+ Math.sin(this.#time * 4.3 + 1) * 0.1;
|
|
59
72
|
|
|
60
73
|
for (let index = 0; index < this.#snowflakes.length; index++) {
|
|
61
74
|
const snowflake = this.#snowflakes[index];
|
|
@@ -104,9 +117,9 @@ export class SnowLayer extends SimulationLayer {
|
|
|
104
117
|
ctx.globalAlpha = this.#baseOpacity * (0.15 + snowflake.depth * 0.85);
|
|
105
118
|
|
|
106
119
|
if (snowflake.spriteIndex === 3) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
ctx.
|
|
120
|
+
const cos = Math.cos(snowflake.rotation);
|
|
121
|
+
const sin = Math.sin(snowflake.rotation);
|
|
122
|
+
ctx.setTransform(cos, sin, -sin, cos, px, py);
|
|
110
123
|
ctx.drawImage(
|
|
111
124
|
this.#sprites[snowflake.spriteIndex],
|
|
112
125
|
-displayRadius,
|
|
@@ -114,7 +127,7 @@ export class SnowLayer extends SimulationLayer {
|
|
|
114
127
|
displaySize,
|
|
115
128
|
displaySize
|
|
116
129
|
);
|
|
117
|
-
ctx.
|
|
130
|
+
ctx.resetTransform();
|
|
118
131
|
} else {
|
|
119
132
|
ctx.drawImage(
|
|
120
133
|
this.#sprites[snowflake.spriteIndex],
|
package/src/sparklers/index.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { Sparklers } from './layer';
|
|
2
|
+
import type { SparklersConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export interface SparklersInstance extends Effect<SparklersConfig> {
|
|
6
|
+
moveTo(x: number, y: number): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createSparklers(config?: SparklersConfig): SparklersInstance {
|
|
10
|
+
return new Sparklers(config) as SparklersInstance;
|
|
11
|
+
}
|
|
12
|
+
|
|
2
13
|
export { SparklerParticle } from './particle';
|
|
3
|
-
export {
|
|
14
|
+
export type { SparklersConfig };
|
|
4
15
|
export type { SparklerParticleConfig } from './particle';
|
|
5
|
-
export type { SparklerSimulationConfig } from './simulation';
|
|
6
16
|
export type { SparklerSpark } from './types';
|
package/src/sparklers/layer.ts
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
1
|
import { hexToRGB } from '@basmilius/utils';
|
|
2
|
-
import {
|
|
2
|
+
import { Effect } from '../effect';
|
|
3
3
|
import { MULBERRY } from './consts';
|
|
4
|
-
import type { SparklerSimulationConfig } from './simulation';
|
|
5
4
|
import type { SparklerSpark } from './types';
|
|
6
5
|
|
|
6
|
+
export interface SparklersConfig {
|
|
7
|
+
readonly emitRate?: number;
|
|
8
|
+
readonly maxSparks?: number;
|
|
9
|
+
readonly colors?: string[];
|
|
10
|
+
readonly speed?: [number, number];
|
|
11
|
+
readonly friction?: number;
|
|
12
|
+
readonly gravity?: number;
|
|
13
|
+
readonly decay?: [number, number];
|
|
14
|
+
readonly trailLength?: number;
|
|
15
|
+
readonly hoverMode?: boolean;
|
|
16
|
+
readonly scale?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
const DEFAULT_COLORS = ['#ffcc33', '#ff9900', '#ffffff', '#ffee88'];
|
|
8
20
|
|
|
9
|
-
export class
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
export class Sparklers extends Effect<SparklersConfig> {
|
|
22
|
+
#scale: number;
|
|
23
|
+
#emitRate: number;
|
|
12
24
|
readonly #maxSparks: number;
|
|
13
25
|
readonly #colorRGBs: [number, number, number][];
|
|
14
26
|
readonly #speedRange: [number, number];
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
#friction: number;
|
|
28
|
+
#gravity: number;
|
|
17
29
|
readonly #decayRange: [number, number];
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
#trailLength: number;
|
|
31
|
+
#hoverMode: boolean;
|
|
20
32
|
readonly #onMouseMoveBound: (evt: MouseEvent) => void;
|
|
21
33
|
readonly #onMouseLeaveBound: () => void;
|
|
22
34
|
#emitX: number = 0.5;
|
|
23
35
|
#emitY: number = 0.5;
|
|
24
36
|
#mouseOnCanvas: boolean = false;
|
|
25
37
|
#sparks: SparklerSpark[] = [];
|
|
38
|
+
#mountedCanvas: HTMLCanvasElement | null = null;
|
|
39
|
+
#cachedRect: DOMRect | null = null;
|
|
26
40
|
|
|
27
|
-
constructor(config:
|
|
41
|
+
constructor(config: SparklersConfig = {}) {
|
|
28
42
|
super();
|
|
29
43
|
|
|
30
44
|
this.#scale = config.scale ?? 1;
|
|
@@ -44,12 +58,15 @@ export class SparklerLayer extends SimulationLayer {
|
|
|
44
58
|
this.#onMouseLeaveBound = this.#onMouseLeave.bind(this);
|
|
45
59
|
}
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
moveTo(x: number, y: number): void {
|
|
48
62
|
this.#emitX = x;
|
|
49
63
|
this.#emitY = y;
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
onMount(canvas: HTMLCanvasElement): void {
|
|
67
|
+
this.#mountedCanvas = canvas;
|
|
68
|
+
this.#cachedRect = canvas.getBoundingClientRect();
|
|
69
|
+
|
|
53
70
|
if (this.#hoverMode) {
|
|
54
71
|
canvas.addEventListener('mousemove', this.#onMouseMoveBound, {passive: true});
|
|
55
72
|
canvas.addEventListener('mouseleave', this.#onMouseLeaveBound, {passive: true});
|
|
@@ -59,6 +76,35 @@ export class SparklerLayer extends SimulationLayer {
|
|
|
59
76
|
onUnmount(canvas: HTMLCanvasElement): void {
|
|
60
77
|
canvas.removeEventListener('mousemove', this.#onMouseMoveBound);
|
|
61
78
|
canvas.removeEventListener('mouseleave', this.#onMouseLeaveBound);
|
|
79
|
+
this.#mountedCanvas = null;
|
|
80
|
+
this.#cachedRect = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onResize(): void {
|
|
84
|
+
if (this.#mountedCanvas) {
|
|
85
|
+
this.#cachedRect = this.#mountedCanvas.getBoundingClientRect();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
configure(config: Partial<SparklersConfig>): void {
|
|
90
|
+
if (config.scale !== undefined) {
|
|
91
|
+
this.#scale = config.scale;
|
|
92
|
+
}
|
|
93
|
+
if (config.emitRate !== undefined) {
|
|
94
|
+
this.#emitRate = config.emitRate;
|
|
95
|
+
}
|
|
96
|
+
if (config.friction !== undefined) {
|
|
97
|
+
this.#friction = config.friction;
|
|
98
|
+
}
|
|
99
|
+
if (config.gravity !== undefined) {
|
|
100
|
+
this.#gravity = config.gravity;
|
|
101
|
+
}
|
|
102
|
+
if (config.trailLength !== undefined) {
|
|
103
|
+
this.#trailLength = config.trailLength;
|
|
104
|
+
}
|
|
105
|
+
if (config.hoverMode !== undefined) {
|
|
106
|
+
this.#hoverMode = config.hoverMode;
|
|
107
|
+
}
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
tick(dt: number, width: number, height: number): void {
|
|
@@ -70,6 +116,7 @@ export class SparklerLayer extends SimulationLayer {
|
|
|
70
116
|
}
|
|
71
117
|
}
|
|
72
118
|
|
|
119
|
+
const frictionFactor = Math.pow(this.#friction, dt);
|
|
73
120
|
let alive = 0;
|
|
74
121
|
|
|
75
122
|
for (let i = 0; i < this.#sparks.length; i++) {
|
|
@@ -81,8 +128,8 @@ export class SparklerLayer extends SimulationLayer {
|
|
|
81
128
|
spark.trail.shift();
|
|
82
129
|
}
|
|
83
130
|
|
|
84
|
-
spark.vx *=
|
|
85
|
-
spark.vy *=
|
|
131
|
+
spark.vx *= frictionFactor;
|
|
132
|
+
spark.vy *= frictionFactor;
|
|
86
133
|
spark.vy += this.#gravity * this.#scale * dt;
|
|
87
134
|
|
|
88
135
|
spark.x += spark.vx * dt;
|
|
@@ -143,8 +190,7 @@ export class SparklerLayer extends SimulationLayer {
|
|
|
143
190
|
}
|
|
144
191
|
|
|
145
192
|
#onMouseMove(evt: MouseEvent): void {
|
|
146
|
-
const
|
|
147
|
-
const rect = target.getBoundingClientRect();
|
|
193
|
+
const rect = this.#cachedRect ?? (evt.currentTarget as HTMLCanvasElement).getBoundingClientRect();
|
|
148
194
|
this.#emitX = (evt.clientX - rect.left) / rect.width;
|
|
149
195
|
this.#emitY = (evt.clientY - rect.top) / rect.height;
|
|
150
196
|
this.#mouseOnCanvas = true;
|
package/src/stars/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Stars } from './layer';
|
|
2
|
+
import type { StarsConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createStars(config?: StarsConfig): Effect<StarsConfig> {
|
|
6
|
+
return new Stars(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { StarsConfig };
|
|
4
10
|
export type { Star, StarMode, ShootingStar } from './types';
|
package/src/stars/layer.ts
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
import { hexToRGB } from '@basmilius/utils';
|
|
2
|
-
import {
|
|
2
|
+
import { Effect } from '../effect';
|
|
3
3
|
import { ShootingStarSystem } from '../shooting-stars';
|
|
4
4
|
import { MULBERRY } from './consts';
|
|
5
|
-
import type { StarSimulationConfig } from './simulation';
|
|
6
5
|
import type { Star, StarMode } from './types';
|
|
7
6
|
|
|
8
|
-
export
|
|
7
|
+
export interface StarsConfig {
|
|
8
|
+
readonly mode?: StarMode;
|
|
9
|
+
readonly starCount?: number;
|
|
10
|
+
readonly shootingInterval?: [number, number];
|
|
11
|
+
readonly shootingSpeed?: number;
|
|
12
|
+
readonly twinkleSpeed?: number;
|
|
13
|
+
readonly color?: string;
|
|
14
|
+
readonly shootingColor?: string;
|
|
15
|
+
readonly trailLength?: number;
|
|
16
|
+
readonly scale?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class Stars extends Effect<StarsConfig> {
|
|
9
20
|
readonly #mode: StarMode;
|
|
10
|
-
|
|
21
|
+
#twinkleSpeed: number;
|
|
11
22
|
readonly #colorRGB: [number, number, number];
|
|
12
|
-
|
|
13
|
-
readonly #verticalFade: [number, number] | null;
|
|
23
|
+
#scale: number;
|
|
14
24
|
readonly #shootingStarSystem: ShootingStarSystem | null;
|
|
15
25
|
#starCount: number;
|
|
16
26
|
#time: number = 0;
|
|
17
27
|
#stars: Star[] = [];
|
|
18
28
|
|
|
19
|
-
constructor(config:
|
|
29
|
+
constructor(config: StarsConfig = {}) {
|
|
20
30
|
super();
|
|
21
31
|
|
|
22
32
|
this.#mode = config.mode ?? 'both';
|
|
23
33
|
this.#starCount = config.starCount ?? 150;
|
|
24
34
|
this.#twinkleSpeed = config.twinkleSpeed ?? 1;
|
|
25
35
|
this.#scale = config.scale ?? 1;
|
|
26
|
-
this.#verticalFade = config.verticalFade ?? null;
|
|
27
36
|
|
|
28
37
|
this.#colorRGB = hexToRGB(config.color ?? '#ffffff');
|
|
29
38
|
|
|
@@ -42,8 +51,7 @@ export class StarLayer extends SimulationLayer {
|
|
|
42
51
|
alphaMin: 0.8,
|
|
43
52
|
alphaRange: 0.2,
|
|
44
53
|
decayMin: 0.01,
|
|
45
|
-
decayRange: 0.015
|
|
46
|
-
verticalFade: this.#verticalFade ?? undefined
|
|
54
|
+
decayRange: 0.015
|
|
47
55
|
},
|
|
48
56
|
() => MULBERRY.next()
|
|
49
57
|
)
|
|
@@ -56,6 +64,15 @@ export class StarLayer extends SimulationLayer {
|
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
configure(config: Partial<StarsConfig>): void {
|
|
68
|
+
if (config.twinkleSpeed !== undefined) {
|
|
69
|
+
this.#twinkleSpeed = config.twinkleSpeed;
|
|
70
|
+
}
|
|
71
|
+
if (config.scale !== undefined) {
|
|
72
|
+
this.#scale = config.scale;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
tick(dt: number, width: number, height: number): void {
|
|
60
77
|
this.#time += 0.02 * dt;
|
|
61
78
|
this.#shootingStarSystem?.tick(dt, width, height);
|
|
@@ -71,18 +88,7 @@ export class StarLayer extends SimulationLayer {
|
|
|
71
88
|
for (const star of this.#stars) {
|
|
72
89
|
const px = star.x * width;
|
|
73
90
|
const py = star.y * height;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (this.#verticalFade) {
|
|
77
|
-
const [fadeStart, fadeEnd] = this.#verticalFade;
|
|
78
|
-
const fadeFactor = 1 - Math.max(0, Math.min(1, (star.y - fadeStart) / (fadeEnd - fadeStart)));
|
|
79
|
-
alpha *= fadeFactor;
|
|
80
|
-
|
|
81
|
-
if (alpha <= 0) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
91
|
+
const alpha = star.brightness * (0.3 + 0.7 * (0.5 + 0.5 * Math.sin(this.#time * star.twinkleSpeed * this.#twinkleSpeed + star.twinklePhase)));
|
|
86
92
|
const size = star.size * this.#scale;
|
|
87
93
|
|
|
88
94
|
ctx.globalAlpha = alpha;
|
package/src/streamers/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Streamers } from './layer';
|
|
2
|
+
import type { StreamersConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createStreamers(config?: StreamersConfig): Effect<StreamersConfig> {
|
|
6
|
+
return new Streamers(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { StreamersConfig };
|
|
4
10
|
export type { Streamer } from './types';
|
package/src/streamers/layer.ts
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Effect } from '../effect';
|
|
2
2
|
import { MULBERRY, STREAMER_COLORS } from './consts';
|
|
3
|
-
import type { StreamerSimulationConfig } from './simulation';
|
|
4
3
|
import type { Streamer } from './types';
|
|
5
4
|
|
|
6
|
-
export
|
|
5
|
+
export interface StreamersConfig {
|
|
6
|
+
readonly count?: number;
|
|
7
|
+
readonly colors?: string[];
|
|
8
|
+
readonly speed?: number;
|
|
9
|
+
readonly scale?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Streamers extends Effect<StreamersConfig> {
|
|
7
13
|
readonly #colors: string[];
|
|
8
14
|
readonly #scale: number;
|
|
9
|
-
|
|
15
|
+
#speed: number;
|
|
10
16
|
#count: number;
|
|
11
17
|
#streamers: Streamer[] = [];
|
|
12
18
|
#width: number = 960;
|
|
13
19
|
#height: number = 540;
|
|
14
20
|
#initialized: boolean = false;
|
|
15
21
|
|
|
16
|
-
constructor(config:
|
|
22
|
+
constructor(config: StreamersConfig = {}) {
|
|
17
23
|
super();
|
|
18
24
|
|
|
19
25
|
this.#colors = config.colors ?? STREAMER_COLORS;
|
|
@@ -40,6 +46,12 @@ export class StreamerLayer extends SimulationLayer {
|
|
|
40
46
|
}
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
configure(config: Partial<StreamersConfig>): void {
|
|
50
|
+
if (config.speed !== undefined) {
|
|
51
|
+
this.#speed = config.speed;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
tick(dt: number, width: number, height: number): void {
|
|
44
56
|
this.#width = width;
|
|
45
57
|
this.#height = height;
|
|
@@ -98,7 +110,7 @@ export class StreamerLayer extends SimulationLayer {
|
|
|
98
110
|
const swayPhase = MULBERRY.next() * Math.PI * 2;
|
|
99
111
|
const color = this.#colors[Math.floor(MULBERRY.next() * this.#colors.length)];
|
|
100
112
|
|
|
101
|
-
const segments: {x: number; y: number}[] = [];
|
|
113
|
+
const segments: { x: number; y: number }[] = [];
|
|
102
114
|
const segmentLength = length / segmentCount;
|
|
103
115
|
|
|
104
116
|
for (let i = 0; i < segmentCount; i++) {
|
package/src/streamers/types.ts
CHANGED
package/src/waves/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Waves } from './layer';
|
|
2
|
+
import type { WavesConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createWaves(config?: WavesConfig): Effect<WavesConfig> {
|
|
6
|
+
return new Waves(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { WavesConfig };
|
|
4
10
|
export type { Wave } from './types';
|
package/src/waves/layer.ts
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hexToRGB } from '@basmilius/utils';
|
|
2
|
+
import { Effect } from '../effect';
|
|
2
3
|
import { MULBERRY } from './consts';
|
|
3
|
-
import type { WaveSimulationConfig } from './simulation';
|
|
4
4
|
import type { Wave } from './types';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_COLORS = ['#0a3d6b', '#0e5a8a', '#1a7ab5', '#3399cc', '#66c2e0'];
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
|
|
8
|
+
export interface WavesConfig {
|
|
9
|
+
readonly layers?: number;
|
|
10
|
+
readonly speed?: number;
|
|
11
|
+
readonly colors?: string[];
|
|
12
|
+
readonly foamColor?: string;
|
|
13
|
+
readonly foamAmount?: number;
|
|
14
|
+
readonly scale?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Waves extends Effect<WavesConfig> {
|
|
18
|
+
#speed: number;
|
|
19
|
+
#foamAmount: number;
|
|
20
|
+
#scale: number;
|
|
21
|
+
readonly #foamRGB: [number, number, number];
|
|
14
22
|
#waves: Wave[] = [];
|
|
15
|
-
#foamParticles: {x: number; y: number; alpha: number; size: number}[] = [];
|
|
23
|
+
#foamParticles: { x: number; y: number; alpha: number; size: number }[] = [];
|
|
16
24
|
#maxFoamParticles: number;
|
|
17
25
|
|
|
18
|
-
constructor(config:
|
|
26
|
+
constructor(config: WavesConfig = {}) {
|
|
19
27
|
super();
|
|
20
28
|
|
|
21
29
|
const layers = config.layers ?? 5;
|
|
22
30
|
const colors = config.colors ?? DEFAULT_COLORS;
|
|
23
31
|
this.#speed = config.speed ?? 1;
|
|
24
|
-
this.#foamColor = config.foamColor ?? '#ffffff';
|
|
25
32
|
this.#foamAmount = config.foamAmount ?? 0.4;
|
|
26
33
|
this.#scale = config.scale ?? 1;
|
|
27
34
|
this.#maxFoamParticles = 120;
|
|
35
|
+
this.#foamRGB = hexToRGB(config.foamColor ?? '#ffffff');
|
|
28
36
|
|
|
29
37
|
if (innerWidth < 991) {
|
|
30
38
|
this.#maxFoamParticles = Math.floor(this.#maxFoamParticles / 2);
|
|
@@ -37,20 +45,31 @@ export class WaveLayer extends SimulationLayer {
|
|
|
37
45
|
this.#waves.push({
|
|
38
46
|
amplitude: (20 + MULBERRY.next() * 30) * (1 - depth * 0.4),
|
|
39
47
|
frequency: 0.005 + MULBERRY.next() * 0.008 + depth * 0.002,
|
|
40
|
-
speed:
|
|
48
|
+
speed: 0.4 + MULBERRY.next() * 0.6 + depth * 0.3,
|
|
41
49
|
phase: MULBERRY.next() * Math.PI * 2,
|
|
42
50
|
baseY: 0.35 + depth * 0.13,
|
|
43
51
|
color,
|
|
44
|
-
foamThreshold: 0.6 + MULBERRY.next() * 0.3
|
|
52
|
+
foamThreshold: 0.6 + MULBERRY.next() * 0.3,
|
|
53
|
+
rgb: hexToRGB(color)
|
|
45
54
|
});
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
configure(config: Partial<WavesConfig>): void {
|
|
59
|
+
if (config.speed !== undefined) {
|
|
60
|
+
this.#speed = config.speed;
|
|
61
|
+
}
|
|
62
|
+
if (config.foamAmount !== undefined) {
|
|
63
|
+
this.#foamAmount = config.foamAmount;
|
|
64
|
+
}
|
|
65
|
+
if (config.scale !== undefined) {
|
|
66
|
+
this.#scale = config.scale;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
51
69
|
|
|
70
|
+
tick(dt: number, width: number, height: number): void {
|
|
52
71
|
for (const wave of this.#waves) {
|
|
53
|
-
wave.phase += 0.015 * wave.speed * dt;
|
|
72
|
+
wave.phase += 0.015 * wave.speed * this.#speed * dt;
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
let aliveFoam = 0;
|
|
@@ -100,6 +119,7 @@ export class WaveLayer extends SimulationLayer {
|
|
|
100
119
|
|
|
101
120
|
for (let wi = 0; wi < this.#waves.length; wi++) {
|
|
102
121
|
const wave = this.#waves[wi];
|
|
122
|
+
const [wr, wg, wb] = wave.rgb;
|
|
103
123
|
const centerY = wave.baseY * height;
|
|
104
124
|
|
|
105
125
|
ctx.beginPath();
|
|
@@ -118,15 +138,17 @@ export class WaveLayer extends SimulationLayer {
|
|
|
118
138
|
ctx.closePath();
|
|
119
139
|
|
|
120
140
|
const gradient = ctx.createLinearGradient(0, centerY - wave.amplitude * this.#scale, 0, height);
|
|
121
|
-
gradient.addColorStop(0,
|
|
122
|
-
gradient.addColorStop(0.4,
|
|
123
|
-
gradient.addColorStop(1,
|
|
141
|
+
gradient.addColorStop(0, `rgba(${wr}, ${wg}, ${wb}, 0.85)`);
|
|
142
|
+
gradient.addColorStop(0.4, `rgb(${wr}, ${wg}, ${wb})`);
|
|
143
|
+
gradient.addColorStop(1, `rgb(${Math.floor(wr * 0.6)}, ${Math.floor(wg * 0.6)}, ${Math.floor(wb * 0.6)})`);
|
|
124
144
|
|
|
125
145
|
ctx.fillStyle = gradient;
|
|
126
146
|
ctx.fill();
|
|
127
147
|
}
|
|
128
148
|
|
|
129
149
|
if (this.#foamAmount > 0) {
|
|
150
|
+
const [fr, fg, fb] = this.#foamRGB;
|
|
151
|
+
|
|
130
152
|
for (const foam of this.#foamParticles) {
|
|
131
153
|
if (foam.alpha <= 0) {
|
|
132
154
|
continue;
|
|
@@ -134,34 +156,9 @@ export class WaveLayer extends SimulationLayer {
|
|
|
134
156
|
|
|
135
157
|
ctx.beginPath();
|
|
136
158
|
ctx.arc(foam.x, foam.y, foam.size * this.#scale, 0, Math.PI * 2);
|
|
137
|
-
ctx.fillStyle =
|
|
159
|
+
ctx.fillStyle = `rgba(${fr}, ${fg}, ${fb}, ${foam.alpha * this.#foamAmount})`;
|
|
138
160
|
ctx.fill();
|
|
139
161
|
}
|
|
140
162
|
}
|
|
141
163
|
}
|
|
142
|
-
|
|
143
|
-
#adjustAlpha(color: string, alpha: number): string {
|
|
144
|
-
const canvas = document.createElement('canvas');
|
|
145
|
-
canvas.width = 1;
|
|
146
|
-
canvas.height = 1;
|
|
147
|
-
const ctx = canvas.getContext('2d')!;
|
|
148
|
-
ctx.fillStyle = color;
|
|
149
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
150
|
-
const data = ctx.getImageData(0, 0, 1, 1).data;
|
|
151
|
-
return `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${alpha})`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
#darkenColor(color: string, factor: number): string {
|
|
155
|
-
const canvas = document.createElement('canvas');
|
|
156
|
-
canvas.width = 1;
|
|
157
|
-
canvas.height = 1;
|
|
158
|
-
const ctx = canvas.getContext('2d')!;
|
|
159
|
-
ctx.fillStyle = color;
|
|
160
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
161
|
-
const data = ctx.getImageData(0, 0, 1, 1).data;
|
|
162
|
-
const r = Math.floor(data[0] * factor);
|
|
163
|
-
const g = Math.floor(data[1] * factor);
|
|
164
|
-
const b = Math.floor(data[2] * factor);
|
|
165
|
-
return `rgb(${r}, ${g}, ${b})`;
|
|
166
|
-
}
|
|
167
164
|
}
|