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

Bad/Simple UI Counter

  1. <script>
  2.   count = 0
  3. </script>
  4. <div>
  5.   <p>You clicked <span id="countEl">0</span> times</p>
  6.   <button onclick="countEl.innerHTML = ++count">Click me</button>
  7. </div>

This kind of thing is often showcased on UI Framework demo pages. I thought it would be interesting to create it in the simplest way possible while still being readable. This is the only code needed for the entire web page… the browser will figure out the rest… 😀

// javascript // tricks // ui

Fast Sine and Cosine Approximation

  1. let t = 0, cos, sin, x, y;
  2.  
  3. const PI = Math.PI;
  4. const TWO_PI = PI * 2;
  5. const HALF_PI = PI / 2;
  6. const tA = 4 / PI;
  7. const tB = 4 / PI ** 2;
  8.  
  9. const canvas = document.body.appendChild(document.createElement('canvas'));
  10. const c = canvas.getContext('2d');
  11. canvas.width = canvas.height = 300;
  12.  
  13. function loop() {
  14.   // low precision sine/cosine
  15.   // always wrap input angle to -PI..PI
  16.   t += 0.1;
  17.   if (t < -PI) {
  18.     t += TWO_PI;
  19.   } else if (t > PI) {
  20.     t -= TWO_PI;
  21.   }
  22.  
  23.   // compute sine
  24.   if (t < 0) {
  25.     sin = tA * t + tB * t * t;
  26.   } else {
  27.     sin = tA * t - tB * t * t;
  28.   }
  29.  
  30.   // compute cosine: sin(t + PI/2) = cos(t)
  31.   t += HALF_PI;
  32.   if (t > PI) {
  33.     t -= TWO_PI;
  34.   }
  35.  
  36.   if (t < 0) {
  37.     cos = tA * t + tB * t * t;
  38.   } else {
  39.     cos = tA * t - tB * t * t;
  40.   }
  41.  
  42.   t -= HALF_PI; // move the shape
  43.  
  44.   x = 110 + 100 * cos;
  45.   y = 110 + 100 * sin;
  46.  
  47.   c.fillStyle = 'rgba(100, 100, 20, .5)';
  48.   c.fillRect(x, y, 10, 10);
  49.   window.requestAnimationFrame(loop);
  50. }
  51. loop();

This is an old trick for fast sine/cosine approximation. I learned about it from Michael Baczynski’s blog which now seems to be down. Here is a wayback link for the original article and another one to a more in-depth discussion about it:
original post
original thread

It’s unlikely that the speed gain (if there even is one here) makes sense in javascript. This is really more for fun… a quick search will turn up all kinds of cool info about fast sine/cosine stuff.

SVG getScreenCTM

  1. const el = document.body.appendChild(
  2.   document.createElement`div`
  3. );
  4. el.innerHTML = `
  5. <svg width="200" height="200" viewBox="0 0 200 200">
  6.   <rect 
  7.     class="rect"
  8.     transform="translate(50, 50) scale(1.2) rotate(25)"
  9.     fill="purple"
  10.     x="0" y="0" width="50" height="50" />
  11. </svg>
  12. `;
  13.  
  14. const box = document.body.appendChild(
  15.   document.createElement`div`
  16. );
  17.  
  18. Object.assign(box.style, {
  19.   position: 'absolute',
  20.   left: 0, top: 0,
  21.   width: '50px',
  22.   height: '50px',
  23.   transformOrigin: '0 0',
  24.   outline: '5px solid red'
  25. });
  26.  
  27. const rect = document.querySelector('.rect');
  28. const {a, b, c, d, e, f} = rect.getScreenCTM()
  29.  
  30. box.style.transform = `
  31.   matrix(${[a, b, c, d, e, f]})
  32. `;

The transformation matrix of an SVG element can be obtained using getScreenCTM or getCTM. The latter of which will be relative to the SVG coordinate space, vs the coordinate space of the page.

Here we take the matrix data from getScreenCTM and use it on a div to place a border over an SVG rect node. This is great for layering HTML on top of SVG.

// dom // javascript // math // matrix // svg // tricks // ui

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

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