@hdcodedev/snowfall 1.0.7 → 1.0.9
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 +85 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +85 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -173,6 +173,12 @@ var getElementRects = (accumulationMap) => {
|
|
|
173
173
|
};
|
|
174
174
|
|
|
175
175
|
// src/utils/snowfall/physics.ts
|
|
176
|
+
var OPACITY_BUCKETS = [0.3, 0.5, 0.7, 0.9];
|
|
177
|
+
var quantizeOpacity = (opacity) => {
|
|
178
|
+
return OPACITY_BUCKETS.reduce(
|
|
179
|
+
(prev, curr) => Math.abs(curr - opacity) < Math.abs(prev - opacity) ? curr : prev
|
|
180
|
+
);
|
|
181
|
+
};
|
|
176
182
|
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
177
183
|
const x = Math.random() * worldWidth;
|
|
178
184
|
const dna = Math.random();
|
|
@@ -208,13 +214,20 @@ var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
|
208
214
|
wobbleScale: 0.02
|
|
209
215
|
};
|
|
210
216
|
const radius = profile.sizeMin + sizeRatio * profile.sizeRange;
|
|
217
|
+
const glowRadius = radius * 1.5;
|
|
218
|
+
const rawOpacity = profile.opacityBase + sizeRatio * profile.opacityScale;
|
|
219
|
+
const opacity = quantizeOpacity(rawOpacity);
|
|
220
|
+
const rawGlowOpacity = opacity * 0.2;
|
|
221
|
+
const glowOpacity = quantizeOpacity(rawGlowOpacity);
|
|
211
222
|
return {
|
|
212
223
|
x,
|
|
213
224
|
y: window.scrollY - 5,
|
|
214
225
|
radius,
|
|
226
|
+
glowRadius,
|
|
215
227
|
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
216
228
|
wind: (noise.wind - 0.5) * profile.windScale,
|
|
217
|
-
opacity
|
|
229
|
+
opacity,
|
|
230
|
+
glowOpacity,
|
|
218
231
|
wobble: noise.wobblePhase * TAU,
|
|
219
232
|
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
220
233
|
sizeRatio,
|
|
@@ -303,6 +316,8 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
303
316
|
leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),
|
|
304
317
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
305
318
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
319
|
+
leftMax: existing?.leftSide.length === height ? existing.leftMax : 0,
|
|
320
|
+
rightMax: existing?.rightSide.length === height ? existing.rightMax : 0,
|
|
306
321
|
borderRadius,
|
|
307
322
|
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
308
323
|
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
@@ -310,9 +325,10 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
310
325
|
});
|
|
311
326
|
});
|
|
312
327
|
};
|
|
313
|
-
var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius, config) => {
|
|
328
|
+
var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius, config, currentMax) => {
|
|
314
329
|
const spread = 4;
|
|
315
330
|
const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);
|
|
331
|
+
let newMax = currentMax;
|
|
316
332
|
for (let dy = -spread; dy <= spread; dy++) {
|
|
317
333
|
const y = localY + dy;
|
|
318
334
|
if (y >= 0 && y < sideArray.length) {
|
|
@@ -321,9 +337,12 @@ var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius
|
|
|
321
337
|
if (borderRadius > 0 && (inTop || inBottom)) continue;
|
|
322
338
|
const normalizedDist = Math.abs(dy) / spread;
|
|
323
339
|
const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;
|
|
324
|
-
|
|
340
|
+
const newHeight = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);
|
|
341
|
+
sideArray[y] = newHeight;
|
|
342
|
+
if (newHeight > newMax) newMax = newHeight;
|
|
325
343
|
}
|
|
326
344
|
}
|
|
345
|
+
return newMax;
|
|
327
346
|
};
|
|
328
347
|
var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldHeight) => {
|
|
329
348
|
const scrollX = window.scrollX;
|
|
@@ -349,13 +368,13 @@ var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldH
|
|
|
349
368
|
const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);
|
|
350
369
|
if (flakeViewportX >= rect.left - 5 && flakeViewportX < rect.left + 3) {
|
|
351
370
|
if (!isCorner) {
|
|
352
|
-
accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
371
|
+
acc.leftMax = accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config, acc.leftMax);
|
|
353
372
|
landed = true;
|
|
354
373
|
}
|
|
355
374
|
}
|
|
356
375
|
if (!landed && flakeViewportX > rect.right - 3 && flakeViewportX <= rect.right + 5) {
|
|
357
376
|
if (!isCorner) {
|
|
358
|
-
accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
377
|
+
acc.rightMax = accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config, acc.rightMax);
|
|
359
378
|
landed = true;
|
|
360
379
|
}
|
|
361
380
|
}
|
|
@@ -422,10 +441,20 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
422
441
|
for (let i = 0; i < acc.heights.length; i++) {
|
|
423
442
|
if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);
|
|
424
443
|
}
|
|
444
|
+
let leftMax = 0;
|
|
445
|
+
let rightMax = 0;
|
|
425
446
|
for (let i = 0; i < acc.leftSide.length; i++) {
|
|
426
|
-
if (acc.leftSide[i] > 0)
|
|
427
|
-
|
|
447
|
+
if (acc.leftSide[i] > 0) {
|
|
448
|
+
acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);
|
|
449
|
+
if (acc.leftSide[i] > leftMax) leftMax = acc.leftSide[i];
|
|
450
|
+
}
|
|
451
|
+
if (acc.rightSide[i] > 0) {
|
|
452
|
+
acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);
|
|
453
|
+
if (acc.rightSide[i] > rightMax) rightMax = acc.rightSide[i];
|
|
454
|
+
}
|
|
428
455
|
}
|
|
456
|
+
acc.leftMax = leftMax;
|
|
457
|
+
acc.rightMax = rightMax;
|
|
429
458
|
}
|
|
430
459
|
};
|
|
431
460
|
|
|
@@ -436,43 +465,40 @@ var drawSnowflakes = (ctx, flakes) => {
|
|
|
436
465
|
if (flakes.length === 0) return;
|
|
437
466
|
ctx.fillStyle = "#FFFFFF";
|
|
438
467
|
for (const flake of flakes) {
|
|
468
|
+
if (!flake.isBackground) {
|
|
469
|
+
ctx.globalAlpha = flake.glowOpacity;
|
|
470
|
+
ctx.beginPath();
|
|
471
|
+
ctx.arc(flake.x, flake.y, flake.glowRadius, 0, TAU);
|
|
472
|
+
ctx.fill();
|
|
473
|
+
}
|
|
439
474
|
ctx.globalAlpha = flake.opacity;
|
|
440
475
|
ctx.beginPath();
|
|
441
476
|
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
442
477
|
ctx.fill();
|
|
443
478
|
}
|
|
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
479
|
ctx.globalAlpha = 1;
|
|
452
480
|
};
|
|
453
|
-
var drawAccumulations = (ctx, elementRects) => {
|
|
481
|
+
var drawAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
454
482
|
ctx.fillStyle = ACC_FILL_STYLE;
|
|
455
483
|
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
456
484
|
ctx.shadowBlur = 4;
|
|
457
485
|
ctx.shadowOffsetY = -1;
|
|
458
486
|
ctx.globalAlpha = 1;
|
|
459
|
-
const currentScrollX = window.scrollX;
|
|
460
|
-
const currentScrollY = window.scrollY;
|
|
461
487
|
ctx.beginPath();
|
|
462
488
|
for (const item of elementRects) {
|
|
463
489
|
const { rect, acc } = item;
|
|
464
490
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
465
|
-
const dx = currentScrollX;
|
|
466
|
-
const dy = currentScrollY;
|
|
467
491
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
468
492
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
493
|
+
const worldLeft = rect.left + scrollX;
|
|
494
|
+
const worldBaseY = baseY + scrollY;
|
|
469
495
|
let first = true;
|
|
470
496
|
const step = 2;
|
|
471
497
|
const len = acc.heights.length;
|
|
472
498
|
for (let x = 0; x < len; x += step) {
|
|
473
499
|
const height = acc.heights[x] || 0;
|
|
474
|
-
const px =
|
|
475
|
-
const py =
|
|
500
|
+
const px = worldLeft + x;
|
|
501
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
476
502
|
if (first) {
|
|
477
503
|
ctx.moveTo(px, py);
|
|
478
504
|
first = false;
|
|
@@ -483,60 +509,59 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
483
509
|
if ((len - 1) % step !== 0) {
|
|
484
510
|
const x = len - 1;
|
|
485
511
|
const height = acc.heights[x] || 0;
|
|
486
|
-
const px =
|
|
487
|
-
const py =
|
|
512
|
+
const px = worldLeft + x;
|
|
513
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
488
514
|
ctx.lineTo(px, py);
|
|
489
515
|
}
|
|
490
516
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
491
|
-
const px =
|
|
492
|
-
const py =
|
|
517
|
+
const px = worldLeft + x;
|
|
518
|
+
const py = worldBaseY + (acc.curveOffsets[x] || 0);
|
|
493
519
|
ctx.lineTo(px, py);
|
|
494
520
|
}
|
|
495
521
|
const startX = 0;
|
|
496
|
-
const startPx =
|
|
497
|
-
const startPy =
|
|
522
|
+
const startPx = worldLeft + startX;
|
|
523
|
+
const startPy = worldBaseY + (acc.curveOffsets[startX] || 0);
|
|
498
524
|
ctx.lineTo(startPx, startPy);
|
|
499
525
|
}
|
|
500
526
|
ctx.fill();
|
|
501
527
|
ctx.shadowBlur = 0;
|
|
502
528
|
ctx.shadowOffsetY = 0;
|
|
503
529
|
};
|
|
504
|
-
var drawSideAccumulations = (ctx, elementRects) => {
|
|
530
|
+
var drawSideAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
505
531
|
ctx.fillStyle = ACC_FILL_STYLE;
|
|
506
532
|
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
507
533
|
ctx.shadowBlur = 3;
|
|
508
534
|
ctx.globalAlpha = 1;
|
|
509
|
-
const currentScrollX = window.scrollX;
|
|
510
|
-
const currentScrollY = window.scrollY;
|
|
511
535
|
ctx.beginPath();
|
|
512
536
|
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
513
537
|
const baseX = isLeft ? rect.left : rect.right;
|
|
514
|
-
|
|
538
|
+
const worldBaseX = baseX + dx;
|
|
539
|
+
const worldTop = rect.top + dy;
|
|
540
|
+
const worldBottom = rect.bottom + dy;
|
|
541
|
+
ctx.moveTo(worldBaseX, worldTop);
|
|
515
542
|
for (let y = 0; y < sideArray.length; y += 2) {
|
|
516
543
|
const width = sideArray[y] || 0;
|
|
517
544
|
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
518
545
|
const nextWidth = sideArray[nextY] || 0;
|
|
519
546
|
const gravityMultiplier = multipliers[y] || 0;
|
|
520
|
-
const py =
|
|
521
|
-
const px =
|
|
522
|
-
const ny =
|
|
547
|
+
const py = worldTop + y;
|
|
548
|
+
const px = isLeft ? worldBaseX - width * gravityMultiplier : worldBaseX + width * gravityMultiplier;
|
|
549
|
+
const ny = worldTop + nextY;
|
|
523
550
|
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
524
|
-
const nx =
|
|
551
|
+
const nx = isLeft ? worldBaseX - nextWidth * nGravityMultiplier : worldBaseX + nextWidth * nGravityMultiplier;
|
|
525
552
|
ctx.lineTo(px, py);
|
|
526
553
|
ctx.lineTo(nx, ny);
|
|
527
554
|
}
|
|
528
|
-
ctx.lineTo(
|
|
555
|
+
ctx.lineTo(worldBaseX, worldBottom);
|
|
529
556
|
};
|
|
530
557
|
for (const item of elementRects) {
|
|
531
558
|
const { rect, acc } = item;
|
|
532
559
|
if (acc.maxSideHeight === 0) continue;
|
|
533
|
-
const hasLeftSnow = acc.
|
|
534
|
-
const hasRightSnow = acc.
|
|
560
|
+
const hasLeftSnow = acc.leftMax > 0.3;
|
|
561
|
+
const hasRightSnow = acc.rightMax > 0.3;
|
|
535
562
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, dx, dy);
|
|
539
|
-
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, dx, dy);
|
|
563
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
564
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
540
565
|
}
|
|
541
566
|
ctx.fill();
|
|
542
567
|
ctx.shadowBlur = 0;
|
|
@@ -555,6 +580,7 @@ function Snowfall() {
|
|
|
555
580
|
const snowflakesRef = useRef([]);
|
|
556
581
|
const accumulationRef = useRef(/* @__PURE__ */ new Map());
|
|
557
582
|
const animationIdRef = useRef(0);
|
|
583
|
+
const dprRef = useRef(1);
|
|
558
584
|
const fpsFrames = useRef([]);
|
|
559
585
|
const metricsRef = useRef({
|
|
560
586
|
scanTime: 0,
|
|
@@ -588,6 +614,7 @@ function Snowfall() {
|
|
|
588
614
|
const newWidth = window.innerWidth;
|
|
589
615
|
const newHeight = window.innerHeight;
|
|
590
616
|
const dpr = window.devicePixelRatio || 1;
|
|
617
|
+
dprRef.current = dpr;
|
|
591
618
|
canvasRef.current.width = newWidth * dpr;
|
|
592
619
|
canvasRef.current.height = newHeight * dpr;
|
|
593
620
|
canvasRef.current.style.width = `${newWidth}px`;
|
|
@@ -643,7 +670,7 @@ function Snowfall() {
|
|
|
643
670
|
const dt = deltaTime / 16.67;
|
|
644
671
|
const frameStartTime = performance.now();
|
|
645
672
|
const clearStart = performance.now();
|
|
646
|
-
const dpr =
|
|
673
|
+
const dpr = dprRef.current;
|
|
647
674
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
648
675
|
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
649
676
|
const scrollX = window.scrollX;
|
|
@@ -668,11 +695,22 @@ function Snowfall() {
|
|
|
668
695
|
const drawStart = performance.now();
|
|
669
696
|
drawSnowflakes(ctx, snowflakes);
|
|
670
697
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
671
|
-
const
|
|
672
|
-
|
|
698
|
+
const currentFps = fpsFrames.current.length;
|
|
699
|
+
const shouldSpawn = currentFps >= 40 || Math.random() < 0.2;
|
|
700
|
+
if (shouldSpawn) {
|
|
701
|
+
const isBackground = Math.random() < 0.4;
|
|
702
|
+
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const viewportWidth = window.innerWidth;
|
|
706
|
+
const viewportHeight = window.innerHeight;
|
|
707
|
+
const visibleRects = elementRects.filter(
|
|
708
|
+
({ rect }) => rect.right >= 0 && rect.left <= viewportWidth && rect.bottom >= 0 && rect.top <= viewportHeight
|
|
709
|
+
);
|
|
710
|
+
if (visibleRects.length > 0) {
|
|
711
|
+
drawAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
712
|
+
drawSideAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
673
713
|
}
|
|
674
|
-
drawAccumulations(ctx, elementRects);
|
|
675
|
-
drawSideAccumulations(ctx, elementRects);
|
|
676
714
|
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
677
715
|
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
678
716
|
if (currentTime - lastMetricsUpdate > 500) {
|