@hdcodedev/snowfall 1.0.6 → 1.0.8
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 +201 -140
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +201 -140
- 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];
|
|
@@ -201,38 +202,113 @@ var getElementRects = (accumulationMap) => {
|
|
|
201
202
|
};
|
|
202
203
|
|
|
203
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
|
+
};
|
|
204
211
|
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
|
-
|
|
212
|
+
const x = Math.random() * worldWidth;
|
|
213
|
+
const dna = Math.random();
|
|
214
|
+
const noise = {
|
|
215
|
+
speed: dna * 13 % 1,
|
|
216
|
+
wind: dna * 7 % 1,
|
|
217
|
+
wobblePhase: dna * 23 % 1,
|
|
218
|
+
wobbleSpeed: dna * 5 % 1
|
|
219
|
+
};
|
|
220
|
+
const { MIN, MAX } = config.FLAKE_SIZE;
|
|
221
|
+
const sizeRatio = dna;
|
|
222
|
+
const profile = isBackground ? {
|
|
223
|
+
sizeMin: MIN * 0.6,
|
|
224
|
+
sizeRange: (MAX - MIN) * 0.4,
|
|
225
|
+
speedBase: 0.2,
|
|
226
|
+
speedScale: 0.3,
|
|
227
|
+
noiseSpeedScale: 0.2,
|
|
228
|
+
windScale: config.WIND_STRENGTH * 0.625,
|
|
229
|
+
opacityBase: 0.2,
|
|
230
|
+
opacityScale: 0.2,
|
|
231
|
+
wobbleBase: 5e-3,
|
|
232
|
+
wobbleScale: 0.015
|
|
233
|
+
} : {
|
|
234
|
+
sizeMin: MIN,
|
|
235
|
+
sizeRange: MAX - MIN,
|
|
236
|
+
speedBase: 0.5,
|
|
237
|
+
speedScale: 0.5,
|
|
238
|
+
noiseSpeedScale: 0.3,
|
|
239
|
+
windScale: config.WIND_STRENGTH,
|
|
240
|
+
opacityBase: 0.5,
|
|
241
|
+
opacityScale: 0.3,
|
|
242
|
+
wobbleBase: 0.01,
|
|
243
|
+
wobbleScale: 0.02
|
|
244
|
+
};
|
|
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);
|
|
251
|
+
return {
|
|
252
|
+
x,
|
|
253
|
+
y: window.scrollY - 5,
|
|
254
|
+
radius,
|
|
255
|
+
glowRadius,
|
|
256
|
+
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
257
|
+
wind: (noise.wind - 0.5) * profile.windScale,
|
|
258
|
+
opacity,
|
|
259
|
+
glowOpacity,
|
|
260
|
+
wobble: noise.wobblePhase * TAU,
|
|
261
|
+
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
262
|
+
sizeRatio,
|
|
263
|
+
isBackground
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
var initializeMaxHeights = (width, baseMax, borderRadius, isBottom = false) => {
|
|
267
|
+
let maxHeights = new Array(width);
|
|
268
|
+
for (let i = 0; i < width; i++) {
|
|
269
|
+
let edgeFactor = 1;
|
|
270
|
+
if (!isBottom && borderRadius > 0) {
|
|
271
|
+
if (i < borderRadius) {
|
|
272
|
+
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
273
|
+
} else if (i > width - borderRadius) {
|
|
274
|
+
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
235
278
|
}
|
|
279
|
+
const smoothPasses = 4;
|
|
280
|
+
for (let p = 0; p < smoothPasses; p++) {
|
|
281
|
+
const smoothed = [...maxHeights];
|
|
282
|
+
for (let i = 1; i < width - 1; i++) {
|
|
283
|
+
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
284
|
+
}
|
|
285
|
+
maxHeights = smoothed;
|
|
286
|
+
}
|
|
287
|
+
return maxHeights;
|
|
288
|
+
};
|
|
289
|
+
var calculateCurveOffsets = (width, borderRadius, isBottom) => {
|
|
290
|
+
const offsets = new Array(width).fill(0);
|
|
291
|
+
if (borderRadius <= 0 || isBottom) return offsets;
|
|
292
|
+
for (let x = 0; x < width; x++) {
|
|
293
|
+
let offset = 0;
|
|
294
|
+
if (x < borderRadius) {
|
|
295
|
+
const dist = borderRadius - x;
|
|
296
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
297
|
+
} else if (x > width - borderRadius) {
|
|
298
|
+
const dist = x - (width - borderRadius);
|
|
299
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
300
|
+
}
|
|
301
|
+
offsets[x] = offset;
|
|
302
|
+
}
|
|
303
|
+
return offsets;
|
|
304
|
+
};
|
|
305
|
+
var calculateGravityMultipliers = (height) => {
|
|
306
|
+
const multipliers = new Array(height);
|
|
307
|
+
for (let i = 0; i < height; i++) {
|
|
308
|
+
const ratio = i / height;
|
|
309
|
+
multipliers[i] = Math.sqrt(ratio);
|
|
310
|
+
}
|
|
311
|
+
return multipliers;
|
|
236
312
|
};
|
|
237
313
|
var initializeAccumulation = (accumulationMap, config) => {
|
|
238
314
|
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
@@ -251,6 +327,10 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
251
327
|
if (existing.borderRadius !== void 0) {
|
|
252
328
|
const styleBuffer = window.getComputedStyle(el);
|
|
253
329
|
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
330
|
+
existing.curveOffsets = calculateCurveOffsets(width, existing.borderRadius, isBottom);
|
|
331
|
+
if (existing.leftSide.length === Math.ceil(rect.height) && !existing.sideGravityMultipliers) {
|
|
332
|
+
existing.sideGravityMultipliers = calculateGravityMultipliers(existing.leftSide.length);
|
|
333
|
+
}
|
|
254
334
|
}
|
|
255
335
|
return;
|
|
256
336
|
}
|
|
@@ -258,26 +338,7 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
258
338
|
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
259
339
|
const styles = window.getComputedStyle(el);
|
|
260
340
|
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
|
-
}
|
|
341
|
+
const maxHeights = initializeMaxHeights(width, baseMax, borderRadius, isBottom);
|
|
281
342
|
accumulationMap.set(el, {
|
|
282
343
|
heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
|
|
283
344
|
maxHeights,
|
|
@@ -285,6 +346,8 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
285
346
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
286
347
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
287
348
|
borderRadius,
|
|
349
|
+
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
350
|
+
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
288
351
|
type
|
|
289
352
|
});
|
|
290
353
|
});
|
|
@@ -409,54 +472,46 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
409
472
|
};
|
|
410
473
|
|
|
411
474
|
// src/utils/snowfall/draw.ts
|
|
412
|
-
var
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
ctx.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
475
|
+
var ACC_FILL_STYLE = "rgba(255, 255, 255, 0.95)";
|
|
476
|
+
var ACC_SHADOW_COLOR = "rgba(200, 230, 255, 0.6)";
|
|
477
|
+
var drawSnowflakes = (ctx, flakes) => {
|
|
478
|
+
if (flakes.length === 0) return;
|
|
479
|
+
ctx.fillStyle = "#FFFFFF";
|
|
480
|
+
for (const flake of flakes) {
|
|
481
|
+
if (!flake.isBackground) {
|
|
482
|
+
ctx.globalAlpha = flake.glowOpacity;
|
|
483
|
+
ctx.beginPath();
|
|
484
|
+
ctx.arc(flake.x, flake.y, flake.glowRadius, 0, TAU);
|
|
485
|
+
ctx.fill();
|
|
486
|
+
}
|
|
487
|
+
ctx.globalAlpha = flake.opacity;
|
|
488
|
+
ctx.beginPath();
|
|
489
|
+
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
490
|
+
ctx.fill();
|
|
491
|
+
}
|
|
492
|
+
ctx.globalAlpha = 1;
|
|
421
493
|
};
|
|
422
|
-
var drawAccumulations = (ctx, elementRects) => {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
setupCtx(ctx);
|
|
430
|
-
const currentScrollX = window.scrollX;
|
|
431
|
-
const currentScrollY = window.scrollY;
|
|
494
|
+
var drawAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
495
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
496
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
497
|
+
ctx.shadowBlur = 4;
|
|
498
|
+
ctx.shadowOffsetY = -1;
|
|
499
|
+
ctx.globalAlpha = 1;
|
|
500
|
+
ctx.beginPath();
|
|
432
501
|
for (const item of elementRects) {
|
|
433
502
|
const { rect, acc } = item;
|
|
434
503
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
435
|
-
const dx = currentScrollX;
|
|
436
|
-
const dy = currentScrollY;
|
|
437
504
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
438
505
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
439
|
-
const
|
|
440
|
-
const
|
|
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();
|
|
506
|
+
const worldLeft = rect.left + scrollX;
|
|
507
|
+
const worldBaseY = baseY + scrollY;
|
|
453
508
|
let first = true;
|
|
454
509
|
const step = 2;
|
|
455
510
|
const len = acc.heights.length;
|
|
456
511
|
for (let x = 0; x < len; x += step) {
|
|
457
512
|
const height = acc.heights[x] || 0;
|
|
458
|
-
const px =
|
|
459
|
-
const py =
|
|
513
|
+
const px = worldLeft + x;
|
|
514
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
460
515
|
if (first) {
|
|
461
516
|
ctx.moveTo(px, py);
|
|
462
517
|
first = false;
|
|
@@ -467,68 +522,61 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
467
522
|
if ((len - 1) % step !== 0) {
|
|
468
523
|
const x = len - 1;
|
|
469
524
|
const height = acc.heights[x] || 0;
|
|
470
|
-
const px =
|
|
471
|
-
const py =
|
|
525
|
+
const px = worldLeft + x;
|
|
526
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
472
527
|
ctx.lineTo(px, py);
|
|
473
528
|
}
|
|
474
529
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
475
|
-
const px =
|
|
476
|
-
const py =
|
|
530
|
+
const px = worldLeft + x;
|
|
531
|
+
const py = worldBaseY + (acc.curveOffsets[x] || 0);
|
|
477
532
|
ctx.lineTo(px, py);
|
|
478
533
|
}
|
|
479
534
|
const startX = 0;
|
|
480
|
-
const startPx =
|
|
481
|
-
const startPy =
|
|
535
|
+
const startPx = worldLeft + startX;
|
|
536
|
+
const startPy = worldBaseY + (acc.curveOffsets[startX] || 0);
|
|
482
537
|
ctx.lineTo(startPx, startPy);
|
|
483
|
-
ctx.closePath();
|
|
484
|
-
ctx.fill();
|
|
485
538
|
}
|
|
539
|
+
ctx.fill();
|
|
486
540
|
ctx.shadowBlur = 0;
|
|
487
541
|
ctx.shadowOffsetY = 0;
|
|
488
542
|
};
|
|
489
|
-
var drawSideAccumulations = (ctx, elementRects) => {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
var drawSideAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
544
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
545
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
546
|
+
ctx.shadowBlur = 3;
|
|
547
|
+
ctx.globalAlpha = 1;
|
|
548
|
+
ctx.beginPath();
|
|
549
|
+
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
550
|
+
const baseX = isLeft ? rect.left : rect.right;
|
|
551
|
+
const worldBaseX = baseX + dx;
|
|
552
|
+
const worldTop = rect.top + dy;
|
|
553
|
+
const worldBottom = rect.bottom + dy;
|
|
554
|
+
ctx.moveTo(worldBaseX, worldTop);
|
|
555
|
+
for (let y = 0; y < sideArray.length; y += 2) {
|
|
556
|
+
const width = sideArray[y] || 0;
|
|
557
|
+
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
558
|
+
const nextWidth = sideArray[nextY] || 0;
|
|
559
|
+
const gravityMultiplier = multipliers[y] || 0;
|
|
560
|
+
const py = worldTop + y;
|
|
561
|
+
const px = isLeft ? worldBaseX - width * gravityMultiplier : worldBaseX + width * gravityMultiplier;
|
|
562
|
+
const ny = worldTop + nextY;
|
|
563
|
+
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
564
|
+
const nx = isLeft ? worldBaseX - nextWidth * nGravityMultiplier : worldBaseX + nextWidth * nGravityMultiplier;
|
|
565
|
+
ctx.lineTo(px, py);
|
|
566
|
+
ctx.lineTo(nx, ny);
|
|
567
|
+
}
|
|
568
|
+
ctx.lineTo(worldBaseX, worldBottom);
|
|
494
569
|
};
|
|
495
|
-
setupCtx(ctx);
|
|
496
|
-
const currentScrollX = window.scrollX;
|
|
497
|
-
const currentScrollY = window.scrollY;
|
|
498
570
|
for (const item of elementRects) {
|
|
499
571
|
const { rect, acc } = item;
|
|
500
572
|
if (acc.maxSideHeight === 0) continue;
|
|
501
573
|
const hasLeftSnow = acc.leftSide.some((h) => h > 0.3);
|
|
502
574
|
const hasRightSnow = acc.rightSide.some((h) => h > 0.3);
|
|
503
575
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const drawSide = (sideArray, isLeft) => {
|
|
507
|
-
ctx.beginPath();
|
|
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);
|
|
576
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
577
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
531
578
|
}
|
|
579
|
+
ctx.fill();
|
|
532
580
|
ctx.shadowBlur = 0;
|
|
533
581
|
};
|
|
534
582
|
|
|
@@ -545,6 +593,7 @@ function Snowfall() {
|
|
|
545
593
|
const snowflakesRef = (0, import_react2.useRef)([]);
|
|
546
594
|
const accumulationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
547
595
|
const animationIdRef = (0, import_react2.useRef)(0);
|
|
596
|
+
const dprRef = (0, import_react2.useRef)(1);
|
|
548
597
|
const fpsFrames = (0, import_react2.useRef)([]);
|
|
549
598
|
const metricsRef = (0, import_react2.useRef)({
|
|
550
599
|
scanTime: 0,
|
|
@@ -556,7 +605,7 @@ function Snowfall() {
|
|
|
556
605
|
drawTime: 0
|
|
557
606
|
});
|
|
558
607
|
(0, import_react2.useEffect)(() => {
|
|
559
|
-
setIsMounted(true);
|
|
608
|
+
requestAnimationFrame(() => setIsMounted(true));
|
|
560
609
|
}, []);
|
|
561
610
|
(0, import_react2.useEffect)(() => {
|
|
562
611
|
isEnabledRef.current = isEnabled;
|
|
@@ -578,6 +627,7 @@ function Snowfall() {
|
|
|
578
627
|
const newWidth = window.innerWidth;
|
|
579
628
|
const newHeight = window.innerHeight;
|
|
580
629
|
const dpr = window.devicePixelRatio || 1;
|
|
630
|
+
dprRef.current = dpr;
|
|
581
631
|
canvasRef.current.width = newWidth * dpr;
|
|
582
632
|
canvasRef.current.height = newHeight * dpr;
|
|
583
633
|
canvasRef.current.style.width = `${newWidth}px`;
|
|
@@ -612,7 +662,9 @@ function Snowfall() {
|
|
|
612
662
|
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
613
663
|
};
|
|
614
664
|
initAccumulationWrapper();
|
|
615
|
-
|
|
665
|
+
requestAnimationFrame(() => {
|
|
666
|
+
if (isMounted) setIsVisible(true);
|
|
667
|
+
});
|
|
616
668
|
let lastTime = 0;
|
|
617
669
|
let lastMetricsUpdate = 0;
|
|
618
670
|
let elementRects = [];
|
|
@@ -631,7 +683,7 @@ function Snowfall() {
|
|
|
631
683
|
const dt = deltaTime / 16.67;
|
|
632
684
|
const frameStartTime = performance.now();
|
|
633
685
|
const clearStart = performance.now();
|
|
634
|
-
const dpr =
|
|
686
|
+
const dpr = dprRef.current;
|
|
635
687
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
636
688
|
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
637
689
|
const scrollX = window.scrollX;
|
|
@@ -654,15 +706,24 @@ function Snowfall() {
|
|
|
654
706
|
);
|
|
655
707
|
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
656
708
|
const drawStart = performance.now();
|
|
657
|
-
|
|
658
|
-
drawSnowflake(ctx, flake);
|
|
659
|
-
}
|
|
709
|
+
drawSnowflakes(ctx, snowflakes);
|
|
660
710
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
661
|
-
const
|
|
662
|
-
|
|
711
|
+
const currentFps = fpsFrames.current.length;
|
|
712
|
+
const shouldSpawn = currentFps >= 40 || Math.random() < 0.2;
|
|
713
|
+
if (shouldSpawn) {
|
|
714
|
+
const isBackground = Math.random() < 0.4;
|
|
715
|
+
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const viewportWidth = window.innerWidth;
|
|
719
|
+
const viewportHeight = window.innerHeight;
|
|
720
|
+
const visibleRects = elementRects.filter(
|
|
721
|
+
({ rect }) => rect.right >= 0 && rect.left <= viewportWidth && rect.bottom >= 0 && rect.top <= viewportHeight
|
|
722
|
+
);
|
|
723
|
+
if (visibleRects.length > 0) {
|
|
724
|
+
drawAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
725
|
+
drawSideAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
663
726
|
}
|
|
664
|
-
drawAccumulations(ctx, elementRects);
|
|
665
|
-
drawSideAccumulations(ctx, elementRects);
|
|
666
727
|
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
667
728
|
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
668
729
|
if (currentTime - lastMetricsUpdate > 500) {
|