Skip to content

Instantly share code, notes, and snippets.

@luizbills
Last active June 4, 2025 14:05
Show Gist options
  • Save luizbills/15460c1c8dc56b243b7bc366d8e615c4 to your computer and use it in GitHub Desktop.
Save luizbills/15460c1c8dc56b243b7bc366d8e615c4 to your computer and use it in GitHub Desktop.
JavaScript Game Loop
// based on https://www.gafferongames.com/post/fix_your_timestep/
const TARGET_FPS = 60
const dt = 1/TARGET_FPS
const eventData = { deltaTime: dt, elapsed: 0 }
let _accumulated = 0
let _lastFrameTime = performance.now()
requestAnimationFrame(gameLoop);
/**
* @param {number} now
*/
function gameLoop (now) {
requestAnimationFrame(gameLoop);
let updated = 0,
frameTime = (now - _lastFrameTime) / 1000
_lastFrameTime = now
if (frameTime > 0.3) {
return console.warn('skipping too long frame')
}
_accumulated += frameTime;
while (_accumulated >= dt) {
updated++
window.dispatchEvent(new CustomEvent('AnimationUpdate', { detail: eventData }))
eventData.elapsed += dt
_accumulated -= dt
}
if (updated) {
window.dispatchEvent(new CustomEvent('AnimationDraw'))
}
}

Usage

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
</head>
<body>
  <canvas id="c" width=300 height=300></canvas>
  <script src="app.js"></script>
</body>
</html>
// app.js
const ctx = c.getContext('2d')
const speed = 5
let t = 0

function update(ev) {
  t += ev.detail.deltaTime * speed
}

function draw() {
  ctx.fillStyle = 'white'
  ctx.fillRect(0, 0, 300, 300)
  
  ctx.fillStyle = 'blue'
  ctx.beginPath()
  ctx.arc(150, 150 + Math.sin(t)*100, 50, 0, 2 * Math.PI)
  ctx.fill()
}

window.addEventListener('AnimationUpdate', update)
window.addEventListener('AnimationDraw', draw)
@luizbills
Copy link
Author

luizbills commented Feb 18, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment