@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.js
CHANGED
|
@@ -202,6 +202,12 @@ var getElementRects = (accumulationMap) => {
|
|
|
202
202
|
};
|
|
203
203
|
|
|
204
204
|
// src/utils/snowfall/physics.ts
|
|
205
|
+
var OPACITY_BUCKETS = [0.3, 0.5, 0.7, 0.9];
|
|
206
|
+
var quantizeOpacity = (opacity) => {
|
|
207
|
+
return OPACITY_BUCKETS.reduce(
|
|
208
|
+
(prev, curr) => Math.abs(curr - opacity) < Math.abs(prev - opacity) ? curr : prev
|
|
209
|
+
);
|
|
210
|
+
};
|
|
205
211
|
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
206
212
|
const x = Math.random() * worldWidth;
|
|
207
213
|
const dna = Math.random();
|
|
@@ -237,13 +243,20 @@ var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
|
237
243
|
wobbleScale: 0.02
|
|
238
244
|
};
|
|
239
245
|
const radius = profile.sizeMin + sizeRatio * profile.sizeRange;
|
|
246
|
+
const glowRadius = radius * 1.5;
|
|
247
|
+
const rawOpacity = profile.opacityBase + sizeRatio * profile.opacityScale;
|
|
248
|
+
const opacity = quantizeOpacity(rawOpacity);
|
|
249
|
+
const rawGlowOpacity = opacity * 0.2;
|
|
250
|
+
const glowOpacity = quantizeOpacity(rawGlowOpacity);
|
|
240
251
|
return {
|
|
241
252
|
x,
|
|
242
253
|
y: window.scrollY - 5,
|
|
243
254
|
radius,
|
|
255
|
+
glowRadius,
|
|
244
256
|
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
245
257
|
wind: (noise.wind - 0.5) * profile.windScale,
|
|
246
|
-
opacity
|
|
258
|
+
opacity,
|
|
259
|
+
glowOpacity,
|
|
247
260
|
wobble: noise.wobblePhase * TAU,
|
|
248
261
|
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
249
262
|
sizeRatio,
|
|
@@ -332,6 +345,8 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
332
345
|
leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),
|
|
333
346
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
334
347
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
348
|
+
leftMax: existing?.leftSide.length === height ? existing.leftMax : 0,
|
|
349
|
+
rightMax: existing?.rightSide.length === height ? existing.rightMax : 0,
|
|
335
350
|
borderRadius,
|
|
336
351
|
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
337
352
|
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
@@ -339,9 +354,10 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
339
354
|
});
|
|
340
355
|
});
|
|
341
356
|
};
|
|
342
|
-
var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius, config) => {
|
|
357
|
+
var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius, config, currentMax) => {
|
|
343
358
|
const spread = 4;
|
|
344
359
|
const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);
|
|
360
|
+
let newMax = currentMax;
|
|
345
361
|
for (let dy = -spread; dy <= spread; dy++) {
|
|
346
362
|
const y = localY + dy;
|
|
347
363
|
if (y >= 0 && y < sideArray.length) {
|
|
@@ -350,9 +366,12 @@ var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius
|
|
|
350
366
|
if (borderRadius > 0 && (inTop || inBottom)) continue;
|
|
351
367
|
const normalizedDist = Math.abs(dy) / spread;
|
|
352
368
|
const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;
|
|
353
|
-
|
|
369
|
+
const newHeight = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);
|
|
370
|
+
sideArray[y] = newHeight;
|
|
371
|
+
if (newHeight > newMax) newMax = newHeight;
|
|
354
372
|
}
|
|
355
373
|
}
|
|
374
|
+
return newMax;
|
|
356
375
|
};
|
|
357
376
|
var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldHeight) => {
|
|
358
377
|
const scrollX = window.scrollX;
|
|
@@ -378,13 +397,13 @@ var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldH
|
|
|
378
397
|
const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);
|
|
379
398
|
if (flakeViewportX >= rect.left - 5 && flakeViewportX < rect.left + 3) {
|
|
380
399
|
if (!isCorner) {
|
|
381
|
-
accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
400
|
+
acc.leftMax = accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config, acc.leftMax);
|
|
382
401
|
landed = true;
|
|
383
402
|
}
|
|
384
403
|
}
|
|
385
404
|
if (!landed && flakeViewportX > rect.right - 3 && flakeViewportX <= rect.right + 5) {
|
|
386
405
|
if (!isCorner) {
|
|
387
|
-
accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
406
|
+
acc.rightMax = accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config, acc.rightMax);
|
|
388
407
|
landed = true;
|
|
389
408
|
}
|
|
390
409
|
}
|
|
@@ -451,10 +470,20 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
451
470
|
for (let i = 0; i < acc.heights.length; i++) {
|
|
452
471
|
if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);
|
|
453
472
|
}
|
|
473
|
+
let leftMax = 0;
|
|
474
|
+
let rightMax = 0;
|
|
454
475
|
for (let i = 0; i < acc.leftSide.length; i++) {
|
|
455
|
-
if (acc.leftSide[i] > 0)
|
|
456
|
-
|
|
476
|
+
if (acc.leftSide[i] > 0) {
|
|
477
|
+
acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);
|
|
478
|
+
if (acc.leftSide[i] > leftMax) leftMax = acc.leftSide[i];
|
|
479
|
+
}
|
|
480
|
+
if (acc.rightSide[i] > 0) {
|
|
481
|
+
acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);
|
|
482
|
+
if (acc.rightSide[i] > rightMax) rightMax = acc.rightSide[i];
|
|
483
|
+
}
|
|
457
484
|
}
|
|
485
|
+
acc.leftMax = leftMax;
|
|
486
|
+
acc.rightMax = rightMax;
|
|
458
487
|
}
|
|
459
488
|
};
|
|
460
489
|
|
|
@@ -465,43 +494,40 @@ var drawSnowflakes = (ctx, flakes) => {
|
|
|
465
494
|
if (flakes.length === 0) return;
|
|
466
495
|
ctx.fillStyle = "#FFFFFF";
|
|
467
496
|
for (const flake of flakes) {
|
|
497
|
+
if (!flake.isBackground) {
|
|
498
|
+
ctx.globalAlpha = flake.glowOpacity;
|
|
499
|
+
ctx.beginPath();
|
|
500
|
+
ctx.arc(flake.x, flake.y, flake.glowRadius, 0, TAU);
|
|
501
|
+
ctx.fill();
|
|
502
|
+
}
|
|
468
503
|
ctx.globalAlpha = flake.opacity;
|
|
469
504
|
ctx.beginPath();
|
|
470
505
|
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
471
506
|
ctx.fill();
|
|
472
507
|
}
|
|
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
508
|
ctx.globalAlpha = 1;
|
|
481
509
|
};
|
|
482
|
-
var drawAccumulations = (ctx, elementRects) => {
|
|
510
|
+
var drawAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
483
511
|
ctx.fillStyle = ACC_FILL_STYLE;
|
|
484
512
|
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
485
513
|
ctx.shadowBlur = 4;
|
|
486
514
|
ctx.shadowOffsetY = -1;
|
|
487
515
|
ctx.globalAlpha = 1;
|
|
488
|
-
const currentScrollX = window.scrollX;
|
|
489
|
-
const currentScrollY = window.scrollY;
|
|
490
516
|
ctx.beginPath();
|
|
491
517
|
for (const item of elementRects) {
|
|
492
518
|
const { rect, acc } = item;
|
|
493
519
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
494
|
-
const dx = currentScrollX;
|
|
495
|
-
const dy = currentScrollY;
|
|
496
520
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
497
521
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
522
|
+
const worldLeft = rect.left + scrollX;
|
|
523
|
+
const worldBaseY = baseY + scrollY;
|
|
498
524
|
let first = true;
|
|
499
525
|
const step = 2;
|
|
500
526
|
const len = acc.heights.length;
|
|
501
527
|
for (let x = 0; x < len; x += step) {
|
|
502
528
|
const height = acc.heights[x] || 0;
|
|
503
|
-
const px =
|
|
504
|
-
const py =
|
|
529
|
+
const px = worldLeft + x;
|
|
530
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
505
531
|
if (first) {
|
|
506
532
|
ctx.moveTo(px, py);
|
|
507
533
|
first = false;
|
|
@@ -512,60 +538,59 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
512
538
|
if ((len - 1) % step !== 0) {
|
|
513
539
|
const x = len - 1;
|
|
514
540
|
const height = acc.heights[x] || 0;
|
|
515
|
-
const px =
|
|
516
|
-
const py =
|
|
541
|
+
const px = worldLeft + x;
|
|
542
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
517
543
|
ctx.lineTo(px, py);
|
|
518
544
|
}
|
|
519
545
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
520
|
-
const px =
|
|
521
|
-
const py =
|
|
546
|
+
const px = worldLeft + x;
|
|
547
|
+
const py = worldBaseY + (acc.curveOffsets[x] || 0);
|
|
522
548
|
ctx.lineTo(px, py);
|
|
523
549
|
}
|
|
524
550
|
const startX = 0;
|
|
525
|
-
const startPx =
|
|
526
|
-
const startPy =
|
|
551
|
+
const startPx = worldLeft + startX;
|
|
552
|
+
const startPy = worldBaseY + (acc.curveOffsets[startX] || 0);
|
|
527
553
|
ctx.lineTo(startPx, startPy);
|
|
528
554
|
}
|
|
529
555
|
ctx.fill();
|
|
530
556
|
ctx.shadowBlur = 0;
|
|
531
557
|
ctx.shadowOffsetY = 0;
|
|
532
558
|
};
|
|
533
|
-
var drawSideAccumulations = (ctx, elementRects) => {
|
|
559
|
+
var drawSideAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
534
560
|
ctx.fillStyle = ACC_FILL_STYLE;
|
|
535
561
|
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
536
562
|
ctx.shadowBlur = 3;
|
|
537
563
|
ctx.globalAlpha = 1;
|
|
538
|
-
const currentScrollX = window.scrollX;
|
|
539
|
-
const currentScrollY = window.scrollY;
|
|
540
564
|
ctx.beginPath();
|
|
541
565
|
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
542
566
|
const baseX = isLeft ? rect.left : rect.right;
|
|
543
|
-
|
|
567
|
+
const worldBaseX = baseX + dx;
|
|
568
|
+
const worldTop = rect.top + dy;
|
|
569
|
+
const worldBottom = rect.bottom + dy;
|
|
570
|
+
ctx.moveTo(worldBaseX, worldTop);
|
|
544
571
|
for (let y = 0; y < sideArray.length; y += 2) {
|
|
545
572
|
const width = sideArray[y] || 0;
|
|
546
573
|
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
547
574
|
const nextWidth = sideArray[nextY] || 0;
|
|
548
575
|
const gravityMultiplier = multipliers[y] || 0;
|
|
549
|
-
const py =
|
|
550
|
-
const px =
|
|
551
|
-
const ny =
|
|
576
|
+
const py = worldTop + y;
|
|
577
|
+
const px = isLeft ? worldBaseX - width * gravityMultiplier : worldBaseX + width * gravityMultiplier;
|
|
578
|
+
const ny = worldTop + nextY;
|
|
552
579
|
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
553
|
-
const nx =
|
|
580
|
+
const nx = isLeft ? worldBaseX - nextWidth * nGravityMultiplier : worldBaseX + nextWidth * nGravityMultiplier;
|
|
554
581
|
ctx.lineTo(px, py);
|
|
555
582
|
ctx.lineTo(nx, ny);
|
|
556
583
|
}
|
|
557
|
-
ctx.lineTo(
|
|
584
|
+
ctx.lineTo(worldBaseX, worldBottom);
|
|
558
585
|
};
|
|
559
586
|
for (const item of elementRects) {
|
|
560
587
|
const { rect, acc } = item;
|
|
561
588
|
if (acc.maxSideHeight === 0) continue;
|
|
562
|
-
const hasLeftSnow = acc.
|
|
563
|
-
const hasRightSnow = acc.
|
|
589
|
+
const hasLeftSnow = acc.leftMax > 0.3;
|
|
590
|
+
const hasRightSnow = acc.rightMax > 0.3;
|
|
564
591
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, dx, dy);
|
|
568
|
-
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, dx, dy);
|
|
592
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
593
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
569
594
|
}
|
|
570
595
|
ctx.fill();
|
|
571
596
|
ctx.shadowBlur = 0;
|
|
@@ -584,6 +609,7 @@ function Snowfall() {
|
|
|
584
609
|
const snowflakesRef = (0, import_react2.useRef)([]);
|
|
585
610
|
const accumulationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
586
611
|
const animationIdRef = (0, import_react2.useRef)(0);
|
|
612
|
+
const dprRef = (0, import_react2.useRef)(1);
|
|
587
613
|
const fpsFrames = (0, import_react2.useRef)([]);
|
|
588
614
|
const metricsRef = (0, import_react2.useRef)({
|
|
589
615
|
scanTime: 0,
|
|
@@ -617,6 +643,7 @@ function Snowfall() {
|
|
|
617
643
|
const newWidth = window.innerWidth;
|
|
618
644
|
const newHeight = window.innerHeight;
|
|
619
645
|
const dpr = window.devicePixelRatio || 1;
|
|
646
|
+
dprRef.current = dpr;
|
|
620
647
|
canvasRef.current.width = newWidth * dpr;
|
|
621
648
|
canvasRef.current.height = newHeight * dpr;
|
|
622
649
|
canvasRef.current.style.width = `${newWidth}px`;
|
|
@@ -672,7 +699,7 @@ function Snowfall() {
|
|
|
672
699
|
const dt = deltaTime / 16.67;
|
|
673
700
|
const frameStartTime = performance.now();
|
|
674
701
|
const clearStart = performance.now();
|
|
675
|
-
const dpr =
|
|
702
|
+
const dpr = dprRef.current;
|
|
676
703
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
677
704
|
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
678
705
|
const scrollX = window.scrollX;
|
|
@@ -697,11 +724,22 @@ function Snowfall() {
|
|
|
697
724
|
const drawStart = performance.now();
|
|
698
725
|
drawSnowflakes(ctx, snowflakes);
|
|
699
726
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
700
|
-
const
|
|
701
|
-
|
|
727
|
+
const currentFps = fpsFrames.current.length;
|
|
728
|
+
const shouldSpawn = currentFps >= 40 || Math.random() < 0.2;
|
|
729
|
+
if (shouldSpawn) {
|
|
730
|
+
const isBackground = Math.random() < 0.4;
|
|
731
|
+
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const viewportWidth = window.innerWidth;
|
|
735
|
+
const viewportHeight = window.innerHeight;
|
|
736
|
+
const visibleRects = elementRects.filter(
|
|
737
|
+
({ rect }) => rect.right >= 0 && rect.left <= viewportWidth && rect.bottom >= 0 && rect.top <= viewportHeight
|
|
738
|
+
);
|
|
739
|
+
if (visibleRects.length > 0) {
|
|
740
|
+
drawAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
741
|
+
drawSideAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
702
742
|
}
|
|
703
|
-
drawAccumulations(ctx, elementRects);
|
|
704
|
-
drawSideAccumulations(ctx, elementRects);
|
|
705
743
|
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
706
744
|
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
707
745
|
if (currentTime - lastMetricsUpdate > 500) {
|