svelte-confetti-explosion 0.0.1 → 0.0.5

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.
@@ -0,0 +1,302 @@
1
+ <script context="module">const ROTATION_SPEED_MIN = 200; // minimum possible duration of single particle full rotation
2
+ const ROTATION_SPEED_MAX = 800; // maximum possible duration of single particle full rotation
3
+ const CRAZY_PARTICLES_FREQUENCY = 0.1; // 0-1 frequency of crazy curvy unpredictable particles
4
+ const CRAZY_PARTICLE_CRAZINESS = 0.3; // 0-1 how crazy these crazy particles are
5
+ const BEZIER_MEDIAN = 0.5; // utility for mid-point bezier curves, to ensure smooth motion paths
6
+ const FORCE = 0.5; // 0-1 roughly the vertical force at which particles initially explode
7
+ const SIZE = 12; // max height for particle rectangles, diameter for particle circles
8
+ const FLOOR_HEIGHT = 800; // pixels the particles will fall from initial explosion point
9
+ const FLOOR_WIDTH = 1600; // horizontal spread of particles in pixels
10
+ const PARTICLE_COUNT = 150;
11
+ const DURATION = 3500;
12
+ const COLORS = ['#FFC700', '#FF0000', '#2E3191', '#41BBC7'];
13
+ const createParticles = (count, colors) => {
14
+ const increment = 360 / count;
15
+ return Array.from({ length: count }, (_, i) => ({
16
+ color: colors[i % colors.length],
17
+ degree: i * increment,
18
+ }));
19
+ };
20
+ const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
21
+ // From here: https://stackoverflow.com/a/11832950
22
+ function round(num, precision = 2) {
23
+ return Math.round((num + Number.EPSILON) * 10 ** precision) / 10 ** precision;
24
+ }
25
+ function arraysEqual(a, b) {
26
+ if (a === b)
27
+ return true;
28
+ if (a == null || b == null)
29
+ return false;
30
+ if (a.length !== b.length)
31
+ return false;
32
+ for (let i = 0; i < a.length; i++)
33
+ if (a[i] !== b[i])
34
+ return false;
35
+ return true;
36
+ }
37
+ const mapRange = (value, x1, y1, x2, y2) => ((value - x1) * (y2 - x2)) / (y1 - x1) + x2;
38
+ const rotate = (degree, amount) => {
39
+ const result = degree + amount;
40
+ return result > 360 ? result - 360 : result;
41
+ };
42
+ const coinFlip = () => Math.random() > 0.5;
43
+ // avoid this for circles, as it will have no visual effect
44
+ const zAxisRotation = [0, 0, 1];
45
+ const rotationTransforms = [
46
+ // dual axis rotations (a bit more realistic)
47
+ [1, 1, 0],
48
+ [1, 0, 1],
49
+ [0, 1, 1],
50
+ // single axis rotations (a bit dumber)
51
+ [1, 0, 0],
52
+ [0, 1, 0],
53
+ zAxisRotation,
54
+ ];
55
+ const shouldBeCircle = (rotationIndex) => !arraysEqual(rotationTransforms[rotationIndex], zAxisRotation) && coinFlip();
56
+ const isUndefined = (value) => typeof value === 'undefined';
57
+ const error = (message) => {
58
+ console.error(message);
59
+ };
60
+ function validate(particleCount, duration, colors, particleSize, force, floorHeight, floorWidth) {
61
+ const isSafeInteger = Number.isSafeInteger;
62
+ if (!isUndefined(particleCount) && isSafeInteger(particleCount) && particleCount < 0) {
63
+ error('particleCount must be a positive integer');
64
+ return false;
65
+ }
66
+ if (!isUndefined(duration) && isSafeInteger(duration) && duration < 0) {
67
+ error('duration must be a positive integer');
68
+ return false;
69
+ }
70
+ if (!isUndefined(colors) && !Array.isArray(colors)) {
71
+ error('colors must be an array of strings');
72
+ return false;
73
+ }
74
+ if (!isUndefined(particleSize) && isSafeInteger(particleSize) && particleSize < 0) {
75
+ error('particleSize must be a positive integer');
76
+ return false;
77
+ }
78
+ if (!isUndefined(force) && isSafeInteger(force) && (force < 0 || force > 1)) {
79
+ error('force must be a positive integer and should be within 0 and 1');
80
+ return false;
81
+ }
82
+ if (!isUndefined(floorHeight) &&
83
+ typeof floorHeight === 'number' &&
84
+ isSafeInteger(floorHeight) &&
85
+ floorHeight < 0) {
86
+ error('floorHeight must be a positive integer');
87
+ return false;
88
+ }
89
+ if (!isUndefined(floorWidth) &&
90
+ typeof floorWidth === 'number' &&
91
+ isSafeInteger(floorWidth) &&
92
+ floorWidth < 0) {
93
+ error('floorWidth must be a positive integer');
94
+ return false;
95
+ }
96
+ return true;
97
+ }
98
+ </script>
99
+
100
+ <script >import { onMount } from 'svelte';
101
+ /**
102
+ * Number of confetti particles to create
103
+ *
104
+ * @default 150
105
+ *
106
+ * @example
107
+ *
108
+ * ```svelte
109
+ * <ConfettiExplosion particleCount={200} />
110
+ * ```
111
+ */
112
+ export let particleCount = PARTICLE_COUNT;
113
+ /**
114
+ * Duration of the animation in milliseconds
115
+ *
116
+ * @default 3500
117
+ *
118
+ * @example
119
+ *
120
+ * ```svelte
121
+ * <ConfettiExplosion duration={5000} />
122
+ * ```
123
+ */
124
+ export let duration = DURATION;
125
+ /**
126
+ * Colors to use for the confetti particles. Pass string array of colors. Can use hex colors, named colors,
127
+ * CSS Variables, literally anything valid in plain CSS.
128
+ *
129
+ * @default ['#FFC700', '#FF0000', '#2E3191', '#41BBC7']
130
+ *
131
+ * @example
132
+ *
133
+ * ```svelte
134
+ * <ConfettiExplosion colors={['var(--yellow)', 'var(--red)', '#2E3191', '#41BBC7']} />
135
+ * ```
136
+ */
137
+ export let colors = COLORS;
138
+ /**
139
+ * Size of the confetti particles in pixels
140
+ *
141
+ * @default 12
142
+ *
143
+ * @example
144
+ *
145
+ * ```svelte
146
+ * <ConfettiExplosion particleSize={20} />
147
+ * ```
148
+ */
149
+ export let particleSize = SIZE;
150
+ /**
151
+ * Force of the confetti particles. Between 0 and 1. 0 is no force, 1 is maximum force.
152
+ *
153
+ * @default 0.5
154
+ *
155
+ * @example
156
+ *
157
+ * ```svelte
158
+ * <ConfettiExplosion force={0.8} />
159
+ * ```
160
+ */
161
+ export let force = FORCE;
162
+ /**
163
+ * Height of the floor in pixels. Confetti will only fall within this height.
164
+ *
165
+ * @default 800
166
+ *
167
+ * @example
168
+ *
169
+ * ```svelte
170
+ * <ConfettiExplosion floorHeight={500} />
171
+ * ```
172
+ */
173
+ export let floorHeight = FLOOR_HEIGHT;
174
+ /**
175
+ * Width of the floor in pixels. Confetti will only fall within this width.
176
+ *
177
+ * @default 1600
178
+ *
179
+ * @example
180
+ *
181
+ * ```svelte
182
+ * <ConfettiExplosion floorWidth={1000} />
183
+ * ```
184
+ */
185
+ export let floorWidth = FLOOR_WIDTH;
186
+ /**
187
+ * Whether or not destroy all confetti nodes after the `duration` period has passed. By default it destroys all nodes, to preserve memory.
188
+ *
189
+ * @default true
190
+ *
191
+ * @example
192
+ *
193
+ * ```svelte
194
+ * <ConfettiExplosion shouldDestroyAfterDone={false} />
195
+ * ```
196
+ */
197
+ export let shouldDestroyAfterDone = true;
198
+ let isVisible = true;
199
+ $: particles = createParticles(particleCount, colors);
200
+ $: isValid = validate(particleCount, duration, colors, particleSize, force, floorHeight, floorWidth);
201
+ onMount(async () => {
202
+ await waitFor(duration);
203
+ if (shouldDestroyAfterDone) {
204
+ isVisible = false;
205
+ }
206
+ });
207
+ function confettiStyles(node, { degree }) {
208
+ // Get x landing point for it
209
+ const landingPoint = mapRange(Math.abs(rotate(degree, 90) - 180), 0, 180, -floorWidth / 2, floorWidth / 2);
210
+ // Crazy calculations for generating styles
211
+ const rotation = Math.random() * (ROTATION_SPEED_MAX - ROTATION_SPEED_MIN) + ROTATION_SPEED_MIN;
212
+ const rotationIndex = Math.round(Math.random() * (rotationTransforms.length - 1));
213
+ const durationChaos = duration - Math.round(Math.random() * 1000);
214
+ const shouldBeCrazy = Math.random() < CRAZY_PARTICLES_FREQUENCY;
215
+ const isCircle = shouldBeCircle(rotationIndex);
216
+ // x-axis disturbance, roughly the distance the particle will initially deviate from its target
217
+ const x1 = shouldBeCrazy ? round(Math.random() * CRAZY_PARTICLE_CRAZINESS, 2) : 0;
218
+ const x2 = x1 * -1;
219
+ const x3 = x1;
220
+ // x-axis arc of explosion, so 90deg and 270deg particles have curve of 1, 0deg and 180deg have 0
221
+ const x4 = round(Math.abs(mapRange(Math.abs(rotate(degree, 90) - 180), 0, 180, -1, 1)), 4);
222
+ // roughly how fast particle reaches end of its explosion curve
223
+ const y1 = round(Math.random() * BEZIER_MEDIAN, 4);
224
+ // roughly maps to the distance particle goes before reaching free-fall
225
+ const y2 = round(Math.random() * force * (coinFlip() ? 1 : -1), 4);
226
+ // roughly how soon the particle transitions from explosion to free-fall
227
+ const y3 = BEZIER_MEDIAN;
228
+ // roughly the ease of free-fall
229
+ const y4 = round(Math.max(mapRange(Math.abs(degree - 180), 0, 180, force, -force), 0), 4);
230
+ const setCSSVar = (key, val) => node.style.setProperty(key, val + '');
231
+ setCSSVar('--x-landing-point', `${landingPoint}px`);
232
+ setCSSVar('--duration-chaos', `${durationChaos}ms`);
233
+ setCSSVar('--x1', `${x1}`);
234
+ setCSSVar('--x2', `${x2}`);
235
+ setCSSVar('--x3', `${x3}`);
236
+ setCSSVar('--x4', `${x4}`);
237
+ setCSSVar('--y1', `${y1}`);
238
+ setCSSVar('--y2', `${y2}`);
239
+ setCSSVar('--y3', `${y3}`);
240
+ setCSSVar('--y4', `${y4}`);
241
+ // set --width and --height here
242
+ setCSSVar('--width', `${isCircle ? particleSize : Math.round(Math.random() * 4) + particleSize / 2}px`);
243
+ setCSSVar('--height', (isCircle ? particleSize : Math.round(Math.random() * 2) + particleSize) + 'px');
244
+ setCSSVar('--rotation', `${rotationTransforms[rotationIndex].join()}`);
245
+ setCSSVar('--rotation-duration', `${rotation}ms`);
246
+ setCSSVar('--border-radius', `${isCircle ? '50%' : '0'}`);
247
+ }
248
+ </script>
249
+
250
+ {#if isVisible && isValid}
251
+ <div class="container" style="--floor-height: {floorHeight}px;">
252
+ {#each particles as { color, degree }}
253
+ <div class="particle" use:confettiStyles={{ degree }}>
254
+ <div style="--bgcolor: {color};" />
255
+ </div>
256
+ {/each}
257
+ </div>
258
+ {/if}
259
+
260
+ <style >@keyframes y-axis {
261
+ to {
262
+ transform: translate3d(0, var(--floor-height), 0);
263
+ }
264
+ }
265
+ @keyframes x-axis {
266
+ to {
267
+ transform: translate3d(var(--x-landing-point), 0, 0);
268
+ }
269
+ }
270
+ @keyframes rotation {
271
+ to {
272
+ transform: rotate3d(var(--rotation), 360deg);
273
+ }
274
+ }
275
+ .container {
276
+ width: 0;
277
+ height: 0;
278
+ overflow: visible;
279
+ position: relative;
280
+ z-index: 1200;
281
+ }
282
+
283
+ .particle {
284
+ animation: x-axis var(--duration-chaos) forwards cubic-bezier(var(--x1), var(--x2), var(--x3), var(--x4));
285
+ }
286
+ .particle div {
287
+ position: absolute;
288
+ top: 0;
289
+ left: 0;
290
+ animation: y-axis var(--duration-chaos) forwards cubic-bezier(var(--y1), var(--y2), var(--y3), var(--y4));
291
+ width: var(--width);
292
+ height: var(--height);
293
+ }
294
+ .particle div:before {
295
+ display: block;
296
+ height: 100%;
297
+ width: 100%;
298
+ content: "";
299
+ background-color: var(--bgcolor);
300
+ animation: rotation var(--rotation-duration) infinite linear;
301
+ border-radius: var(--border-radius);
302
+ }</style>
@@ -0,0 +1,104 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ /**
5
+ * Number of confetti particles to create
6
+ *
7
+ * @default 150
8
+ *
9
+ * @example
10
+ *
11
+ * ```svelte
12
+ * <ConfettiExplosion particleCount={200} />
13
+ * ```
14
+ */ particleCount?: number;
15
+ /**
16
+ * Duration of the animation in milliseconds
17
+ *
18
+ * @default 3500
19
+ *
20
+ * @example
21
+ *
22
+ * ```svelte
23
+ * <ConfettiExplosion duration={5000} />
24
+ * ```
25
+ */ duration?: number;
26
+ /**
27
+ * Colors to use for the confetti particles. Pass string array of colors. Can use hex colors, named colors,
28
+ * CSS Variables, literally anything valid in plain CSS.
29
+ *
30
+ * @default ['#FFC700', '#FF0000', '#2E3191', '#41BBC7']
31
+ *
32
+ * @example
33
+ *
34
+ * ```svelte
35
+ * <ConfettiExplosion colors={['var(--yellow)', 'var(--red)', '#2E3191', '#41BBC7']} />
36
+ * ```
37
+ */ colors?: string[];
38
+ /**
39
+ * Size of the confetti particles in pixels
40
+ *
41
+ * @default 12
42
+ *
43
+ * @example
44
+ *
45
+ * ```svelte
46
+ * <ConfettiExplosion particleSize={20} />
47
+ * ```
48
+ */ particleSize?: number;
49
+ /**
50
+ * Force of the confetti particles. Between 0 and 1. 0 is no force, 1 is maximum force.
51
+ *
52
+ * @default 0.5
53
+ *
54
+ * @example
55
+ *
56
+ * ```svelte
57
+ * <ConfettiExplosion force={0.8} />
58
+ * ```
59
+ */ force?: number;
60
+ /**
61
+ * Height of the floor in pixels. Confetti will only fall within this height.
62
+ *
63
+ * @default 800
64
+ *
65
+ * @example
66
+ *
67
+ * ```svelte
68
+ * <ConfettiExplosion floorHeight={500} />
69
+ * ```
70
+ */ floorHeight?: number;
71
+ /**
72
+ * Width of the floor in pixels. Confetti will only fall within this width.
73
+ *
74
+ * @default 1600
75
+ *
76
+ * @example
77
+ *
78
+ * ```svelte
79
+ * <ConfettiExplosion floorWidth={1000} />
80
+ * ```
81
+ */ floorWidth?: number;
82
+ /**
83
+ * Whether or not destroy all confetti nodes after the `duration` period has passed. By default it destroys all nodes, to preserve memory.
84
+ *
85
+ * @default true
86
+ *
87
+ * @example
88
+ *
89
+ * ```svelte
90
+ * <ConfettiExplosion shouldDestroyAfterDone={false} />
91
+ * ```
92
+ */ shouldDestroyAfterDone?: boolean;
93
+ };
94
+ events: {
95
+ [evt: string]: CustomEvent<any>;
96
+ };
97
+ slots: {};
98
+ };
99
+ export declare type ConfettiExplosionProps = typeof __propDef.props;
100
+ export declare type ConfettiExplosionEvents = typeof __propDef.events;
101
+ export declare type ConfettiExplosionSlots = typeof __propDef.slots;
102
+ export default class ConfettiExplosion extends SvelteComponentTyped<ConfettiExplosionProps, ConfettiExplosionEvents, ConfettiExplosionSlots> {
103
+ }
104
+ export {};
File without changes
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default as ConfettiExplosion } from './ConfettiExplosion.svelte';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svelte-confetti-explosion",
3
- "version": "0.0.1",
4
- "svelte": "./ConfettiExplosion.svelte",
3
+ "version": "0.0.5",
4
+ "svelte": "index.js",
5
5
  "devDependencies": {
6
6
  "@sveltejs/kit": "^1.0.0-next.197",
7
7
  "prettier": "^2.4.1",
@@ -15,14 +15,12 @@
15
15
  "typescript": "^4.5.2"
16
16
  },
17
17
  "type": "module",
18
- "scripts": {
19
- "dev": "svelte-kit dev",
20
- "build": "svelte-kit build",
21
- "package": "svelte-kit package",
22
- "preview": "svelte-kit preview",
23
- "check": "svelte-check --tsconfig ./tsconfig.json",
24
- "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
25
- "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. .",
26
- "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
18
+ "dependencies": {
19
+ "svelte-confetti-explosion": "0.0.4"
20
+ },
21
+ "exports": {
22
+ "./package.json": "./package.json",
23
+ "./ConfettiExplosion.svelte": "./ConfettiExplosion.svelte",
24
+ ".": "./index.js"
27
25
  }
28
26
  }
package/.prettierrc DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "useTabs": true,
3
- "singleQuote": true,
4
- "printWidth": 100
5
- }