Spot the bug - #128: Floating Balloon Element

Why does my floating balloon speed up whenever I tab away?

const balloon = document.querySelector("#balloon");

const physics = {
  posY: window.innerHeight - 80,
  speedY: -180,
  wobbleFreq: 0.005,
  wobbleAmp: 40,
  baseX: window.innerWidth / 2
};

let lastTime = null;
let runningTime = 0;

function renderBalloon(timestamp) {
  if (lastTime === null) {
    lastTime = timestamp;
  }

  const delta = (timestamp - lastTime) / 1000;
  runningTime += delta;

  physics.posY += physics.speedY * delta;
  const wobbleX = Math.sin(runningTime * physics.wobbleFreq * 1000) * physics.wobbleAmp;
  const posX = physics.baseX + wobbleX;

  if (physics.posY < -100) {
    physics.posY = window.innerHeight + 20;
  }

  balloon.style.transform = `translate3d(${posX}px, ${physics.posY}px, 0)`;

  requestAnimationFrame(renderBalloon);
}

requestAnimationFrame(renderBalloon);

Reply with what is broken and how you would fix it.

When you tab away, requestAnimationFrame likely stops running. This does change your delta time values because the animation thinks it needs to move faster to make up for the time lost by your requestAnimationFrame not looping when this tab wasn’t in focus.

That’s exactly it. The lastTime gets stale when the tab isn’t active. So when it comes back, the delta calculation goes wild. You could reset lastTime in the if block, or just cap the delta. Capping it is probably safer for any weird edge cases.

if (delta > maxDelta) {
  delta = maxDelta;
}

Oh, that’s a good call on the stale lastTime. I’ve definitely seen animations jump around after switching tabs because of that. Capping the delta is smart.

Clean

That’s a good observation about requestAnimationFrame! You’re right that it often pauses when the tab isn’t active.

The problem is that delta is still calculating the full time difference, even when the animation wasn’t running. You’ll want to cap delta to a maximum value to prevent the balloon from jumping too far. For example, setting delta = Math.min(delta, 1 / 30); would make sure it doesn’t try to catch up too much.