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

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.

SVG Set Tag MDN

  1. document.body.innerHTML = `
  2. click the rounded rect<br>
  3. <svg viewBox="0 0 10 10" width="150" height="150">
  4.   <style>
  5.     rect { cursor: pointer }
  6.     .round { rx: 5px; fill: green; }
  7.   </style>
  8.  
  9.   <rect id="me" width="10" height="10" rx="3">
  10.     <set attributeName="class" to="round" begin="me.click" dur="2s" />
  11.   </rect>
  12. </svg>
  13. `

This is from MDN. Having never seen or used the set tag before, I thought this was worth a quick post…

// javascript // svg // tricks // ui

Canvas Path2D

  1. const canvas = document.body.appendChild(
  2.   document.createElement('canvas')
  3. )
  4. const c = canvas.getContext('2d')
  5.  
  6. canvas.width = innerWidth
  7. canvas.height = innerHeight
  8.  
  9. c.translate(10, 10)
  10. const path = new Path2D('M 154.75 61.5 Q 169.75 63.8 184.8 69.35 173.15 68.3 161.7 69.4 173.1 73.85 184.8 75.95 173.1 77.15 161.65 75.95 173.05 80.45 184.8 82.55 173 83.75 160.05 82.35 173 87.05 184.8 89.15 172.95 90.35 161.65 89.1 172.9 93.65 184.8 95.75 172.8 96.9 161.65 95.7 172.8 100.2 184.8 102.35 172.75 103.5 163.7 102.7 172.7 106.8 184.8 110.3 172.7 110.1 161.65 108.85 172.6 113.35 184.8 118.85 171.05 116.05 158.35 114.65 167.3 126.9 174 141.45 166.5 132.45 157.65 125.1 162.55 136.3 169.35 146.1 160.2 138.65 152.95 129.7 157.85 141 164.7 150.75 155.5 143.25 147.3 133.15 153.15 145.6 160 155.45 150.75 147.9 143.65 139 148.45 150.2 155.35 160.1 146.05 152.4 139 143.65 143.7 154.75 150.7 164.75 141.3 157.05 135.5 150.05 139 159.35 145.05 170.4 136.65 161.7 129.7 152.95 134.25 163.9 139 176.45 131.3 164.75 123.3 154.8 121 169.8 115.45 184.8 116.5 173.15 115.4 161.7 110.95 173.1 108.85 184.8 107.65 173.1 108.85 161.65 104.35 173.05 102.25 184.8 101.05 173 102.45 160.05 97.75 173 95.65 184.8 94.45 172.95 95.7 161.65 91.15 172.9 89.05 184.8 87.9 172.8 89.1 161.65 84.6 172.8 82.45 184.8 81.3 172.75 82.1 163.7 78 172.7 74.5 184.8 74.7 172.7 75.95 161.65 71.45 172.6 65.95 184.8 68.75 171.05 70.15 158.4 57.9 167.35 43.35 174.05 52.35 166.55 59.7 157.7 48.5 162.6 38.7 169.4 46.15 160.25 55.1 153 43.8 157.9 34.05 164.75 41.55 155.55 51.65 147.35 39.2 153.2 29.35 160.05 36.9 150.8 45.8 143.7 34.6 148.5 24.7 155.4 32.4 146.1 41.15 139.05 30.05 143.75 20.05 150.75 27.75 141.35 34.75 135.55 25.45 139.05 14.4 145.1 23.1 136.7 31.85 129.75 20.9 134.3 8.35 139.05 20.05 131.3 30.05 123.3 15.05 121 0 115.45 11.65 116.5 23.1 115.4 11.7 110.95 0 108.85 11.7 107.65 23.15 108.85 11.75 104.35 0 102.25 11.8 101.05 24.75 102.45 11.8 97.75 0 95.65 11.85 94.45 23.15 95.7 11.9 91.15 0 89.05 12 87.9 23.15 89.1 12 84.6 0 82.45 12.05 81.3 21.1 82.1 12.1 78 0 74.5 12.1 74.7 23.15 75.95 12.2 71.45 0 65.95 13.75 68.75 26.45 70.15 17.45 57.9 10.75 43.35 18.25 52.35 27.1 59.7 22.2 48.5 15.4 38.7 24.55 46.15 31.8 55.1 26.9 43.8 20.05 34.05 29.25 41.55 37.45 51.65 31.6 39.2 24.75 29.35 34 36.9 41.1 45.8 36.3 34.6 29.4 24.7 38.7 32.4 45.75 41.15 41.05 30.05 34.05 20.05 43.45 27.75 49.25 34.75 45.75 25.45 39.7 14.4 48.1 23.1 55.05 31.85 50.5 20.9 45.75 8.35 53.5 20.05 61.5 30.05 63.8 15.05 69.35 0 68.3 11.65 69.4 23.1 73.85 11.7 75.95 0 77.15 11.7 75.95 23.15 80.45 11.75 82.55 0 83.75 11.8 82.35 24.75 87.05 11.8 89.15 0 90.35 11.85 89.1 23.15 93.65 11.9 95.75 0 96.9 12 95.7 23.15 100.2 12 102.35 0 103.5 12.05 102.7 21.1 106.8 12.1 110.3 0 110.1 12.1 108.85 23.15 113.35 12.2 118.85 0 116.05 13.75 114.65 26.45 126.9 17.45 141.45 10.75 132.45 18.25 125.1 27.1 136.3 22.2 146.1 15.4 138.65 24.55 129.7 31.8 141 26.9 150.75 20.05 143.25 29.25 133.15 37.45 145.6 31.6 155.45 24.75 147.9 34 139 41.1 150.2 36.3 160.1 29.4 152.4 38.7 143.65 45.75 154.75 41.05 164.75 34.05 157.05 43.45 150.05 49.25 159.35 45.75 170.4 39.7 161.7 48.1 152.95 55.05 163.9 50.5 176.45 45.75 164.75 53.5 154.75 61.5 140.2 73.15 129.35 85.4 146 97.85 158.35 114.65 M 61.5 30.05 Q 73.15 44.6 85.4 55.45 97.8 38.8 114.65 26.45 M 30.05 123.3 Q 44.6 111.65 55.45 99.45 38.8 87 26.45 70.15 M 123.3 154.8 Q 111.65 140.25 99.4 129.4 86.95 146.05 70.15 158.4');
  11.  
  12. c.stroke(path);

Use SVG style paths in canvas…

xPath and svg

  1. // hack to shove some html on the page since snippet.zone
  2. // quick editor does not yet suppor HTML :P
  3. document.body.innerHTML += `
  4.   <svg style="overflow:visible">
  5.   <rect x="10" y="10" fill="red" width="30" height="10" />
  6.   <rect x="10" y="20" fill="blue" width="30" height="10" />
  7.   <rect x="10" y="30" fill="purple" width="30" height="10" />
  8.   </svg>
  9. `;
  10.  
  11. const svgPrefix = pre => pre === 'svg' && 'http://www.w3.org/2000/svg';
  12.  
  13. // https://stackoverflow.com/questions/31162358/extract-svg-element-by-using-document-evaluate
  14. let els = document.evaluate(
  15.   '//svg:svg/*[@fill="blue"]',
  16.   document,
  17.   svgPrefix,
  18.   XPathResult.ANY_TYPE,
  19.   null
  20. );
  21.  
  22. const results = [];
  23. while ((el = els.iterateNext())) {
  24.   // save results to avoid mutation error
  25.   results.push(el);
  26. }
  27. results[0].x.baseVal.value = 50;
  28. results[0].setAttribute('stroke', 'orange');
  29. results[0].setAttribute('stroke-width', 2);

Now that IE 11 is almost dead… we can use document.evaluate – I knew there would be some trick to get it to work on svg… Found the answer from user Martin Honnen on stackoverflow – very cool.

Of course this one can be done with document.querySelectorAll('*[fill="blue"]') – but xPath can do some other stuff that css selectors can’t exactly do yet…

// svg // xPath

Drag Svg Circles

  1. document.addEventListener('touchmove', (e) => e.preventDefault(), {
  2.   passive: false,
  3. });
  4.  
  5. // this is just lazy - obviously goes in your stylesheet.... :P
  6. document.body.innerHTML += `
  7. <style>
  8.   body, html {
  9.     padding: 0;
  10.     height: 100%;
  11.     margin: 0;
  12.   }
  13. </style>
  14. `;
  15.  
  16. const temp = document.createElement('div');
  17. temp.innerHTML = `<svg width="100%" height="100%" viewBox="0 0 500 500"></svg>`;
  18.  
  19. const svg = document.body.appendChild(temp.querySelector('svg'));
  20.  
  21. const CIRCLE_NUM = 4;
  22.  
  23. // make some randomly positioned circles
  24. for (let i = 0; i < CIRCLE_NUM; i++) {
  25.   const x = Math.random() * 150 + 150;
  26.   const y = Math.random() * 150 + 150;
  27.   svg.innerHTML += `<circle 
  28.     cx="${x}" cy="${y}" r="60" 
  29.     stroke="#000" fill="rgba(81, 121, 200, 0.3)" 
  30.     style="cursor:pointer" />`;
  31. }
  32.  
  33. function touch(e) {
  34.   const pt = svg.createSVGPoint();
  35.   pt.x = e.clientX;
  36.   pt.y = e.clientY;
  37.   return pt.matrixTransform(svg.getScreenCTM().inverse());
  38. }
  39.  
  40. let down, ox, oy, curr;
  41. document.addEventListener('pointerdown', (e) => {
  42.   down = true;
  43.   if (e.target.tagName === 'circle') {
  44.     curr = e.target;
  45.     const { x, y } = touch(e);
  46.     ox = curr.cx.baseVal.value - x;
  47.     oy = curr.cy.baseVal.value - y;
  48.   }
  49. });
  50.  
  51. document.addEventListener('pointermove', (e) => {
  52.   if (down && curr) {
  53.     const { x, y } = touch(e);
  54.     curr.cx.baseVal.value = x + ox;
  55.     curr.cy.baseVal.value = y + oy;
  56.   }
  57. });
  58.  
  59. document.addEventListener('pointerup', (e) => {
  60.   down = false;
  61.   curr = null;
  62. });

Drag some svg circles on mobile and desktop…

// javascript // svg // ui
snippet.zone /// {s/z}