@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.js
CHANGED
|
@@ -117,6 +117,7 @@ var TAG_HEADER = "header";
|
|
|
117
117
|
var TAG_FOOTER = "footer";
|
|
118
118
|
var ROLE_BANNER = "banner";
|
|
119
119
|
var ROLE_CONTENTINFO = "contentinfo";
|
|
120
|
+
var TAU = Math.PI * 2;
|
|
120
121
|
|
|
121
122
|
// src/utils/snowfall/dom.ts
|
|
122
123
|
var BOTTOM_TAGS = [TAG_HEADER];
|
|
@@ -202,37 +203,99 @@ var getElementRects = (accumulationMap) => {
|
|
|
202
203
|
|
|
203
204
|
// src/utils/snowfall/physics.ts
|
|
204
205
|
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
206
|
+
const x = Math.random() * worldWidth;
|
|
207
|
+
const dna = Math.random();
|
|
208
|
+
const noise = {
|
|
209
|
+
speed: dna * 13 % 1,
|
|
210
|
+
wind: dna * 7 % 1,
|
|
211
|
+
wobblePhase: dna * 23 % 1,
|
|
212
|
+
wobbleSpeed: dna * 5 % 1
|
|
213
|
+
};
|
|
214
|
+
const { MIN, MAX } = config.FLAKE_SIZE;
|
|
215
|
+
const sizeRatio = dna;
|
|
216
|
+
const profile = isBackground ? {
|
|
217
|
+
sizeMin: MIN * 0.6,
|
|
218
|
+
sizeRange: (MAX - MIN) * 0.4,
|
|
219
|
+
speedBase: 0.2,
|
|
220
|
+
speedScale: 0.3,
|
|
221
|
+
noiseSpeedScale: 0.2,
|
|
222
|
+
windScale: config.WIND_STRENGTH * 0.625,
|
|
223
|
+
opacityBase: 0.2,
|
|
224
|
+
opacityScale: 0.2,
|
|
225
|
+
wobbleBase: 5e-3,
|
|
226
|
+
wobbleScale: 0.015
|
|
227
|
+
} : {
|
|
228
|
+
sizeMin: MIN,
|
|
229
|
+
sizeRange: MAX - MIN,
|
|
230
|
+
speedBase: 0.5,
|
|
231
|
+
speedScale: 0.5,
|
|
232
|
+
noiseSpeedScale: 0.3,
|
|
233
|
+
windScale: config.WIND_STRENGTH,
|
|
234
|
+
opacityBase: 0.5,
|
|
235
|
+
opacityScale: 0.3,
|
|
236
|
+
wobbleBase: 0.01,
|
|
237
|
+
wobbleScale: 0.02
|
|
238
|
+
};
|
|
239
|
+
const radius = profile.sizeMin + sizeRatio * profile.sizeRange;
|
|
240
|
+
return {
|
|
241
|
+
x,
|
|
242
|
+
y: window.scrollY - 5,
|
|
243
|
+
radius,
|
|
244
|
+
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
245
|
+
wind: (noise.wind - 0.5) * profile.windScale,
|
|
246
|
+
opacity: profile.opacityBase + sizeRatio * profile.opacityScale,
|
|
247
|
+
wobble: noise.wobblePhase * TAU,
|
|
248
|
+
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
249
|
+
sizeRatio,
|
|
250
|
+
isBackground
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
var initializeMaxHeights = (width, baseMax, borderRadius, isBottom = false) => {
|
|
254
|
+
let maxHeights = new Array(width);
|
|
255
|
+
for (let i = 0; i < width; i++) {
|
|
256
|
+
let edgeFactor = 1;
|
|
257
|
+
if (!isBottom && borderRadius > 0) {
|
|
258
|
+
if (i < borderRadius) {
|
|
259
|
+
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
260
|
+
} else if (i > width - borderRadius) {
|
|
261
|
+
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
265
|
+
}
|
|
266
|
+
const smoothPasses = 4;
|
|
267
|
+
for (let p = 0; p < smoothPasses; p++) {
|
|
268
|
+
const smoothed = [...maxHeights];
|
|
269
|
+
for (let i = 1; i < width - 1; i++) {
|
|
270
|
+
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
271
|
+
}
|
|
272
|
+
maxHeights = smoothed;
|
|
273
|
+
}
|
|
274
|
+
return maxHeights;
|
|
275
|
+
};
|
|
276
|
+
var calculateCurveOffsets = (width, borderRadius, isBottom) => {
|
|
277
|
+
const offsets = new Array(width).fill(0);
|
|
278
|
+
if (borderRadius <= 0 || isBottom) return offsets;
|
|
279
|
+
for (let x = 0; x < width; x++) {
|
|
280
|
+
let offset = 0;
|
|
281
|
+
if (x < borderRadius) {
|
|
282
|
+
const dist = borderRadius - x;
|
|
283
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
284
|
+
} else if (x > width - borderRadius) {
|
|
285
|
+
const dist = x - (width - borderRadius);
|
|
286
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
287
|
+
}
|
|
288
|
+
offsets[x] = offset;
|
|
235
289
|
}
|
|
290
|
+
return offsets;
|
|
291
|
+
};
|
|
292
|
+
var calculateGravityMultipliers = (height) => {
|
|
293
|
+
const multipliers = new Array(height);
|
|
294
|
+
for (let i = 0; i < height; i++) {
|
|
295
|
+
const ratio = i / height;
|
|
296
|
+
multipliers[i] = Math.sqrt(ratio);
|
|
297
|
+
}
|
|
298
|
+
return multipliers;
|
|
236
299
|
};
|
|
237
300
|
var initializeAccumulation = (accumulationMap, config) => {
|
|
238
301
|
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
@@ -251,6 +314,10 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
251
314
|
if (existing.borderRadius !== void 0) {
|
|
252
315
|
const styleBuffer = window.getComputedStyle(el);
|
|
253
316
|
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
317
|
+
existing.curveOffsets = calculateCurveOffsets(width, existing.borderRadius, isBottom);
|
|
318
|
+
if (existing.leftSide.length === Math.ceil(rect.height) && !existing.sideGravityMultipliers) {
|
|
319
|
+
existing.sideGravityMultipliers = calculateGravityMultipliers(existing.leftSide.length);
|
|
320
|
+
}
|
|
254
321
|
}
|
|
255
322
|
return;
|
|
256
323
|
}
|
|
@@ -258,26 +325,7 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
258
325
|
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
259
326
|
const styles = window.getComputedStyle(el);
|
|
260
327
|
const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
|
|
261
|
-
|
|
262
|
-
for (let i = 0; i < width; i++) {
|
|
263
|
-
let edgeFactor = 1;
|
|
264
|
-
if (!isBottom && borderRadius > 0) {
|
|
265
|
-
if (i < borderRadius) {
|
|
266
|
-
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
267
|
-
} else if (i > width - borderRadius) {
|
|
268
|
-
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
272
|
-
}
|
|
273
|
-
const smoothPasses = 4;
|
|
274
|
-
for (let p = 0; p < smoothPasses; p++) {
|
|
275
|
-
const smoothed = [...maxHeights];
|
|
276
|
-
for (let i = 1; i < width - 1; i++) {
|
|
277
|
-
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
278
|
-
}
|
|
279
|
-
maxHeights = smoothed;
|
|
280
|
-
}
|
|
328
|
+
const maxHeights = initializeMaxHeights(width, baseMax, borderRadius, isBottom);
|
|
281
329
|
accumulationMap.set(el, {
|
|
282
330
|
heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
|
|
283
331
|
maxHeights,
|
|
@@ -285,6 +333,8 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
285
333
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
286
334
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
287
335
|
borderRadius,
|
|
336
|
+
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
337
|
+
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
288
338
|
type
|
|
289
339
|
});
|
|
290
340
|
});
|
|
@@ -409,26 +459,35 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
409
459
|
};
|
|
410
460
|
|
|
411
461
|
// src/utils/snowfall/draw.ts
|
|
412
|
-
var
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
ctx.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
462
|
+
var ACC_FILL_STYLE = "rgba(255, 255, 255, 0.95)";
|
|
463
|
+
var ACC_SHADOW_COLOR = "rgba(200, 230, 255, 0.6)";
|
|
464
|
+
var drawSnowflakes = (ctx, flakes) => {
|
|
465
|
+
if (flakes.length === 0) return;
|
|
466
|
+
ctx.fillStyle = "#FFFFFF";
|
|
467
|
+
for (const flake of flakes) {
|
|
468
|
+
ctx.globalAlpha = flake.opacity;
|
|
469
|
+
ctx.beginPath();
|
|
470
|
+
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
471
|
+
ctx.fill();
|
|
472
|
+
}
|
|
473
|
+
for (const flake of flakes) {
|
|
474
|
+
if (flake.isBackground) continue;
|
|
475
|
+
ctx.globalAlpha = flake.opacity * 0.2;
|
|
476
|
+
ctx.beginPath();
|
|
477
|
+
ctx.arc(flake.x, flake.y, flake.radius * 1.5, 0, TAU);
|
|
478
|
+
ctx.fill();
|
|
479
|
+
}
|
|
480
|
+
ctx.globalAlpha = 1;
|
|
421
481
|
};
|
|
422
482
|
var drawAccumulations = (ctx, elementRects) => {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
};
|
|
429
|
-
setupCtx(ctx);
|
|
483
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
484
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
485
|
+
ctx.shadowBlur = 4;
|
|
486
|
+
ctx.shadowOffsetY = -1;
|
|
487
|
+
ctx.globalAlpha = 1;
|
|
430
488
|
const currentScrollX = window.scrollX;
|
|
431
489
|
const currentScrollY = window.scrollY;
|
|
490
|
+
ctx.beginPath();
|
|
432
491
|
for (const item of elementRects) {
|
|
433
492
|
const { rect, acc } = item;
|
|
434
493
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
@@ -436,27 +495,13 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
436
495
|
const dy = currentScrollY;
|
|
437
496
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
438
497
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
439
|
-
const borderRadius = acc.borderRadius;
|
|
440
|
-
const getCurveOffset = (xPos) => {
|
|
441
|
-
if (borderRadius <= 0 || isBottom) return 0;
|
|
442
|
-
let offset = 0;
|
|
443
|
-
if (xPos < borderRadius) {
|
|
444
|
-
const dist = borderRadius - xPos;
|
|
445
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
446
|
-
} else if (xPos > rect.width - borderRadius) {
|
|
447
|
-
const dist = xPos - (rect.width - borderRadius);
|
|
448
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
449
|
-
}
|
|
450
|
-
return offset;
|
|
451
|
-
};
|
|
452
|
-
ctx.beginPath();
|
|
453
498
|
let first = true;
|
|
454
499
|
const step = 2;
|
|
455
500
|
const len = acc.heights.length;
|
|
456
501
|
for (let x = 0; x < len; x += step) {
|
|
457
502
|
const height = acc.heights[x] || 0;
|
|
458
503
|
const px = rect.left + x + dx;
|
|
459
|
-
const py = baseY - height +
|
|
504
|
+
const py = baseY - height + (acc.curveOffsets[x] || 0) + dy;
|
|
460
505
|
if (first) {
|
|
461
506
|
ctx.moveTo(px, py);
|
|
462
507
|
first = false;
|
|
@@ -468,33 +513,49 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
468
513
|
const x = len - 1;
|
|
469
514
|
const height = acc.heights[x] || 0;
|
|
470
515
|
const px = rect.left + x + dx;
|
|
471
|
-
const py = baseY - height +
|
|
516
|
+
const py = baseY - height + (acc.curveOffsets[x] || 0) + dy;
|
|
472
517
|
ctx.lineTo(px, py);
|
|
473
518
|
}
|
|
474
519
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
475
520
|
const px = rect.left + x + dx;
|
|
476
|
-
const py = baseY +
|
|
521
|
+
const py = baseY + (acc.curveOffsets[x] || 0) + dy;
|
|
477
522
|
ctx.lineTo(px, py);
|
|
478
523
|
}
|
|
479
524
|
const startX = 0;
|
|
480
525
|
const startPx = rect.left + startX + dx;
|
|
481
|
-
const startPy = baseY +
|
|
526
|
+
const startPy = baseY + (acc.curveOffsets[startX] || 0) + dy;
|
|
482
527
|
ctx.lineTo(startPx, startPy);
|
|
483
|
-
ctx.closePath();
|
|
484
|
-
ctx.fill();
|
|
485
528
|
}
|
|
529
|
+
ctx.fill();
|
|
486
530
|
ctx.shadowBlur = 0;
|
|
487
531
|
ctx.shadowOffsetY = 0;
|
|
488
532
|
};
|
|
489
533
|
var drawSideAccumulations = (ctx, elementRects) => {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
};
|
|
495
|
-
setupCtx(ctx);
|
|
534
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
535
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
536
|
+
ctx.shadowBlur = 3;
|
|
537
|
+
ctx.globalAlpha = 1;
|
|
496
538
|
const currentScrollX = window.scrollX;
|
|
497
539
|
const currentScrollY = window.scrollY;
|
|
540
|
+
ctx.beginPath();
|
|
541
|
+
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
542
|
+
const baseX = isLeft ? rect.left : rect.right;
|
|
543
|
+
ctx.moveTo(baseX + dx, rect.top + dy);
|
|
544
|
+
for (let y = 0; y < sideArray.length; y += 2) {
|
|
545
|
+
const width = sideArray[y] || 0;
|
|
546
|
+
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
547
|
+
const nextWidth = sideArray[nextY] || 0;
|
|
548
|
+
const gravityMultiplier = multipliers[y] || 0;
|
|
549
|
+
const py = rect.top + y + dy;
|
|
550
|
+
const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
|
|
551
|
+
const ny = rect.top + nextY + dy;
|
|
552
|
+
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
553
|
+
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
554
|
+
ctx.lineTo(px, py);
|
|
555
|
+
ctx.lineTo(nx, ny);
|
|
556
|
+
}
|
|
557
|
+
ctx.lineTo(baseX + dx, rect.bottom + dy);
|
|
558
|
+
};
|
|
498
559
|
for (const item of elementRects) {
|
|
499
560
|
const { rect, acc } = item;
|
|
500
561
|
if (acc.maxSideHeight === 0) continue;
|
|
@@ -503,32 +564,10 @@ var drawSideAccumulations = (ctx, elementRects) => {
|
|
|
503
564
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
504
565
|
const dx = currentScrollX;
|
|
505
566
|
const dy = currentScrollY;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const baseX = isLeft ? rect.left : rect.right;
|
|
509
|
-
ctx.moveTo(baseX + dx, rect.top + dy);
|
|
510
|
-
for (let y = 0; y < sideArray.length; y += 2) {
|
|
511
|
-
const width = sideArray[y] || 0;
|
|
512
|
-
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
513
|
-
const nextWidth = sideArray[nextY] || 0;
|
|
514
|
-
const heightRatio = y / sideArray.length;
|
|
515
|
-
const gravityMultiplier = Math.pow(heightRatio, 1.5);
|
|
516
|
-
const py = rect.top + y + dy;
|
|
517
|
-
const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
|
|
518
|
-
const ny = rect.top + nextY + dy;
|
|
519
|
-
const nRatio = nextY / sideArray.length;
|
|
520
|
-
const nGravityMultiplier = Math.pow(nRatio, 1.5);
|
|
521
|
-
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
522
|
-
ctx.lineTo(px, py);
|
|
523
|
-
ctx.lineTo(nx, ny);
|
|
524
|
-
}
|
|
525
|
-
ctx.lineTo(baseX + dx, rect.bottom + dy);
|
|
526
|
-
ctx.closePath();
|
|
527
|
-
ctx.fill();
|
|
528
|
-
};
|
|
529
|
-
if (hasLeftSnow) drawSide(acc.leftSide, true);
|
|
530
|
-
if (hasRightSnow) drawSide(acc.rightSide, false);
|
|
567
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, dx, dy);
|
|
568
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, dx, dy);
|
|
531
569
|
}
|
|
570
|
+
ctx.fill();
|
|
532
571
|
ctx.shadowBlur = 0;
|
|
533
572
|
};
|
|
534
573
|
|
|
@@ -556,7 +595,7 @@ function Snowfall() {
|
|
|
556
595
|
drawTime: 0
|
|
557
596
|
});
|
|
558
597
|
(0, import_react2.useEffect)(() => {
|
|
559
|
-
setIsMounted(true);
|
|
598
|
+
requestAnimationFrame(() => setIsMounted(true));
|
|
560
599
|
}, []);
|
|
561
600
|
(0, import_react2.useEffect)(() => {
|
|
562
601
|
isEnabledRef.current = isEnabled;
|
|
@@ -612,7 +651,9 @@ function Snowfall() {
|
|
|
612
651
|
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
613
652
|
};
|
|
614
653
|
initAccumulationWrapper();
|
|
615
|
-
|
|
654
|
+
requestAnimationFrame(() => {
|
|
655
|
+
if (isMounted) setIsVisible(true);
|
|
656
|
+
});
|
|
616
657
|
let lastTime = 0;
|
|
617
658
|
let lastMetricsUpdate = 0;
|
|
618
659
|
let elementRects = [];
|
|
@@ -654,9 +695,7 @@ function Snowfall() {
|
|
|
654
695
|
);
|
|
655
696
|
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
656
697
|
const drawStart = performance.now();
|
|
657
|
-
|
|
658
|
-
drawSnowflake(ctx, flake);
|
|
659
|
-
}
|
|
698
|
+
drawSnowflakes(ctx, snowflakes);
|
|
660
699
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
661
700
|
const isBackground = Math.random() < 0.4;
|
|
662
701
|
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|