diff --git a/src/lib/Frost/FrostVanilla.ts b/src/lib/Frost/FrostVanilla.ts index 0bea98b..36ff0c3 100644 --- a/src/lib/Frost/FrostVanilla.ts +++ b/src/lib/Frost/FrostVanilla.ts @@ -845,7 +845,9 @@ export function createFrost( let prevPointerY = 0.5; let lastScrollX = 0; let lastScrollY = 0; - let queuedMelts: Array<[number, number]> = []; + const MAX_PENDING_MELTS = 32; + const MAX_MELTS_PER_FRAME = 4; + const queuedMelts: Array<[number, number]> = []; function renderPointer() { if (!pointer) return; @@ -885,7 +887,9 @@ export function createFrost( config.meltEdges ? 0 : config.edgeFade, ); if (queuedMelts.length > 0) { - for (const [mx, my] of queuedMelts) { + const count = Math.min(queuedMelts.length, MAX_MELTS_PER_FRAME); + for (let i = 0; i < count; i++) { + const [mx, my] = queuedMelts.shift()!; gl!.uniform2f(pointerProgram.uniforms.uPoint, mx, 1 - my); gl!.uniform2f(pointerProgram.uniforms.uPrevPoint, mx, 1 - my); gl!.uniform1f(pointerProgram.uniforms.uTouching, 1); @@ -897,7 +901,6 @@ export function createFrost( ); gl!.uniform2f(pointerProgram.uniforms.uBackShift, 0, 0); } - queuedMelts = []; } else { gl!.uniform2f(pointerProgram.uniforms.uPoint, pointerX, 1 - pointerY); gl!.uniform2f( @@ -1048,7 +1051,8 @@ export function createFrost( now < activeUntil || now < introStart + Math.max(config.introDuration, 0) * 1000 + 120 || contentDirty || - config.shimmer > 0.001; + config.shimmer > 0.001 || + queuedMelts.length > 0; if (!animating) { running = false; return; @@ -1062,6 +1066,13 @@ export function createFrost( raf = requestAnimationFrame(frame); } + function enqueueMelt(x: number, y: number) { + if (destroyed || !visible) return false; + if (queuedMelts.length >= MAX_PENDING_MELTS) queuedMelts.shift(); + queuedMelts.push([x, y]); + return true; + } + wake = start; start(); @@ -1118,13 +1129,14 @@ export function createFrost( const intersection = new IntersectionObserver((entries) => { visible = entries[entries.length - 1]?.isIntersecting ?? true; if (visible) start(); + else queuedMelts.length = 0; }); intersection.observe(output); return { melt(x, y) { if (reducedMotion) return; - queuedMelts.push([x, y]); + if (!enqueueMelt(x, y)) return; activeUntil = performance.now() + refreezeDelayMs(); start(); }, diff --git a/src/lib/Liquid/LiquidVanilla.ts b/src/lib/Liquid/LiquidVanilla.ts index 081fac3..82dad2d 100644 --- a/src/lib/Liquid/LiquidVanilla.ts +++ b/src/lib/Liquid/LiquidVanilla.ts @@ -790,6 +790,8 @@ export function createLiquid( blit(null); } + const MAX_PENDING_SPLATS = 64; + const MAX_SPLATS_PER_FRAME = 8; const queued: Array<[number, number, number, number]> = []; let raf = 0; @@ -815,8 +817,9 @@ export function createLiquid( lastTime = now; if (queued.length > 0) { idleAt = now + idleDelayMs(); - while (queued.length > 0) { - const [x, y, dx, dy] = queued.pop()!; + const count = Math.min(queued.length, MAX_SPLATS_PER_FRAME); + for (let i = 0; i < count; i++) { + const [x, y, dx, dy] = queued.shift()!; applySplat(x, y, dx, dy); } } @@ -829,6 +832,12 @@ export function createLiquid( raf = requestAnimationFrame(frame); } + function enqueueSplat(x: number, y: number, dx: number, dy: number) { + if (destroyed || !visible) return; + if (queued.length >= MAX_PENDING_SPLATS) queued.shift(); + queued.push([x, y, dx, dy]); + } + function start() { if (destroyed || running || !visible) return; running = true; @@ -864,7 +873,7 @@ export function createLiquid( if (!previous) return; const dx = (px - previous.x) * config.force; const dy = -(py - previous.y) * config.force; - queued.push([px / rect.width, 1 - py / rect.height, dx, dy]); + enqueueSplat(px / rect.width, 1 - py / rect.height, dx, dy); start(); } @@ -875,7 +884,7 @@ export function createLiquid( const y = event.clientY - rect.top; if (x < 0 || x > rect.width || y < 0 || y > rect.height) return; pointers.set(event.pointerId, { x, y }); - queued.push([x / rect.width, 1 - y / rect.height, 1, 1]); + enqueueSplat(x / rect.width, 1 - y / rect.height, 1, 1); start(); } @@ -911,13 +920,14 @@ export function createLiquid( const intersection = new IntersectionObserver((entries) => { visible = entries[entries.length - 1]?.isIntersecting ?? true; if (visible) start(); + else queued.length = 0; }); intersection.observe(output); return { splat(x, y, dx, dy) { if (reducedMotion) return; - queued.push([x, y, dx, dy]); + enqueueSplat(x, y, dx, dy); start(); }, setOptions(next) {