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

Set Multiple Attributes (hack)

  1. // hacky way to set many attributes - just for fun
  2. const el = document.body.appendChild(
  3.   document.createElement('button'));
  4. let i, a;
  5. el.innerHTML = 'hello';
  6. for (i in a = {
  7.   id: 'xyz',
  8.   title: 'hi there...',
  9.   style: `
  10.     background: red; 
  11.     color:white;
  12.     font-size: 1.4em;
  13.   `
  14. }) el.setAttribute(i, a[i]);

Chances are you don’t want to use this unless you are just speed coding, prototyping or playing around

If you do want to set multiple attributes, you’ll likely be doing so in an abstract way with one of the many popular js libs or frameworks.

In my personal projects I usually don’t use DOM related libs or frameworks, so I do this or something like it:

  1. function attrs(el, attrs) {
  2.   for (let i in attrs) {
  3.     el.setAttribute(i, attrs[i])
  4.   }
  5. }

I’ll always miss jQuery, used it for many years and found it extremely intuitive and a huge time saver… The above attrs function is just a crappier version of the jQuery attr method. Read more about that here in the docs.

// dom // hacks // javascript

Rectangle Intersection

  1. function testRectIntersection(divA, divB) {
  2.   const rectA = divA.getBoundingClientRect();
  3.   const rectB = divB.getBoundingClientRect();
  4.  
  5.   return (
  6.     rectA.left < rectB.right &&
  7.     rectA.right > rectB.left &&
  8.     rectA.top < rectB.bottom &&
  9.     rectA.bottom > rectB.top
  10.   );
  11. }
  12.  
  13. function rect(color, x, y, width = 100, height = 100) {
  14.   const el = document.body.appendChild(document.createElement('div'));
  15.   Object.assign(el.style, {
  16.     position: 'absolute',
  17.     left: `${x}px`,
  18.     top: `${y}px`,
  19.     width: `${width}px`,
  20.     height: `${height}px`,
  21.     background: color
  22.   });
  23.  
  24.   return el;
  25. }
  26.  
  27. const redBox = rect('red', 20, 20, 100, 100);
  28. const mover = rect('green', 130, 20, 100, 100);
  29. // with a `mousemove` only this won't be _great_ on mobile
  30. document.addEventListener('mousemove', e => {
  31.   Object.assign(mover.style, {
  32.     left: `${e.clientX - parseFloat(mover.style.width) / 2}px`,
  33.     top: `${e.clientY - parseFloat(mover.style.height) / 2}px`
  34.   });
  35.  
  36.   if (testRectIntersection(mover, redBox)) {
  37.     redBox.style.background = 'blue';
  38.   } else {
  39.     redBox.style.background = 'red';
  40.   }
  41. });

Move your mouse so that the green rectangle touches the red.

This snippet shows how to test the if two non-rotated rectangles are intersecting. The only real part of the code that does this is:

  1. rectA.left < rectB.right &&
  2. rectA.right > rectB.left &&
  3. rectA.top < rectB.bottom &&
  4. rectA.bottom > rectB.top

With an understanding of how x/y coordinates work on the web, it can be fun to draw this out and figure out why it works on your own.

I’ve used getBoundingClientRect to obtain the rectangles of two divs here. In the rare case where you need to calculate many many intersections frequently, getBoundingClientRect should be avoided if possible as it can get slow for a variety of reasons. The alternative to getBoundingClientRect is to keep track of all the rectangle coordinate and size information yourself.

// css // dom // graphics // javascript // math // tricks

Replace with “O”

  1. const txt = document.body.appendChild(
  2.   document.createElement('textarea')
  3. );
  4.  
  5. document.body.style.margin = 0;
  6. Object.assign(txt.style, {
  7.   position: 'relative',
  8.   width: '90%',
  9.   fontSize: '1.5em',
  10.   borderRadius: 0,
  11.   padding: '1em',
  12.   outline: 'none'
  13. });
  14. txt.setAttribute('placeholder', 'enter some text');
  15.  
  16. const result = document.body.appendChild(
  17.   document.createElement('div')
  18. );
  19.  
  20. Object.assign(result.style, {
  21.   padding: '1em', 
  22.   fontSize: '2em'
  23. });
  24.  
  25. txt.addEventListener('input', () => { 
  26.   result.innerHTML = txt.value.replace(/[aeiou]/gi, 'o');
  27. });

This snippet takes input into a textfield and replaces all vowels with the letter “o”. I saw a meme that did something like this with musical instruments piano = poono, guitar = gootor etc..

// css // dom // regex // strings

SVG Relative Touch/Mouse Events

  1. const hasTouch =
  2.   navigator.maxTouchPoints != null && navigator.maxTouchPoints > 0;
  3.  
  4. const el = document.body.appendChild(document.createElement('div'));
  5.  
  6. el.innerHTML = `
  7.   <svg id="mainSvg" width="100%" height="100%" viewBox="0 0 800 800">
  8.     <g transform="translate(100, 100) scale(0.8, 0.8) rotate(25, 400, 400)">
  9.       <rect x="0" y="0" width="800" height="800" fill="#ccc" stroke="none"/>
  10.       <text x="10" y="30" font-size="30px">click/tap the box</text>
  11.     </g>
  12.   </svg>
  13.   <style>
  14.     svg, div, body, html {
  15.       height: 100%;
  16.       width: 100%;
  17.       margin: 0; padding: 0;
  18.     }
  19.  
  20.     svg {
  21.       overflow: hidden;
  22.     }
  23.   </style>
  24. `;
  25. function createSvg(type) {
  26.   return document.createElementNS('http://www.w3.org/2000/svg', type);
  27. }
  28.  
  29. // mouse or touch location is always relative to the svg
  30. // any css transformations on the svg are only accounted for in 
  31. // Chrome unfortunately
  32. function svgTouch(e, svgEl) {
  33.   const touches = e.touches;
  34.   let locX, locY;
  35.   if (touches != null && touches.length > 0) {
  36.     locX = touches[0].clientX;
  37.     locY = touches[0].clientY;
  38.   } else {
  39.     locX = e.clientX;
  40.     locY = e.clientY;
  41.   }
  42.  
  43.   const pt = svgEl.createSVGPoint();
  44.   pt.x = locX;
  45.   pt.y = locY;
  46.  
  47.   const newPnt = pt.matrixTransform(svgEl.getScreenCTM().inverse());
  48.   return { locX: newPnt.x, locY: newPnt.y };
  49. }
  50.  
  51. document.addEventListener(hasTouch ? 'touchstart' : 'mousedown', e => {
  52.   // global id `mainSvg` :P
  53.   const { locX, locY } = svgTouch(e, mainSvg);
  54.  
  55.   const circle = createSvg('circle');
  56.   circle.cx.baseVal.value = locX;
  57.   circle.cy.baseVal.value = locY;
  58.   circle.r.baseVal.value = 20;
  59.   circle.style.fill = 'blue';
  60.  
  61.   mainSvg.appendChild(circle);
  62. });

This snippet shows how to get relative mouse/touch coordinates within an SVG element. The main trick here is on line 50… pt.matrixTransform(svgEl.getScreenCTM().inverse()); – we get the inverse transformation matrix of the “mainSvg” node and transform the mouse/touch coordinates by it.

Beware, when this was posted, as noted in the comments – CSS transforms on the SVG element or any of its parents won’t be accounted for in the `getScreenCTM` call in any browsers other than Chrome. There is an open Firefox issue for this I believe…

// dom // graphics // javascript // math // matrix // svg // tricks

Quick Touch Events 2 (easing)

  1. // no scrolling on mobile
  2. document.addEventListener('touchmove', e => e.preventDefault(), {
  3.   passive: false
  4. });
  5.  
  6. const hasTouch =
  7.   navigator.maxTouchPoints != null && navigator.maxTouchPoints > 0;
  8. // looking forward to `navigator?.maxTouchPoints > 0`
  9. // being better supported
  10.  
  11. function touchify(e) {
  12.   const touch = [];
  13.   touch.x = touch.y = 0;
  14.  
  15.   if (e.touches != null && e.touches.length > 0) {
  16.     touch.x = e.touches[0].clientX;
  17.     touch.y = e.touches[0].clientY;
  18.     for (let i = 0; i < e.touches.length; i++) {
  19.       touch[i] = e.touches[i];
  20.     }
  21.   } else {
  22.     touch.x = e.clientX;
  23.     touch.y = e.clientY;
  24.     touch[0] = { clientX: e.clientX, clientY: e.clientY };
  25.   }
  26.   return touch;
  27. }
  28.  
  29. let moveOrTouchMove;
  30.  
  31. if (hasTouch) {
  32.   moveOrTouchMove = 'touchmove';
  33. } else {
  34.   moveOrTouchMove = 'mousemove';
  35. }
  36.  
  37. function dot(x, y, radius, color) {
  38.   const el = document.createElement('div');
  39.   const size = `${radius * 2}px`;
  40.   Object.assign(el.style, {
  41.     position: 'absolute',
  42.     left: `${x}px`,
  43.     top: `${y}px`,
  44.     width: size,
  45.     height: size,
  46.     transform: `translate(${-radius}px, ${-radius}px)`,
  47.     borderRadius: '50%',
  48.     background: color
  49.   });
  50.   el.classList.add('dot');
  51.   document.body.appendChild(el);
  52.   return el;
  53. }
  54.  
  55. let dotX = 100,
  56.   dotY = 100,
  57.   touchX = 200,
  58.   touchY = 150,
  59.   damp = 12,
  60.   dotEl = dot(dotX, dotY, 20, 'red');
  61.  
  62. document.addEventListener(moveOrTouchMove, e => {
  63.   const { x, y } = touchify(e);
  64.   touchX = x;
  65.   touchY = y;
  66. });
  67.  
  68. function loop() {
  69.   dotX += (touchX - dotX) / damp;
  70.   dotY += (touchY - dotY) / damp;
  71.   Object.assign(dotEl.style, {
  72.     left: `${dotX}px`,
  73.     top: `${dotY}px`
  74.   });
  75.   window.requestAnimationFrame(loop);
  76. }
  77. loop();

Move your mouse on desktop or your finger on mobile – watch the red dot follow…

This is a simpler version of some of the things used in yesterdays post – just a quick way to normalize touch events – just one of many ways to do this – for simple stuff this is my goto.

If you want to try it out on its own page – take a look here (specifically good for trying it on mobile).

You’ll probably want a to use a meta tag like this – for the full effect.

  1. <meta name="viewport" content="width=device-width, initial-scale=1.0">
snippet.zone ~ 2021-24 /// {s/z}