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

Elasticity With Trails

  1. let pointX = pointY = 0;
  2.  
  3. document.addEventListener('touchmove', 
  4.   e => e.preventDefault(), { passive: false });
  5.  
  6. document.addEventListener('mousemove', e => {
  7.   pointX = e.clientX;
  8.   pointY = e.clientY;
  9. });
  10.  
  11. document.addEventListener('touchmove', e => {
  12.   pointX = e.touches[0].clientX;
  13.   pointY = e.touches[0].clientY;
  14. });
  15.  
  16. let el = document.body.appendChild(
  17.   document.createElement`div`
  18. );
  19.  
  20. const size = 20;
  21. const halfSize = size / 2;
  22.  
  23. Object.assign(el.style, {
  24.   position: 'absolute',
  25.   width: `${size}px`,
  26.   height: `${size}px`,
  27.   background: 'red',
  28.   borderRadius: `${size}px`,
  29.   left: 0, top: 0
  30. });
  31.  
  32. let x = vx = y = vy = 0;
  33. const FADE_TIME = 800;
  34. const plotDot = (x, y) => {
  35.   const dot = document.body.appendChild(el.cloneNode());
  36.   const time = 
  37.   dot.style.transform += ' scale(.25)';
  38.   dot.style.transition = `opacity ${FADE_TIME}ms ease-out`;
  39.   window.requestAnimationFrame(() => {
  40.     dot.style.opacity = 0;
  41.     setTimeout(() => dot.parentNode.removeChild(dot), FADE_TIME);
  42.   })
  43. }
  44.  
  45. let ticks = 0;
  46. const loop = () => { 
  47.   vx = ((pointX - x) * .08 + vx) * .95;
  48.   vy = ((pointY - y) * .08 + vy) * .95;
  49.   x += vx;
  50.   y += vy;
  51.  
  52.   if (ticks++ % 2 === 0 && 
  53.     Math.abs(pointX - x) > 1 && 
  54.     Math.abs(pointY - y) > 1) {
  55.       plotDot();
  56.     }
  57.   el.style.transform = `translate(${x - halfSize}px, ${y - halfSize}px)`;
  58.   requestAnimationFrame(loop);
  59. }
  60. loop();
  61.  
  62. const info = document.body.appendChild(
  63.   document.createElement`div`
  64. );
  65. info.innerHTML = 'move mouse or finger left/right/up/down';

This is a variation on yesterdays post. This has elasticity on both axis and draws a trail of dots…

Elasticity

  1. let pointX = pointY = 0;
  2.  
  3. document.addEventListener('mousemove', e => {
  4.   pointX = e.clientX;
  5.   pointY = e.clientY;
  6. });
  7.  
  8. document.addEventListener('touchmove', e => {
  9.   pointX = e.touches[0].clientX
  10.   pointY = e.touches[0].clientY
  11. });
  12.  
  13. let el = document.body.appendChild(
  14.   document.createElement`div`
  15. );
  16.  
  17. const size = 20;
  18. const halfSize = size / 2;
  19.  
  20. Object.assign(el.style, {
  21.   position: 'absolute',
  22.   width: `${size}px`,
  23.   height: `${size}px`,
  24.   background: 'red',
  25.   left: 0, top: 0
  26. })
  27.  
  28. let x = vx = 0;
  29. const loop = () => { 
  30.   vx = ((pointX - x) * .2 + vx) * .79;
  31.   x += vx;
  32.   el.style.transform = `translate(${x - halfSize}px, 50px)`;
  33.   requestAnimationFrame(loop);
  34. }
  35. loop();
  36.  
  37. let info = document.body.appendChild(
  38.   document.createElement`div`
  39. );
  40. info.innerHTML = 'move mouse or finger left/right';

Basic interactive elasticity with mouse or touch

Animation Along Path SVG

  1. const el = document.body.appendChild(
  2.   document.createElement('div'));
  3.  
  4. el.innerHTML = `
  5.   <svg width="100%" height="100%" viewBox="0 0 550 496">
  6.     <path 
  7.       d="M 20 10 C 100 100 300 00 300 100 200 200 150 300 20 10" 
  8.       stroke="black" fill='none' vector-effect="non-scaling-stroke"/>
  9.     <circle cx="20" cy="10" r="6" fill="red" />
  10.   </svg>
  11.   <style>
  12.     svg, div, body, html {
  13.       overflow: visible; 
  14.       height: 100%; 
  15.       width: 100%;
  16.       margin: 0; padding: 0;
  17.     }
  18.   </style>
  19.  `;
  20.  
  21. const circle = el.querySelector('circle');
  22. const path = el.querySelector('path');
  23. const length = path.getTotalLength();
  24. let pos = 0;
  25.  
  26. function loop() {
  27.   pos += 3;
  28.   if (pos > length) {
  29.     pos = 0;
  30.   }
  31.   const point = path.getPointAtLength(pos);
  32.   circle.cx.baseVal.value = point.x;
  33.   circle.cy.baseVal.value = point.y;
  34.   requestAnimationFrame(loop);
  35. }
  36. loop();

SVG has an easy way to animate something along an arbitrary path… getPointAtLength

ASCII Circles (Bresenham Fun)

  1. // some circles made of text
  2.  
  3. const cols = 50;
  4. const rows = 50;
  5. const sym = '.';
  6. const body = document.body;
  7.  
  8. Object.assign(body.style, {
  9.   userSelect: 'none',
  10.   fontFamily: 'Courier, monospace',
  11.   position: 'fixed', 
  12.   width: '100%',
  13.   height: '100%',
  14.   margin: 0
  15. });
  16.  
  17. const el = body.appendChild(
  18.   document.createElement('span')
  19. );
  20. el.innerHTML = sym;
  21.  
  22. Object.assign(el.style, {
  23.   position: 'absolute',
  24.   left: '50%',
  25.   top: '50%',
  26.   wordWrap: 'break-word',
  27.   cursor: 'pointer'
  28. });
  29.  
  30. const charSize = el.getBoundingClientRect().width;
  31. const elWidth = charSize * cols;
  32. el.style.display = 'block'
  33. el.style.width = `${elWidth}px`;
  34.  
  35. const info = body.appendChild(
  36.   document.createElement('div')
  37. );
  38.  
  39. Object.assign(info.style, {
  40.   background: '#fff',
  41.   padding: '4px',
  42.   position: 'absolute'
  43. })
  44. info.innerHTML = 'click/tap and hold for different fx';
  45.  
  46. function resize() {
  47.   const scl = Math.min(
  48.     1.23, 
  49.     Math.min(innerWidth, innerHeight) / elWidth * .93
  50.   );
  51.   el.style.transform = `translate(-50%, -50%) scale(${scl}, ${scl * .55})`
  52. }
  53. addEventListener('resize', resize);
  54. resize();
  55.  
  56. const size = cols * rows;
  57. const pix = sym.repeat(size);
  58. el.innerHTML = pix;
  59.  
  60. let cells = pix.split('')
  61. const blank = cells.concat();
  62.  
  63. function setSym(x, y, col) {
  64.   const idx = x + y * cols;
  65.   if (cells[idx] != null) {
  66.     cells[idx] = col;
  67.   }
  68.   return setSym
  69. }
  70.  
  71. const grad = '::;|0UU888NN';
  72. function circ(shooter) {
  73.   let x = Math.round(Math.random() * cols);
  74.   let y = Math.round(Math.random() * rows);
  75.   const rad = Math.round(
  76.     shooter ? Math.random() * 2 :
  77.       Math.random() * Math.random() * 13 + 1
  78.   );
  79.  
  80.   const sym = shooter ? '#' : grad.charAt(rad % grad.length);
  81.   let speed = rad / 10 + .1;
  82.   let dir = Math.random() * 2 - 1;
  83.   return () => {
  84.     drawCircle(x, y, rad, sym, shooter);
  85.     y += speed;
  86.     if (shooter) x += speed * 3 * dir;
  87.     if (y > rows + 10) y = -14;
  88.   }
  89. }
  90.  
  91. const circs = [];
  92. const NUM = 40;
  93. for (let i = 0; i < NUM; i++) {
  94.   circs.push(circ(Math.random() > 0.5))
  95. }
  96.  
  97. let down;
  98. document.addEventListener('mousedown', () => {
  99.   down = true;
  100. });
  101. document.addEventListener('mouseup', () => {
  102.   down = false;
  103. });
  104. document.addEventListener('touchstart', () => {
  105.   down = true;
  106. });
  107. document.addEventListener('touchend', () => {
  108.   down = false;
  109. });
  110.  
  111. let tweak;
  112. let tweakChoice;
  113. let tweakChance = 0.4;
  114. const clear = () => cells = blank.concat();
  115.  
  116. function draw() {
  117.   if (!down) {
  118.     tweak = false;
  119.     tweakChoice = Math.random();
  120.     clear();
  121.   } else {
  122.     if (tweakChoice < tweakChance) {
  123.       tweak = true;
  124.       clear();
  125.     }
  126.   }
  127.  
  128.   circs.forEach(circ => circ());
  129.   el.innerHTML = cells.join('');
  130. }
  131.  
  132. // 60fps is too fast, so use 30ms interval
  133. setInterval(draw, 30);
  134.  
  135. function hLine(xp, yp, w, col) {
  136.   for (let i = 0; i < w; i++) {
  137.     setSym(xp + i, yp, col);
  138.   }
  139.   return hLine;
  140. }
  141.  
  142. // bresenham circle
  143. function drawCircle(xp, yp, radius, sym = '@', isFilled) {
  144.   if (isFilled && tweak) sym = '';
  145.  
  146.   xp = parseInt(xp, 10);
  147.   yp = parseInt(yp, 10);
  148.   radius = parseInt(radius, 10);
  149.   let balance = -radius,
  150.     xoff = 0,
  151.     yoff = radius;
  152.  
  153.   while (xoff <= yoff) {
  154.     const p0 = xp - xoff;
  155.     const p1 = xp + xoff;
  156.  
  157.     const p2 = yp + yoff;
  158.     const p3 = yp - yoff;
  159.     const p4 = yp + xoff;
  160.     const p5 = xp + yoff;
  161.     const p6 = xp - yoff;
  162.     const p7 = yp - xoff;
  163.  
  164.     if (isFilled) {
  165.       const w0 = xoff + xoff;
  166.       const w1 = yoff + yoff;
  167.  
  168.       hLine
  169.         (p0, yp + yoff, w0, sym)
  170.         (p0, yp - yoff, w0, sym)
  171.         (p6, yp + xoff, w1, sym)
  172.         (p6, yp - xoff, w1, sym);
  173.  
  174.     } else {
  175.       setSym
  176.         (p1, p2, sym)
  177.         (p0, p2, sym)
  178.         (p0, p3, sym)
  179.         (p1, p3, sym)
  180.         (p5, p4, sym)
  181.         (p6, p4, sym)
  182.         (p6, p7, sym)
  183.         (p5, p7, sym);
  184.     }
  185.  
  186.     // never been able to find the original 
  187.     // source for the below condition 
  188.     // more info here: https://actionsnippet.com/?p=492
  189.     if ((balance += xoff++ + xoff) >= 0) {
  190.       balance -= --yoff + yoff;
  191.     }
  192.   }
  193. }

This is a bit of a longer snippet that uses the Bresenham circle drawing algorithm to draw some circles with text. I recommend looking at it with the fullscreen button.

I like to do this:

  1. hLine
  2.   (p0, yp + yoff, w0, sym)
  3.   (p0, yp - yoff, w0, sym)
  4.   (p6, yp + xoff, w1, sym)
  5.   (p6, yp - xoff, w1, sym);

Make a function return itself, so that if you need to call it many times without having to repeat the function name. Because this isn’t a common style it is generally frowned upon, I’m always tempted to use it at work for some reason… maybe next April fools.

Array Based Collision Cells

  1. ((
  2.   d = document,
  3.   b = d.body,
  4.   canvas = b.appendChild(
  5.     d.createElement('canvas')
  6.   ),
  7.   c = canvas.getContext('2d'),
  8.   r = _ => Math.random(),
  9.   map = [
  10.     [0, 0, 0, 0, 0, 2],
  11.     [1, 1, 0, 0, 0, 1],
  12.     [1, 1, 2, 0, 0, 0],
  13.     [2, 2, 0, 0, 0, 1],
  14.     [0, 1, 0, 0, 2, 0],
  15.     [2, 0, 0, 0, 2, 1],
  16.   ],
  17.   mapW = map[0].length,
  18.   mapH = map.length,
  19.   cols = [, 'red', 'black'],
  20.   cell = (
  21.     x, y, idx,
  22.     col = cols[idx],
  23.     size = 30,
  24.     xp = x * size,
  25.     yp = y * size,
  26.     dir, 
  27.     mv = f => {
  28.       map[y][x] = 0
  29.       f()
  30.       map[y][x] = idx
  31.     }
  32.   ) => (move) => {
  33.     if (move) {
  34.       dir = ~~(r() * 4)
  35.       if (dir == 0 &&
  36.         x != 0 &&
  37.         map[y][x - 1] == 0) {
  38.         mv(_ => x--)
  39.       } else if (dir == 1 &&
  40.         x != mapW - 1 &&
  41.         map[y][x + 1] == 0) {
  42.         mv(_ => x++)
  43.       } else if (dir == 2 &&
  44.         y != 0 &&
  45.         map[y - 1][x] == 0) {
  46.         mv(_ => y--)
  47.       } else if (dir == 3 &&
  48.         y != mapH - 1 &&
  49.         map[y + 1][x] == 0
  50.       ) {
  51.         mv(_ => y++)
  52.       }
  53.     }
  54.  
  55.     xp += (x * size - xp) / 4
  56.     yp += (y * size - yp) / 4
  57.     c.fillStyle = col
  58.     c.fillRect(xp, yp, size, size)
  59.     c.strokeStyle = 'gray'
  60.     c.strokeRect(xp, yp, size, size)
  61.   },
  62.   cells = [],
  63.   w, h, idx, val, i, j,
  64.   draw = () => {
  65.     c.fillStyle = 'gray'
  66.     c.fillRect(0, 0, w, h)
  67.  
  68.     idx = ~~(r() * mapH * mapW)
  69.  
  70.     cells.forEach((cell, i) =>
  71.       cell(idx == i && r() < .3))
  72.   }
  73. ) => {
  74.   b.style.margin = 0
  75.  
  76.   onresize = () => {
  77.     w = canvas.width = innerWidth
  78.     h = canvas.height = innerHeight
  79.     draw()
  80.   }
  81.   onresize()
  82.  
  83.   for (i = 0; i < mapH; i++) {
  84.     for (j = 0; j < mapW; j++) {
  85.       val = map[i][j]
  86.       if (val != 0) cells.push(cell(j, i, val)) 
  87.     }
  88.   }
  89.  
  90.   setInterval(draw, 16)
  91. })()

Array based avoid. I was about to port an old thing that was similar to this and then thought it would be more fun to speedcode it instead. The result is a slightly golfed version of this old thing.

snippet.zone ~ 2021-24 /// {s/z}