)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Fixed Timestep with State Interpolation

  1. // move your mouse around - click to reverse
  2.  
  3. function attrs(el, a) {
  4.   for (let k in a) el.setAttribute(k, a[k])
  5.   return el
  6. }
  7.  
  8. document.body.style.margin = '0'
  9. document.body.style.overflow = 'hidden'
  10. document.body.style.background = '#ccc'
  11.  
  12. const NS = 'http://www.w3.org/2000/svg'
  13. const svg = document.createElementNS(NS, 'svg')
  14. document.body.appendChild(svg)
  15.  
  16. Object.assign(svg.style, {
  17.   display: 'block',
  18.   background: 'white'
  19. })
  20.  
  21. function resize() {
  22.   attrs(svg, { width: innerWidth, height: innerHeight })
  23. }
  24. resize()
  25.  
  26. const line = attrs(
  27.   document.createElementNS(NS, 'line'),
  28.   { stroke: 'black' }
  29. )
  30. svg.appendChild(line)
  31.  
  32. const circ = attrs(
  33.   document.createElementNS(NS, 'circle'),
  34.   { r: 10, fill: 'red' }
  35. )
  36. svg.appendChild(circ)
  37.  
  38. let mouseX = 0, mouseY = 0
  39. let bx = 0, by = 0
  40. let x = 0, y = 0
  41. let px = 0, py = 0
  42.  
  43. const HZ = 60
  44. const DT = 1 / HZ
  45. let accumulator = 0
  46. let lastTime = performance.now()
  47.  
  48. onmousemove = e => {
  49.   mouseX = e.pageX
  50.   mouseY = e.pageY
  51. }
  52.  
  53. ontouchmove = e => {
  54.   e.preventDefault()
  55.   const t = e.touches[0]
  56.   mouseX = t.pageX
  57.   mouseY = t.pageY
  58. }
  59.  
  60. onclick = () => {
  61.   bx *= -1
  62.   by *= -1
  63. }
  64.  
  65. function update() {
  66.   const now = performance.now()
  67.   let frameTime = (now - lastTime) * 0.001
  68.   lastTime = now
  69.   if (frameTime > 0.25) frameTime = 0.25
  70.   accumulator += frameTime
  71.  
  72.   while (accumulator >= DT) {
  73.     px = x
  74.     py = y
  75.  
  76.     x -= bx
  77.     y -= by
  78.     bx = ((x - mouseX) / 17 + bx) / 1.05
  79.     by = ((y - mouseY) / 17 + by) / 1.05
  80.  
  81.     accumulator -= DT
  82.   }
  83.  
  84.   render(accumulator / DT)
  85.   requestAnimationFrame(update)
  86. }
  87.  
  88. function render(alpha) {
  89.   const dx = x * alpha + px * (1 - alpha)
  90.   const dy = y * alpha + py * (1 - alpha)
  91.  
  92.   attrs(circ, { cx: dx, cy: dy })
  93.   attrs(line, {
  94.     x1: mouseX, y1: mouseY,
  95.     x2: dx,     y2: dy
  96.   })
  97. }
  98.  
  99. requestAnimationFrame(update)
  100. onresize = resize

Used Gemini 3 Pro to create this nice smooth “Fixed Timestep with State Interpolation”. This yoyo thing is an old experiment I made to run on an iPhone 3 I think… back before canvas was hardware accelerated and SVG was faster – original version used Raphael.js – timestep logic comes from this well known article: https://gafferongames.com/post/fix_your_timestep/ by Glenn Fiedler.

snippet.zone /// {s/z}