@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.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];
|
|
@@ -172,38 +173,113 @@ var getElementRects = (accumulationMap) => {
|
|
|
172
173
|
};
|
|
173
174
|
|
|
174
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
|
+
};
|
|
175
182
|
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
|
-
|
|
183
|
+
const x = Math.random() * worldWidth;
|
|
184
|
+
const dna = Math.random();
|
|
185
|
+
const noise = {
|
|
186
|
+
speed: dna * 13 % 1,
|
|
187
|
+
wind: dna * 7 % 1,
|
|
188
|
+
wobblePhase: dna * 23 % 1,
|
|
189
|
+
wobbleSpeed: dna * 5 % 1
|
|
190
|
+
};
|
|
191
|
+
const { MIN, MAX } = config.FLAKE_SIZE;
|
|
192
|
+
const sizeRatio = dna;
|
|
193
|
+
const profile = isBackground ? {
|
|
194
|
+
sizeMin: MIN * 0.6,
|
|
195
|
+
sizeRange: (MAX - MIN) * 0.4,
|
|
196
|
+
speedBase: 0.2,
|
|
197
|
+
speedScale: 0.3,
|
|
198
|
+
noiseSpeedScale: 0.2,
|
|
199
|
+
windScale: config.WIND_STRENGTH * 0.625,
|
|
200
|
+
opacityBase: 0.2,
|
|
201
|
+
opacityScale: 0.2,
|
|
202
|
+
wobbleBase: 5e-3,
|
|
203
|
+
wobbleScale: 0.015
|
|
204
|
+
} : {
|
|
205
|
+
sizeMin: MIN,
|
|
206
|
+
sizeRange: MAX - MIN,
|
|
207
|
+
speedBase: 0.5,
|
|
208
|
+
speedScale: 0.5,
|
|
209
|
+
noiseSpeedScale: 0.3,
|
|
210
|
+
windScale: config.WIND_STRENGTH,
|
|
211
|
+
opacityBase: 0.5,
|
|
212
|
+
opacityScale: 0.3,
|
|
213
|
+
wobbleBase: 0.01,
|
|
214
|
+
wobbleScale: 0.02
|
|
215
|
+
};
|
|
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);
|
|
222
|
+
return {
|
|
223
|
+
x,
|
|
224
|
+
y: window.scrollY - 5,
|
|
225
|
+
radius,
|
|
226
|
+
glowRadius,
|
|
227
|
+
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
228
|
+
wind: (noise.wind - 0.5) * profile.windScale,
|
|
229
|
+
opacity,
|
|
230
|
+
glowOpacity,
|
|
231
|
+
wobble: noise.wobblePhase * TAU,
|
|
232
|
+
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
233
|
+
sizeRatio,
|
|
234
|
+
isBackground
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
var initializeMaxHeights = (width, baseMax, borderRadius, isBottom = false) => {
|
|
238
|
+
let maxHeights = new Array(width);
|
|
239
|
+
for (let i = 0; i < width; i++) {
|
|
240
|
+
let edgeFactor = 1;
|
|
241
|
+
if (!isBottom && borderRadius > 0) {
|
|
242
|
+
if (i < borderRadius) {
|
|
243
|
+
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
244
|
+
} else if (i > width - borderRadius) {
|
|
245
|
+
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
206
249
|
}
|
|
250
|
+
const smoothPasses = 4;
|
|
251
|
+
for (let p = 0; p < smoothPasses; p++) {
|
|
252
|
+
const smoothed = [...maxHeights];
|
|
253
|
+
for (let i = 1; i < width - 1; i++) {
|
|
254
|
+
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
255
|
+
}
|
|
256
|
+
maxHeights = smoothed;
|
|
257
|
+
}
|
|
258
|
+
return maxHeights;
|
|
259
|
+
};
|
|
260
|
+
var calculateCurveOffsets = (width, borderRadius, isBottom) => {
|
|
261
|
+
const offsets = new Array(width).fill(0);
|
|
262
|
+
if (borderRadius <= 0 || isBottom) return offsets;
|
|
263
|
+
for (let x = 0; x < width; x++) {
|
|
264
|
+
let offset = 0;
|
|
265
|
+
if (x < borderRadius) {
|
|
266
|
+
const dist = borderRadius - x;
|
|
267
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
268
|
+
} else if (x > width - borderRadius) {
|
|
269
|
+
const dist = x - (width - borderRadius);
|
|
270
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
271
|
+
}
|
|
272
|
+
offsets[x] = offset;
|
|
273
|
+
}
|
|
274
|
+
return offsets;
|
|
275
|
+
};
|
|
276
|
+
var calculateGravityMultipliers = (height) => {
|
|
277
|
+
const multipliers = new Array(height);
|
|
278
|
+
for (let i = 0; i < height; i++) {
|
|
279
|
+
const ratio = i / height;
|
|
280
|
+
multipliers[i] = Math.sqrt(ratio);
|
|
281
|
+
}
|
|
282
|
+
return multipliers;
|
|
207
283
|
};
|
|
208
284
|
var initializeAccumulation = (accumulationMap, config) => {
|
|
209
285
|
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
@@ -222,6 +298,10 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
222
298
|
if (existing.borderRadius !== void 0) {
|
|
223
299
|
const styleBuffer = window.getComputedStyle(el);
|
|
224
300
|
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
301
|
+
existing.curveOffsets = calculateCurveOffsets(width, existing.borderRadius, isBottom);
|
|
302
|
+
if (existing.leftSide.length === Math.ceil(rect.height) && !existing.sideGravityMultipliers) {
|
|
303
|
+
existing.sideGravityMultipliers = calculateGravityMultipliers(existing.leftSide.length);
|
|
304
|
+
}
|
|
225
305
|
}
|
|
226
306
|
return;
|
|
227
307
|
}
|
|
@@ -229,26 +309,7 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
229
309
|
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
230
310
|
const styles = window.getComputedStyle(el);
|
|
231
311
|
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
|
-
}
|
|
312
|
+
const maxHeights = initializeMaxHeights(width, baseMax, borderRadius, isBottom);
|
|
252
313
|
accumulationMap.set(el, {
|
|
253
314
|
heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
|
|
254
315
|
maxHeights,
|
|
@@ -256,6 +317,8 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
256
317
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
257
318
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
258
319
|
borderRadius,
|
|
320
|
+
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
321
|
+
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
259
322
|
type
|
|
260
323
|
});
|
|
261
324
|
});
|
|
@@ -380,54 +443,46 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
380
443
|
};
|
|
381
444
|
|
|
382
445
|
// src/utils/snowfall/draw.ts
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
ctx.
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
446
|
+
var ACC_FILL_STYLE = "rgba(255, 255, 255, 0.95)";
|
|
447
|
+
var ACC_SHADOW_COLOR = "rgba(200, 230, 255, 0.6)";
|
|
448
|
+
var drawSnowflakes = (ctx, flakes) => {
|
|
449
|
+
if (flakes.length === 0) return;
|
|
450
|
+
ctx.fillStyle = "#FFFFFF";
|
|
451
|
+
for (const flake of flakes) {
|
|
452
|
+
if (!flake.isBackground) {
|
|
453
|
+
ctx.globalAlpha = flake.glowOpacity;
|
|
454
|
+
ctx.beginPath();
|
|
455
|
+
ctx.arc(flake.x, flake.y, flake.glowRadius, 0, TAU);
|
|
456
|
+
ctx.fill();
|
|
457
|
+
}
|
|
458
|
+
ctx.globalAlpha = flake.opacity;
|
|
459
|
+
ctx.beginPath();
|
|
460
|
+
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
461
|
+
ctx.fill();
|
|
462
|
+
}
|
|
463
|
+
ctx.globalAlpha = 1;
|
|
392
464
|
};
|
|
393
|
-
var drawAccumulations = (ctx, elementRects) => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
setupCtx(ctx);
|
|
401
|
-
const currentScrollX = window.scrollX;
|
|
402
|
-
const currentScrollY = window.scrollY;
|
|
465
|
+
var drawAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
466
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
467
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
468
|
+
ctx.shadowBlur = 4;
|
|
469
|
+
ctx.shadowOffsetY = -1;
|
|
470
|
+
ctx.globalAlpha = 1;
|
|
471
|
+
ctx.beginPath();
|
|
403
472
|
for (const item of elementRects) {
|
|
404
473
|
const { rect, acc } = item;
|
|
405
474
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
406
|
-
const dx = currentScrollX;
|
|
407
|
-
const dy = currentScrollY;
|
|
408
475
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
409
476
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
410
|
-
const
|
|
411
|
-
const
|
|
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();
|
|
477
|
+
const worldLeft = rect.left + scrollX;
|
|
478
|
+
const worldBaseY = baseY + scrollY;
|
|
424
479
|
let first = true;
|
|
425
480
|
const step = 2;
|
|
426
481
|
const len = acc.heights.length;
|
|
427
482
|
for (let x = 0; x < len; x += step) {
|
|
428
483
|
const height = acc.heights[x] || 0;
|
|
429
|
-
const px =
|
|
430
|
-
const py =
|
|
484
|
+
const px = worldLeft + x;
|
|
485
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
431
486
|
if (first) {
|
|
432
487
|
ctx.moveTo(px, py);
|
|
433
488
|
first = false;
|
|
@@ -438,68 +493,61 @@ var drawAccumulations = (ctx, elementRects) => {
|
|
|
438
493
|
if ((len - 1) % step !== 0) {
|
|
439
494
|
const x = len - 1;
|
|
440
495
|
const height = acc.heights[x] || 0;
|
|
441
|
-
const px =
|
|
442
|
-
const py =
|
|
496
|
+
const px = worldLeft + x;
|
|
497
|
+
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
443
498
|
ctx.lineTo(px, py);
|
|
444
499
|
}
|
|
445
500
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
446
|
-
const px =
|
|
447
|
-
const py =
|
|
501
|
+
const px = worldLeft + x;
|
|
502
|
+
const py = worldBaseY + (acc.curveOffsets[x] || 0);
|
|
448
503
|
ctx.lineTo(px, py);
|
|
449
504
|
}
|
|
450
505
|
const startX = 0;
|
|
451
|
-
const startPx =
|
|
452
|
-
const startPy =
|
|
506
|
+
const startPx = worldLeft + startX;
|
|
507
|
+
const startPy = worldBaseY + (acc.curveOffsets[startX] || 0);
|
|
453
508
|
ctx.lineTo(startPx, startPy);
|
|
454
|
-
ctx.closePath();
|
|
455
|
-
ctx.fill();
|
|
456
509
|
}
|
|
510
|
+
ctx.fill();
|
|
457
511
|
ctx.shadowBlur = 0;
|
|
458
512
|
ctx.shadowOffsetY = 0;
|
|
459
513
|
};
|
|
460
|
-
var drawSideAccumulations = (ctx, elementRects) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
514
|
+
var drawSideAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
515
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
516
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
517
|
+
ctx.shadowBlur = 3;
|
|
518
|
+
ctx.globalAlpha = 1;
|
|
519
|
+
ctx.beginPath();
|
|
520
|
+
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
521
|
+
const baseX = isLeft ? rect.left : rect.right;
|
|
522
|
+
const worldBaseX = baseX + dx;
|
|
523
|
+
const worldTop = rect.top + dy;
|
|
524
|
+
const worldBottom = rect.bottom + dy;
|
|
525
|
+
ctx.moveTo(worldBaseX, worldTop);
|
|
526
|
+
for (let y = 0; y < sideArray.length; y += 2) {
|
|
527
|
+
const width = sideArray[y] || 0;
|
|
528
|
+
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
529
|
+
const nextWidth = sideArray[nextY] || 0;
|
|
530
|
+
const gravityMultiplier = multipliers[y] || 0;
|
|
531
|
+
const py = worldTop + y;
|
|
532
|
+
const px = isLeft ? worldBaseX - width * gravityMultiplier : worldBaseX + width * gravityMultiplier;
|
|
533
|
+
const ny = worldTop + nextY;
|
|
534
|
+
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
535
|
+
const nx = isLeft ? worldBaseX - nextWidth * nGravityMultiplier : worldBaseX + nextWidth * nGravityMultiplier;
|
|
536
|
+
ctx.lineTo(px, py);
|
|
537
|
+
ctx.lineTo(nx, ny);
|
|
538
|
+
}
|
|
539
|
+
ctx.lineTo(worldBaseX, worldBottom);
|
|
465
540
|
};
|
|
466
|
-
setupCtx(ctx);
|
|
467
|
-
const currentScrollX = window.scrollX;
|
|
468
|
-
const currentScrollY = window.scrollY;
|
|
469
541
|
for (const item of elementRects) {
|
|
470
542
|
const { rect, acc } = item;
|
|
471
543
|
if (acc.maxSideHeight === 0) continue;
|
|
472
544
|
const hasLeftSnow = acc.leftSide.some((h) => h > 0.3);
|
|
473
545
|
const hasRightSnow = acc.rightSide.some((h) => h > 0.3);
|
|
474
546
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const drawSide = (sideArray, isLeft) => {
|
|
478
|
-
ctx.beginPath();
|
|
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);
|
|
547
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
548
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
502
549
|
}
|
|
550
|
+
ctx.fill();
|
|
503
551
|
ctx.shadowBlur = 0;
|
|
504
552
|
};
|
|
505
553
|
|
|
@@ -516,6 +564,7 @@ function Snowfall() {
|
|
|
516
564
|
const snowflakesRef = useRef([]);
|
|
517
565
|
const accumulationRef = useRef(/* @__PURE__ */ new Map());
|
|
518
566
|
const animationIdRef = useRef(0);
|
|
567
|
+
const dprRef = useRef(1);
|
|
519
568
|
const fpsFrames = useRef([]);
|
|
520
569
|
const metricsRef = useRef({
|
|
521
570
|
scanTime: 0,
|
|
@@ -527,7 +576,7 @@ function Snowfall() {
|
|
|
527
576
|
drawTime: 0
|
|
528
577
|
});
|
|
529
578
|
useEffect(() => {
|
|
530
|
-
setIsMounted(true);
|
|
579
|
+
requestAnimationFrame(() => setIsMounted(true));
|
|
531
580
|
}, []);
|
|
532
581
|
useEffect(() => {
|
|
533
582
|
isEnabledRef.current = isEnabled;
|
|
@@ -549,6 +598,7 @@ function Snowfall() {
|
|
|
549
598
|
const newWidth = window.innerWidth;
|
|
550
599
|
const newHeight = window.innerHeight;
|
|
551
600
|
const dpr = window.devicePixelRatio || 1;
|
|
601
|
+
dprRef.current = dpr;
|
|
552
602
|
canvasRef.current.width = newWidth * dpr;
|
|
553
603
|
canvasRef.current.height = newHeight * dpr;
|
|
554
604
|
canvasRef.current.style.width = `${newWidth}px`;
|
|
@@ -583,7 +633,9 @@ function Snowfall() {
|
|
|
583
633
|
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
584
634
|
};
|
|
585
635
|
initAccumulationWrapper();
|
|
586
|
-
|
|
636
|
+
requestAnimationFrame(() => {
|
|
637
|
+
if (isMounted) setIsVisible(true);
|
|
638
|
+
});
|
|
587
639
|
let lastTime = 0;
|
|
588
640
|
let lastMetricsUpdate = 0;
|
|
589
641
|
let elementRects = [];
|
|
@@ -602,7 +654,7 @@ function Snowfall() {
|
|
|
602
654
|
const dt = deltaTime / 16.67;
|
|
603
655
|
const frameStartTime = performance.now();
|
|
604
656
|
const clearStart = performance.now();
|
|
605
|
-
const dpr =
|
|
657
|
+
const dpr = dprRef.current;
|
|
606
658
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
607
659
|
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
608
660
|
const scrollX = window.scrollX;
|
|
@@ -625,15 +677,24 @@ function Snowfall() {
|
|
|
625
677
|
);
|
|
626
678
|
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
627
679
|
const drawStart = performance.now();
|
|
628
|
-
|
|
629
|
-
drawSnowflake(ctx, flake);
|
|
630
|
-
}
|
|
680
|
+
drawSnowflakes(ctx, snowflakes);
|
|
631
681
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
632
|
-
const
|
|
633
|
-
|
|
682
|
+
const currentFps = fpsFrames.current.length;
|
|
683
|
+
const shouldSpawn = currentFps >= 40 || Math.random() < 0.2;
|
|
684
|
+
if (shouldSpawn) {
|
|
685
|
+
const isBackground = Math.random() < 0.4;
|
|
686
|
+
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const viewportWidth = window.innerWidth;
|
|
690
|
+
const viewportHeight = window.innerHeight;
|
|
691
|
+
const visibleRects = elementRects.filter(
|
|
692
|
+
({ rect }) => rect.right >= 0 && rect.left <= viewportWidth && rect.bottom >= 0 && rect.top <= viewportHeight
|
|
693
|
+
);
|
|
694
|
+
if (visibleRects.length > 0) {
|
|
695
|
+
drawAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
696
|
+
drawSideAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
634
697
|
}
|
|
635
|
-
drawAccumulations(ctx, elementRects);
|
|
636
|
-
drawSideAccumulations(ctx, elementRects);
|
|
637
698
|
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
638
699
|
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
639
700
|
if (currentTime - lastMetricsUpdate > 500) {
|