@hdcodedev/snowfall 1.0.6 → 1.0.7
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.js +161 -122
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +161 -122
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -2
package/dist/index.mjs
CHANGED
|
@@ -88,6 +88,7 @@ var TAG_HEADER = "header";
|
|
|
88
88
|
var TAG_FOOTER = "footer";
|
|
89
89
|
var ROLE_BANNER = "banner";
|
|
90
90
|
var ROLE_CONTENTINFO = "contentinfo";
|
|
91
|
+
var TAU = Math.PI * 2;
|
|
91
92
|
|
|
92
93
|
// src/utils/snowfall/dom.ts
|
|
93
94
|
var BOTTOM_TAGS = [TAG_HEADER];
|
|
@@ -173,37 +174,99 @@ var getElementRects = (accumulationMap) => {
|
|
|
173
174
|
|
|
174
175
|
// src/utils/snowfall/physics.ts
|
|
175
176
|
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
177
|
+
const x = Math.random() * worldWidth;
|
|
178
|
+
const dna = Math.random();
|
|
179
|
+
const noise = {
|
|
180
|
+
speed: dna * 13 % 1,
|
|
181
|
+
wind: dna * 7 % 1,
|
|
182
|
+
wobblePhase: dna * 23 % 1,
|
|
183
|
+
wobbleSpeed: dna * 5 % 1
|
|
184
|
+
};
|
|
185
|
+
const { MIN, MAX } = config.FLAKE_SIZE;
|
|
186
|
+
const sizeRatio = dna;
|
|
187
|
+
const profile = isBackground ? {
|
|
188
|
+
sizeMin: MIN * 0.6,
|
|
189
|
+
sizeRange: (MAX - MIN) * 0.4,
|
|
190
|
+
speedBase: 0.2,
|
|
191
|
+
speedScale: 0.3,
|
|
192
|
+
noiseSpeedScale: 0.2,
|
|
193
|
+
windScale: config.WIND_STRENGTH * 0.625,
|
|
194
|
+
opacityBase: 0.2,
|
|
195
|
+
opacityScale: 0.2,
|
|
196
|
+
wobbleBase: 5e-3,
|
|
197
|
+
wobbleScale: 0.015
|
|
198
|
+
} : {
|
|
199
|
+
sizeMin: MIN,
|
|
200
|
+
sizeRange: MAX - MIN,
|
|
201
|
+
speedBase: 0.5,
|
|
202
|
+
speedScale: 0.5,
|
|
203
|
+
noiseSpeedScale: 0.3,
|
|
204
|
+
windScale: config.WIND_STRENGTH,
|
|
205
|
+
opacityBase: 0.5,
|
|
206
|
+
opacityScale: 0.3,
|
|
207
|
+
wobbleBase: 0.01,
|
|
208
|
+
wobbleScale: 0.02
|
|
209
|
+
};
|
|
210
|
+
const radius = profile.sizeMin + sizeRatio * profile.sizeRange;
|
|
211
|
+
return {
|
|
212
|
+
x,
|
|
213
|
+
y: window.scrollY - 5,
|
|
214
|
+
radius,
|
|
215
|
+
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
216
|
+
wind: (noise.wind - 0.5) * profile.windScale,
|
|
217
|
+
opacity: profile.opacityBase + sizeRatio * profile.opacityScale,
|
|
218
|
+
wobble: noise.wobblePhase * TAU,
|
|
219
|
+
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
220
|
+
sizeRatio,
|
|
221
|
+
isBackground
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
var initializeMaxHeights = (width, baseMax, borderRadius, isBottom = false) => {
|
|
225
|
+
let maxHeights = new Array(width);
|
|
226
|
+
for (let i = 0; i < width; i++) {
|
|
227
|
+
let edgeFactor = 1;
|
|
228
|
+
if (!isBottom && borderRadius > 0) {
|
|
229
|
+
if (i < borderRadius) {
|
|
230
|
+
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
231
|
+
} else if (i > width - borderRadius) {
|
|
232
|
+
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
236
|
+
}
|
|
237
|
+
const smoothPasses = 4;
|
|
238
|
+
for (let p = 0; p < smoothPasses; p++) {
|
|
239
|
+
const smoothed = [...maxHeights];
|
|
240
|
+
for (let i = 1; i < width - 1; i++) {
|
|
241
|
+
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
242
|
+
}
|
|
243
|
+
maxHeights = smoothed;
|
|
244
|
+
}
|
|
245
|
+
return maxHeights;
|
|
246
|
+
};
|
|
247
|
+
var calculateCurveOffsets = (width, borderRadius, isBottom) => {
|
|
248
|
+
const offsets = new Array(width).fill(0);
|
|
249
|
+
if (borderRadius <= 0 || isBottom) return offsets;
|
|
250
|
+
for (let x = 0; x < width; x++) {
|
|
251
|
+
let offset = 0;
|
|
252
|
+
if (x < borderRadius) {
|
|
253
|
+
const dist = borderRadius - x;
|
|
254
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
255
|
+
} else if (x > width - borderRadius) {
|
|
256
|
+
const dist = x - (width - borderRadius);
|
|
257
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
258
|
+
}
|
|
259
|
+
offsets[x] = offset;
|
|
206
260
|
}
|
|
261
|
+
return offsets;
|
|
262
|
+
};
|
|
263
|
+
var calculateGravityMultipliers = (height) => {
|
|
264
|
+
const multipliers = new Array(height);
|
|
265
|
+
for (let i = 0; i < height; i++) {
|
|
266
|
+
const ratio = i / height;
|
|
267
|
+
multipliers[i] = Math.sqrt(ratio);
|
|
268
|
+
}
|
|
269
|
+
return multipliers;
|
|
207
270
|
};
|
|
208
271
|
var initializeAccumulation = (accumulationMap, config) => {
|
|
209
272
|
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
@@ -222,6 +285,10 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
222
285
|
if (existing.borderRadius !== void 0) {
|
|
223
286
|
const styleBuffer = window.getComputedStyle(el);
|
|
224
287
|
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
288
|
+
existing.curveOffsets = calculateCurveOffsets(width, existing.borderRadius, isBottom);
|
|
289
|
+
if (existing.leftSide.length === Math.ceil(rect.height) && !existing.sideGravityMultipliers) {
|
|
290
|
+
existing.sideGravityMultipliers = calculateGravityMultipliers(existing.leftSide.length);
|
|
291
|
+
}
|
|
225
292
|
}
|
|
226
293
|
return;
|
|
227
294
|
}
|
|
@@ -229,26 +296,7 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
229
296
|
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
230
297
|
const styles = window.getComputedStyle(el);
|
|
231
298
|
const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
|
|
232
|
-
|
|
233
|
-
for (let i = 0; i < width; i++) {
|
|
234
|
-
let edgeFactor = 1;
|
|
235
|
-
if (!isBottom && borderRadius > 0) {
|
|
236
|
-
if (i < borderRadius) {
|
|
237
|
-
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
238
|
-
} else if (i > width - borderRadius) {
|
|
239
|
-
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
243
|
-
}
|
|
244
|
-
const smoothPasses = 4;
|
|
245
|
-
for (let p = 0; p < smoothPasses; p++) {
|
|
246
|
-
const smoothed = [...maxHeights];
|
|
247
|
-
for (let i = 1; i < width - 1; i++) {
|
|
248
|
-
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
249
|
-
}
|
|
250
|
-
maxHeights = smoothed;
|
|
251
|
-
}
|
|
299
|
+
const maxHeights = initializeMaxHeights(width, baseMax, borderRadius, isBottom);
|
|
252
300
|
accumulationMap.set(el, {
|
|
253
301
|
heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
|
|
254
302
|
maxHeights,
|
|
@@ -256,6 +304,8 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
256
304
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
257
305
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
258
306
|
borderRadius,
|
|
307
|
+
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
308
|
+
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
259
309
|
type
|
|
260
310
|
});
|
|
261
311
|
});
|
|
@@ -380,26 +430,35 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
380
430
|
};
|
|
381
431
|
|
|
382
432
|
// src/utils/snowfall/draw.ts
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
ctx.
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
433
|
+
var ACC_FILL_STYLE = "rgba(255, 255, 255, 0.95)";
|
|
434
|
+
var ACC_SHADOW_COLOR = "rgba(200, 230, 255, 0.6)";
|
|
435
|
+
var drawSnowflakes = (ctx, flakes) => {
|
|
436
|
+
if (flakes.length === 0) return;
|
|
437
|
+
ctx.fillStyle = "#FFFFFF";
|
|
438
|
+
for (const flake of flakes) {
|
|
439
|
+
ctx.globalAlpha = flake.opacity;
|
|
440
|
+
ctx.beginPath();
|
|
441
|
+
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
442
|
+
ctx.fill();
|
|
443
|
+
}
|
|
444
|
+
for (const flake of flakes) {
|
|
445
|
+
if (flake.isBackground) continue;
|
|
446
|
+
ctx.globalAlpha = flake.opacity * 0.2;
|
|
447
|
+
ctx.beginPath();
|
|
448
|
+
ctx.arc(flake.x, flake.y, flake.radius * 1.5, 0, TAU);
|
|
449
|
+
ctx.fill();
|
|
450
|
+
}
|
|
451
|
+
ctx.globalAlpha = 1;
|
|
392
452
|
};
|
|
393
453
|
var drawAccumulations = (ctx, elementRects) => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
};
|
|
400
|
-
setupCtx(ctx);
|
|
454
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
455
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
456
|
+
ctx.shadowBlur = 4;
|
|
457
|
+
ctx.shadowOffsetY = -1;
|
|
458
|
+
ctx.globalAlpha = 1;
|
|
401
459
|
const currentScrollX = window.scrollX;
|
|
402
460
|
const currentScrollY = window.scrollY;
|
|
461
|
+
ctx.beginPath();
|
|
403
462
|
for (const item of elementRects) {
|
|
404
463
|
const { rect, acc } = item;
|
|
405
464
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
@@ -407,27 +466,13 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
407
466
|
const dy = currentScrollY;
|
|
408
467
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
409
468
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
410
|
-
const borderRadius = acc.borderRadius;
|
|
411
|
-
const getCurveOffset = (xPos) => {
|
|
412
|
-
if (borderRadius <= 0 || isBottom) return 0;
|
|
413
|
-
let offset = 0;
|
|
414
|
-
if (xPos < borderRadius) {
|
|
415
|
-
const dist = borderRadius - xPos;
|
|
416
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
417
|
-
} else if (xPos > rect.width - borderRadius) {
|
|
418
|
-
const dist = xPos - (rect.width - borderRadius);
|
|
419
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
420
|
-
}
|
|
421
|
-
return offset;
|
|
422
|
-
};
|
|
423
|
-
ctx.beginPath();
|
|
424
469
|
let first = true;
|
|
425
470
|
const step = 2;
|
|
426
471
|
const len = acc.heights.length;
|
|
427
472
|
for (let x = 0; x < len; x += step) {
|
|
428
473
|
const height = acc.heights[x] || 0;
|
|
429
474
|
const px = rect.left + x + dx;
|
|
430
|
-
const py = baseY - height +
|
|
475
|
+
const py = baseY - height + (acc.curveOffsets[x] || 0) + dy;
|
|
431
476
|
if (first) {
|
|
432
477
|
ctx.moveTo(px, py);
|
|
433
478
|
first = false;
|
|
@@ -439,33 +484,49 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
439
484
|
const x = len - 1;
|
|
440
485
|
const height = acc.heights[x] || 0;
|
|
441
486
|
const px = rect.left + x + dx;
|
|
442
|
-
const py = baseY - height +
|
|
487
|
+
const py = baseY - height + (acc.curveOffsets[x] || 0) + dy;
|
|
443
488
|
ctx.lineTo(px, py);
|
|
444
489
|
}
|
|
445
490
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
446
491
|
const px = rect.left + x + dx;
|
|
447
|
-
const py = baseY +
|
|
492
|
+
const py = baseY + (acc.curveOffsets[x] || 0) + dy;
|
|
448
493
|
ctx.lineTo(px, py);
|
|
449
494
|
}
|
|
450
495
|
const startX = 0;
|
|
451
496
|
const startPx = rect.left + startX + dx;
|
|
452
|
-
const startPy = baseY +
|
|
497
|
+
const startPy = baseY + (acc.curveOffsets[startX] || 0) + dy;
|
|
453
498
|
ctx.lineTo(startPx, startPy);
|
|
454
|
-
ctx.closePath();
|
|
455
|
-
ctx.fill();
|
|
456
499
|
}
|
|
500
|
+
ctx.fill();
|
|
457
501
|
ctx.shadowBlur = 0;
|
|
458
502
|
ctx.shadowOffsetY = 0;
|
|
459
503
|
};
|
|
460
504
|
var drawSideAccumulations = (ctx, elementRects) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
};
|
|
466
|
-
setupCtx(ctx);
|
|
505
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
506
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
507
|
+
ctx.shadowBlur = 3;
|
|
508
|
+
ctx.globalAlpha = 1;
|
|
467
509
|
const currentScrollX = window.scrollX;
|
|
468
510
|
const currentScrollY = window.scrollY;
|
|
511
|
+
ctx.beginPath();
|
|
512
|
+
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
513
|
+
const baseX = isLeft ? rect.left : rect.right;
|
|
514
|
+
ctx.moveTo(baseX + dx, rect.top + dy);
|
|
515
|
+
for (let y = 0; y < sideArray.length; y += 2) {
|
|
516
|
+
const width = sideArray[y] || 0;
|
|
517
|
+
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
518
|
+
const nextWidth = sideArray[nextY] || 0;
|
|
519
|
+
const gravityMultiplier = multipliers[y] || 0;
|
|
520
|
+
const py = rect.top + y + dy;
|
|
521
|
+
const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
|
|
522
|
+
const ny = rect.top + nextY + dy;
|
|
523
|
+
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
524
|
+
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
525
|
+
ctx.lineTo(px, py);
|
|
526
|
+
ctx.lineTo(nx, ny);
|
|
527
|
+
}
|
|
528
|
+
ctx.lineTo(baseX + dx, rect.bottom + dy);
|
|
529
|
+
};
|
|
469
530
|
for (const item of elementRects) {
|
|
470
531
|
const { rect, acc } = item;
|
|
471
532
|
if (acc.maxSideHeight === 0) continue;
|
|
@@ -474,32 +535,10 @@ var drawSideAccumulations = (ctx, elementRects) => {
|
|
|
474
535
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
475
536
|
const dx = currentScrollX;
|
|
476
537
|
const dy = currentScrollY;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const baseX = isLeft ? rect.left : rect.right;
|
|
480
|
-
ctx.moveTo(baseX + dx, rect.top + dy);
|
|
481
|
-
for (let y = 0; y < sideArray.length; y += 2) {
|
|
482
|
-
const width = sideArray[y] || 0;
|
|
483
|
-
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
484
|
-
const nextWidth = sideArray[nextY] || 0;
|
|
485
|
-
const heightRatio = y / sideArray.length;
|
|
486
|
-
const gravityMultiplier = Math.pow(heightRatio, 1.5);
|
|
487
|
-
const py = rect.top + y + dy;
|
|
488
|
-
const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
|
|
489
|
-
const ny = rect.top + nextY + dy;
|
|
490
|
-
const nRatio = nextY / sideArray.length;
|
|
491
|
-
const nGravityMultiplier = Math.pow(nRatio, 1.5);
|
|
492
|
-
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
493
|
-
ctx.lineTo(px, py);
|
|
494
|
-
ctx.lineTo(nx, ny);
|
|
495
|
-
}
|
|
496
|
-
ctx.lineTo(baseX + dx, rect.bottom + dy);
|
|
497
|
-
ctx.closePath();
|
|
498
|
-
ctx.fill();
|
|
499
|
-
};
|
|
500
|
-
if (hasLeftSnow) drawSide(acc.leftSide, true);
|
|
501
|
-
if (hasRightSnow) drawSide(acc.rightSide, false);
|
|
538
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, dx, dy);
|
|
539
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, dx, dy);
|
|
502
540
|
}
|
|
541
|
+
ctx.fill();
|
|
503
542
|
ctx.shadowBlur = 0;
|
|
504
543
|
};
|
|
505
544
|
|
|
@@ -527,7 +566,7 @@ function Snowfall() {
|
|
|
527
566
|
drawTime: 0
|
|
528
567
|
});
|
|
529
568
|
useEffect(() => {
|
|
530
|
-
setIsMounted(true);
|
|
569
|
+
requestAnimationFrame(() => setIsMounted(true));
|
|
531
570
|
}, []);
|
|
532
571
|
useEffect(() => {
|
|
533
572
|
isEnabledRef.current = isEnabled;
|
|
@@ -583,7 +622,9 @@ function Snowfall() {
|
|
|
583
622
|
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
584
623
|
};
|
|
585
624
|
initAccumulationWrapper();
|
|
586
|
-
|
|
625
|
+
requestAnimationFrame(() => {
|
|
626
|
+
if (isMounted) setIsVisible(true);
|
|
627
|
+
});
|
|
587
628
|
let lastTime = 0;
|
|
588
629
|
let lastMetricsUpdate = 0;
|
|
589
630
|
let elementRects = [];
|
|
@@ -625,9 +666,7 @@ function Snowfall() {
|
|
|
625
666
|
);
|
|
626
667
|
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
627
668
|
const drawStart = performance.now();
|
|
628
|
-
|
|
629
|
-
drawSnowflake(ctx, flake);
|
|
630
|
-
}
|
|
669
|
+
drawSnowflakes(ctx, snowflakes);
|
|
631
670
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
632
671
|
const isBackground = Math.random() < 0.4;
|
|
633
672
|
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|