svelte-confetti-explosion 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
- }