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

Easy Mouse Events

  1. const ui = document.body.appendChild(
  2.   document.createElement('div')
  3. );
  4.  
  5. ui.innerHTML = `
  6.   <button class="hello">say hello</button>
  7.   <button class="make-box">make a box</button>
  8.   <button class="remove-boxes">remove boxes</button>
  9.   <button class="consoleLog red">log in console</button>
  10.  
  11.   <style>
  12.     button { margin: .5em; cursor: pointer; }
  13.     .red { background: red; color: white; }
  14.     .box {
  15.       position: relative;
  16.       float: left;
  17.       width: 30px;
  18.       height: 30px;
  19.       margin: 1em;
  20.       background: blue;
  21.     }
  22.   </style>
  23. `;
  24.  
  25. const actions = {
  26.   hello(e) {
  27.     alert(e.target.className);
  28.   },
  29.  
  30.   ['make-box']() {
  31.     const box = document.body.appendChild(
  32.       document.createElement('div')
  33.     );
  34.     box.classList.add('box');
  35.   },
  36.  
  37.   ['remove-boxes']() {
  38.     const boxes = [...document.querySelectorAll('.box')];
  39.     const num = boxes.length;
  40.     boxes.forEach((el) => el.parentNode.removeChild(el));
  41.  
  42.    alert(
  43.       num === 0
  44.         ? 'no boxes to remove'
  45.         : `removing ${num} box${num > 1 ? 'es' : ''}`
  46.     );
  47.   },
  48.  
  49.   consoleLog(e) {
  50.     console.log('camelCase instead of kebab-case :D');
  51.   },
  52. };
  53.  
  54. document.addEventListener('mousedown', (e) => {
  55.   const action = actions[e.target.classList[0]];
  56.   if (action != null) action(e);
  57. });

This is a powerful little pattern for managing mouse/touch events. Rather than assigning many listeners, this snippet has one listener on the document. Anytime the page is clicked, we look at the event targets classList and use its first value as a key in an actions object.

I have used this or some variation of it many many times over the years. With a little customization it scales well into large projects. I always find myself on the fence about class naming conventions… kebab-case vs camelCase – probably because it just doesn’t matter that much. For large projects, each main section of the UI will have its own document.addEventListener just for organizational purposes.

Variations

The choice to use the first class as the key for the action is pretty arbitrary. Depending on how you like to set things up you could use the last value of the classList, the element id, the element name, or a custom data attribute etc… Like this:

  1. // id
  2. const action = actions[e.target.id];
  3.  
  4. // name
  5. const action = actions[e.target.name];
  6.  
  7. // data attribute <button data-my-id="test">test</button>
  8. const action = actions[e.target.dataset.myId];
// dom // events // javascript // tricks // ui

Pretty Print JSON in Console

  1.   const obj = {
  2.     x: 1, y: 2,
  3.     data: { test: 'xyz' }
  4.   };
  5.   console.log(JSON.stringify(obj, null, 2));

JSON.stringify has two more arguments that allow for pretty printing and processing of the object in question. The second argument is a “replacer” function and the third argument is for indentation. Read more details here.

I am pretty sure I first learned this from this stack overflow post.

I used this technique in yesterdays console hijacking post.

console.log Hijack

  1. const log = console.log;
  2. const consoleUI = document.body.appendChild(
  3.   document.createElement('div')
  4. );
  5. document.body.style.margin = 0;
  6. Object.assign(consoleUI.style, {
  7.   position: 'fixed',
  8.   bottom: 0,
  9.   width: '100%',
  10.   height: '30%',
  11.   maxHeight: '450px',
  12.   minHeight: '200px',
  13.   background: 'rgb(68, 68, 81)',
  14.   overflow: 'scroll'
  15. });
  16. function consoleRow(str) {
  17.   const row = document.createElement('div');
  18.   consoleUI.prepend(row);
  19.   row.innerText = str;
  20.   Object.assign(row.style, {
  21.     padding: '.5em',
  22.     fontFamily: 'sans-serif',
  23.     color: 'white',
  24.     borderBottom: '1px solid rgba(255, 255, 255, .1)',
  25.     whiteSpace: 'pre-wrap'
  26.   });
  27. }
  28.  
  29. console.log = (...args) => {
  30.   const formatted = args.map(val => {
  31.     return typeof val === 'object' ? 
  32.       JSON.stringify(val, null, 2) : val;
  33.   });
  34.   consoleRow(formatted.join(' '));
  35.   log.apply(console, args);
  36. };
  37.  
  38. // test it out
  39.  
  40. console.log(1, 2, 3, 4);
  41. console.log(new Date());
  42.  
  43. const someObject = {
  44.   test: 123,
  45.   testing: { x: 1, y: 2, z: 3 }
  46. };
  47. console.log(someObject);
  48.  
  49. console.log(3, 2, 1, 0);

I’m thinking about adding a little fake console to the Snippet Zone Quick Editor – just whipped this up as a proof of concept – something like this should work…

// css // hacks // javascript // meta // strings // tricks // ui

Distance Between Two Points (SVG)

  1. const dist = (x1, y1, x2, y2) => 
  2.   Math.sqrt(
  3.     (x1 - x2) ** 2 + 
  4.     (y1 - y2) ** 2);
  5.  
  6. const el = document.body.appendChild(
  7.   document.createElement('div')
  8. );
  9.  
  10. el.innerHTML = `
  11.   <svg style="overflow:visible;">
  12.     <circle id="circA" cx="150" cy="100" r="50" fill="gray" />
  13.     <circle id="circB" cx="150" cy="200" r="50" fill="blue" />
  14.     <text id="text" dy="20" dx="20">move mouse</text>
  15.   </svg>
  16.   <style>
  17.     body, html, svg {
  18.       width: 100%;
  19.       height: 100%;
  20.     }
  21.   </style>
  22. `;
  23.  
  24. function touch(e) {
  25.   const touches = e.touches;
  26.   let x, y;
  27.   if (touches != null && touches.length > 0) {
  28.     x = touches[0].clientX;
  29.     y = touches[0].clientY;
  30.   } else {
  31.     x = e.clientX;
  32.     y = e.clientY;
  33.   }
  34.   return { x, y };
  35. }
  36.  
  37. const hasTouch = navigator.maxTouchPoints > 0;
  38. const move = hasTouch ? 'touchmove' : 'mousemove';
  39. document.addEventListener(move, e => {
  40.   const { x, y } = touch(e);
  41.  
  42.   // using global ids :D
  43.   circB.cx.baseVal.value = x;
  44.   circB.cy.baseVal.value = y;
  45.  
  46.   const distance = dist(
  47.     circA.cx.baseVal.value, 
  48.     circA.cy.baseVal.value, x, y
  49.   );
  50.   text.innerHTML = 'move mouse, distance: ' + distance;
  51.  
  52.   circA.r.baseVal.value = distance - circB.r.baseVal.value;
  53. });

This snippet shows how to calculate the distance between two points. The dist function uses the pythagorean theorem:

  1. const dist = (x1, y1, x2, y2) => 
  2.   Math.sqrt(
  3.     (x1 - x2) ** 2 + 
  4.     (y1 - y2) ** 2);
// dom // graphics // javascript // math // svg

Combine Two or More Arrays

  1. const numbers = [1, 2, 3];
  2. const letters = ['a', 'b', 'c'];
  3. const symbols = ['!', '@', '#'];
  4.  
  5. // use spread operator
  6. const combined = [...numbers, ...letters, ...symbols];
  7. console.log('first', combined);
  8.  
  9. // older way
  10. const combinedConcat = numbers
  11.   .concat(letters)
  12.   .concat(symbols);
  13.  
  14. console.log('second', combinedConcat);

If you click “Try it out” be sure to open your console.

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