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.
package/src/app.html DELETED
@@ -1,12 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <link rel="icon" href="/favicon.png" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- %svelte.head%
8
- </head>
9
- <body>
10
- <div id="svelte">%svelte.body%</div>
11
- </body>
12
- </html>
package/src/global.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="@sveltejs/kit" />
@@ -1,404 +0,0 @@
1
- <script lang="ts" context="module">
2
- type Particle = {
3
- color: string; // color of particle
4
- degree: number; // vector direction, between 0-360 (0 being straight up ↑)
5
- };
6
-
7
- type Rotate3dTransform = [number, number, number];
8
-
9
- const ROTATION_SPEED_MIN = 200; // minimum possible duration of single particle full rotation
10
- const ROTATION_SPEED_MAX = 800; // maximum possible duration of single particle full rotation
11
- const CRAZY_PARTICLES_FREQUENCY = 0.1; // 0-1 frequency of crazy curvy unpredictable particles
12
- const CRAZY_PARTICLE_CRAZINESS = 0.3; // 0-1 how crazy these crazy particles are
13
- const BEZIER_MEDIAN = 0.5; // utility for mid-point bezier curves, to ensure smooth motion paths
14
-
15
- const FORCE = 0.5; // 0-1 roughly the vertical force at which particles initially explode
16
- const SIZE = 12; // max height for particle rectangles, diameter for particle circles
17
- const FLOOR_HEIGHT = 800; // pixels the particles will fall from initial explosion point
18
- const FLOOR_WIDTH = 1600; // horizontal spread of particles in pixels
19
- const PARTICLE_COUNT = 150;
20
- const DURATION = 3500;
21
- const COLORS = ['#FFC700', '#FF0000', '#2E3191', '#41BBC7'];
22
-
23
- const createParticles = (count: number, colors: string[]): Particle[] => {
24
- const increment = 360 / count;
25
- return Array.from({ length: count }, (_, i) => ({
26
- color: colors[i % colors.length],
27
- degree: i * increment,
28
- }));
29
- };
30
-
31
- const waitFor = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
32
-
33
- // From here: https://stackoverflow.com/a/11832950
34
- function round(num: number, precision: number = 2) {
35
- return Math.round((num + Number.EPSILON) * 10 ** precision) / 10 ** precision;
36
- }
37
-
38
- function arraysEqual<ItemType>(a: ItemType[], b: ItemType[]) {
39
- if (a === b) return true;
40
- if (a == null || b == null) return false;
41
- if (a.length !== b.length) return false;
42
-
43
- for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
44
-
45
- return true;
46
- }
47
-
48
- const mapRange = (value: number, x1: number, y1: number, x2: number, y2: number) =>
49
- ((value - x1) * (y2 - x2)) / (y1 - x1) + x2;
50
-
51
- const rotate = (degree: number, amount: number) => {
52
- const result = degree + amount;
53
- return result > 360 ? result - 360 : result;
54
- };
55
-
56
- const coinFlip = () => Math.random() > 0.5;
57
-
58
- // avoid this for circles, as it will have no visual effect
59
- const zAxisRotation: Rotate3dTransform = [0, 0, 1];
60
-
61
- const rotationTransforms: Rotate3dTransform[] = [
62
- // dual axis rotations (a bit more realistic)
63
- [1, 1, 0],
64
- [1, 0, 1],
65
- [0, 1, 1],
66
- // single axis rotations (a bit dumber)
67
- [1, 0, 0],
68
- [0, 1, 0],
69
- zAxisRotation,
70
- ];
71
-
72
- const shouldBeCircle = (rotationIndex: number) =>
73
- !arraysEqual(rotationTransforms[rotationIndex], zAxisRotation) && coinFlip();
74
-
75
- const isUndefined = (value: any) => typeof value === 'undefined';
76
-
77
- const error = (message: string) => {
78
- console.error(message);
79
- };
80
-
81
- function validate(
82
- particleCount: number,
83
- duration: number,
84
- colors: string[],
85
- particleSize: number,
86
- force: number,
87
- floorHeight: number,
88
- floorWidth: number
89
- ) {
90
- const isSafeInteger = Number.isSafeInteger;
91
- if (!isUndefined(particleCount) && isSafeInteger(particleCount) && particleCount < 0) {
92
- error('particleCount must be a positive integer');
93
- return false;
94
- }
95
-
96
- if (!isUndefined(duration) && isSafeInteger(duration) && duration < 0) {
97
- error('duration must be a positive integer');
98
- return false;
99
- }
100
-
101
- if (!isUndefined(colors) && !Array.isArray(colors)) {
102
- error('colors must be an array of strings');
103
- return false;
104
- }
105
-
106
- if (!isUndefined(particleSize) && isSafeInteger(particleSize) && particleSize < 0) {
107
- error('particleSize must be a positive integer');
108
- return false;
109
- }
110
-
111
- if (!isUndefined(force) && isSafeInteger(force) && (force < 0 || force > 1)) {
112
- error('force must be a positive integer and should be within 0 and 1');
113
- return false;
114
- }
115
-
116
- if (
117
- !isUndefined(floorHeight) &&
118
- typeof floorHeight === 'number' &&
119
- isSafeInteger(floorHeight) &&
120
- floorHeight < 0
121
- ) {
122
- error('floorHeight must be a positive integer');
123
- return false;
124
- }
125
-
126
- if (
127
- !isUndefined(floorWidth) &&
128
- typeof floorWidth === 'number' &&
129
- isSafeInteger(floorWidth) &&
130
- floorWidth < 0
131
- ) {
132
- error('floorWidth must be a positive integer');
133
- return false;
134
- }
135
-
136
- return true;
137
- }
138
- </script>
139
-
140
- <script lang="ts">
141
- import { onMount } from 'svelte';
142
-
143
- /**
144
- * Number of confetti particles to create
145
- *
146
- * @default 150
147
- *
148
- * @example
149
- *
150
- * ```svelte
151
- * <ConfettiExplosion particleCount={200} />
152
- * ```
153
- */
154
- export let particleCount = PARTICLE_COUNT;
155
-
156
- /**
157
- * Duration of the animation in milliseconds
158
- *
159
- * @default 3500
160
- *
161
- * @example
162
- *
163
- * ```svelte
164
- * <ConfettiExplosion duration={5000} />
165
- * ```
166
- */
167
- export let duration = DURATION;
168
-
169
- /**
170
- * Colors to use for the confetti particles. Pass string array of colors. Can use hex colors, named colors,
171
- * CSS Variables, literally anything valid in plain CSS.
172
- *
173
- * @default ['#FFC700', '#FF0000', '#2E3191', '#41BBC7']
174
- *
175
- * @example
176
- *
177
- * ```svelte
178
- * <ConfettiExplosion colors={['var(--yellow)', 'var(--red)', '#2E3191', '#41BBC7']} />
179
- * ```
180
- */
181
- export let colors = COLORS;
182
-
183
- /**
184
- * Size of the confetti particles in pixels
185
- *
186
- * @default 12
187
- *
188
- * @example
189
- *
190
- * ```svelte
191
- * <ConfettiExplosion particleSize={20} />
192
- * ```
193
- */
194
- export let particleSize = SIZE;
195
-
196
- /**
197
- * Force of the confetti particles. Between 0 and 1. 0 is no force, 1 is maximum force.
198
- *
199
- * @default 0.5
200
- *
201
- * @example
202
- *
203
- * ```svelte
204
- * <ConfettiExplosion force={0.8} />
205
- * ```
206
- */
207
- export let force = FORCE;
208
-
209
- /**
210
- * Height of the floor in pixels. Confetti will only fall within this height.
211
- *
212
- * @default 800
213
- *
214
- * @example
215
- *
216
- * ```svelte
217
- * <ConfettiExplosion floorHeight={500} />
218
- * ```
219
- */
220
- export let floorHeight = FLOOR_HEIGHT;
221
-
222
- /**
223
- * Width of the floor in pixels. Confetti will only fall within this width.
224
- *
225
- * @default 1600
226
- *
227
- * @example
228
- *
229
- * ```svelte
230
- * <ConfettiExplosion floorWidth={1000} />
231
- * ```
232
- */
233
- export let floorWidth = FLOOR_WIDTH;
234
-
235
- /**
236
- * Whether or not destroy all confetti nodes after the `duration` period has passed. By default it destroys all nodes, to preserve memory.
237
- *
238
- * @default true
239
- *
240
- * @example
241
- *
242
- * ```svelte
243
- * <ConfettiExplosion shouldDestroyAfterDone={false} />
244
- * ```
245
- */
246
- export let shouldDestroyAfterDone = true;
247
-
248
- let isVisible = true;
249
-
250
- $: particles = createParticles(particleCount, colors);
251
-
252
- $: isValid = validate(
253
- particleCount,
254
- duration,
255
- colors,
256
- particleSize,
257
- force,
258
- floorHeight,
259
- floorWidth
260
- );
261
-
262
- onMount(async () => {
263
- await waitFor(duration);
264
-
265
- if (shouldDestroyAfterDone) {
266
- isVisible = false;
267
- }
268
- });
269
-
270
- function confettiStyles(node: HTMLDivElement, { degree }: { degree: number }) {
271
- // Get x landing point for it
272
- const landingPoint = mapRange(
273
- Math.abs(rotate(degree, 90) - 180),
274
- 0,
275
- 180,
276
- -floorWidth / 2,
277
- floorWidth / 2
278
- );
279
-
280
- // Crazy calculations for generating styles
281
- const rotation = Math.random() * (ROTATION_SPEED_MAX - ROTATION_SPEED_MIN) + ROTATION_SPEED_MIN;
282
- const rotationIndex = Math.round(Math.random() * (rotationTransforms.length - 1));
283
- const durationChaos = duration - Math.round(Math.random() * 1000);
284
- const shouldBeCrazy = Math.random() < CRAZY_PARTICLES_FREQUENCY;
285
- const isCircle = shouldBeCircle(rotationIndex);
286
-
287
- // x-axis disturbance, roughly the distance the particle will initially deviate from its target
288
- const x1 = shouldBeCrazy ? round(Math.random() * CRAZY_PARTICLE_CRAZINESS, 2) : 0;
289
- const x2 = x1 * -1;
290
- const x3 = x1;
291
- // x-axis arc of explosion, so 90deg and 270deg particles have curve of 1, 0deg and 180deg have 0
292
- const x4 = round(Math.abs(mapRange(Math.abs(rotate(degree, 90) - 180), 0, 180, -1, 1)), 4);
293
-
294
- // roughly how fast particle reaches end of its explosion curve
295
- const y1 = round(Math.random() * BEZIER_MEDIAN, 4);
296
- // roughly maps to the distance particle goes before reaching free-fall
297
- const y2 = round(Math.random() * force * (coinFlip() ? 1 : -1), 4);
298
- // roughly how soon the particle transitions from explosion to free-fall
299
- const y3 = BEZIER_MEDIAN;
300
- // roughly the ease of free-fall
301
- const y4 = round(Math.max(mapRange(Math.abs(degree - 180), 0, 180, force, -force), 0), 4);
302
-
303
- const setCSSVar = (key: string, val: string | number) => node.style.setProperty(key, val + '');
304
-
305
- setCSSVar('--x-landing-point', `${landingPoint}px`);
306
-
307
- setCSSVar('--duration-chaos', `${durationChaos}ms`);
308
-
309
- setCSSVar('--x1', `${x1}`);
310
- setCSSVar('--x2', `${x2}`);
311
- setCSSVar('--x3', `${x3}`);
312
- setCSSVar('--x4', `${x4}`);
313
-
314
- setCSSVar('--y1', `${y1}`);
315
- setCSSVar('--y2', `${y2}`);
316
- setCSSVar('--y3', `${y3}`);
317
- setCSSVar('--y4', `${y4}`);
318
-
319
- // set --width and --height here
320
- setCSSVar(
321
- '--width',
322
- `${isCircle ? particleSize : Math.round(Math.random() * 4) + particleSize / 2}px`
323
- );
324
- setCSSVar(
325
- '--height',
326
- (isCircle ? particleSize : Math.round(Math.random() * 2) + particleSize) + 'px'
327
- );
328
-
329
- setCSSVar('--rotation', `${rotationTransforms[rotationIndex].join()}`);
330
- setCSSVar('--rotation-duration', `${rotation}ms`);
331
- setCSSVar('--border-radius', `${isCircle ? '50%' : '0'}`);
332
- }
333
- </script>
334
-
335
- {#if isVisible && isValid}
336
- <div class="container" style="--floor-height: {floorHeight}px;">
337
- {#each particles as { color, degree }}
338
- <div class="particle" use:confettiStyles={{ degree }}>
339
- <div style="--bgcolor: {color};" />
340
- </div>
341
- {/each}
342
- </div>
343
- {/if}
344
-
345
- <style lang="scss">
346
- @keyframes y-axis {
347
- to {
348
- transform: translate3d(0, var(--floor-height), 0);
349
- }
350
- }
351
-
352
- @keyframes x-axis {
353
- to {
354
- transform: translate3d(var(--x-landing-point), 0, 0);
355
- }
356
- }
357
-
358
- @keyframes rotation {
359
- to {
360
- transform: rotate3d(var(--rotation), 360deg);
361
- }
362
- }
363
-
364
- .container {
365
- width: 0;
366
- height: 0;
367
-
368
- overflow: visible;
369
-
370
- position: relative;
371
- z-index: 1200;
372
- }
373
-
374
- .particle {
375
- animation: x-axis var(--duration-chaos) forwards
376
- cubic-bezier(var(--x1), var(--x2), var(--x3), var(--x4));
377
-
378
- div {
379
- position: absolute;
380
- top: 0;
381
- left: 0;
382
-
383
- animation: y-axis var(--duration-chaos) forwards
384
- cubic-bezier(var(--y1), var(--y2), var(--y3), var(--y4));
385
-
386
- width: var(--width);
387
- height: var(--height);
388
-
389
- &:before {
390
- display: block;
391
-
392
- height: 100%;
393
- width: 100%;
394
-
395
- content: '';
396
- background-color: var(--bgcolor);
397
-
398
- animation: rotation var(--rotation-duration) infinite linear;
399
-
400
- border-radius: var(--border-radius);
401
- }
402
- }
403
- }
404
- </style>
@@ -1,22 +0,0 @@
1
- <script lang="ts">
2
- import { ConfettiExplosion } from '$lib';
3
- </script>
4
-
5
- <div>
6
- <ConfettiExplosion particleCount={100} force={0.3} />
7
- </div>
8
-
9
- <style lang="scss">
10
- :global(body) {
11
- overflow: hidden;
12
- }
13
-
14
- div {
15
- position: absolute;
16
- top: 20%;
17
- left: 40%;
18
- right: 0;
19
- bottom: 0;
20
- background: rgba(255, 255, 255, 0.5);
21
- }
22
- </style>
@@ -1,5 +0,0 @@
1
- <script>
2
- import ConfettiExplosion from '$lib/ConfettiExplosion.svelte';
3
- </script>
4
-
5
- <ConfettiExplosion force={0.31} particleCount={10} />
Binary file
package/svelte.config.js DELETED
@@ -1,23 +0,0 @@
1
- import preprocess from 'svelte-preprocess';
2
-
3
- /** @type {import('@sveltejs/kit').Config} */
4
- const config = {
5
- // Consult https://github.com/sveltejs/svelte-preprocess
6
- // for more information about preprocessors
7
- preprocess: preprocess(),
8
-
9
- kit: {
10
- // hydrate the <div id="svelte"> element in src/app.html
11
- target: '#svelte',
12
-
13
- package: {
14
- emitTypes: true,
15
- dir: 'package',
16
- exports: (file) => {
17
- return file === 'index.ts';
18
- },
19
- },
20
- },
21
- };
22
-
23
- export default config;
package/tsconfig.json DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "moduleResolution": "node",
4
- "module": "es2020",
5
- "lib": ["es2020", "DOM"],
6
- "target": "es2020",
7
- /**
8
- svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
9
- to enforce using \`import type\` instead of \`import\` for Types.
10
- */
11
- "importsNotUsedAsValues": "error",
12
- "isolatedModules": true,
13
- "resolveJsonModule": true,
14
- /**
15
- To have warnings/errors of the Svelte compiler at the correct position,
16
- enable source maps by default.
17
- */
18
- "sourceMap": true,
19
- "esModuleInterop": true,
20
- "skipLibCheck": true,
21
- "forceConsistentCasingInFileNames": true,
22
- "baseUrl": ".",
23
- "allowJs": true,
24
- "checkJs": true,
25
- "paths": {
26
- "$lib": ["src/lib"],
27
- "$lib/*": ["src/lib/*"]
28
- }
29
- },
30
- "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
31
- }