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.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-confetti-explosion",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "svelte": "./ConfettiExplosion.svelte",
5
5
  "devDependencies": {
6
6
  "@sveltejs/kit": "^1.0.0-next.197",
@@ -15,14 +15,8 @@
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
+ "exports": {
19
+ "./package.json": "./package.json",
20
+ ".": "./index.js"
27
21
  }
28
22
  }
package/.prettierrc DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "useTabs": true,
3
- "singleQuote": true,
4
- "printWidth": 100
5
- }