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

Range Slider and Progress Bar

  1. const NUM = 8;
  2. const NUM_M_1 = NUM - 1;
  3. const ui = document.createElement('div')
  4. ui.innerHTML = `
  5.   <div class="ui">
  6.     <div class="progress" data-ref>
  7.       <div class="progressFill" data-ref></div>
  8.     </div>
  9.     <div class="text" data-ref>
  10.     0% of 100%
  11.     </div>
  12.  
  13.     <div class="dots" data-ref>
  14.     ${
  15.       `<div></div>`.repeat(NUM)
  16.     }
  17.     </div>
  18.  
  19.     <label>
  20.       drag the slider...
  21.       <input class="slider" type="range" min="0" max="100" step="1" value="0" data-ref/>
  22.     </label>
  23.   </div>
  24.   <style>
  25.     body {
  26.       font-family: sans-serif;
  27.     }
  28.     .progress, .dots {
  29.       position: relative;
  30.       width: 80%;
  31.       margin: 0 auto;
  32.       height: 30px;
  33.       border: 1px solid black;
  34.     }
  35.     .progressFill {
  36.       height: 100%;
  37.       width: 0;
  38.       background: red;
  39.     }
  40.     .text {
  41.       padding: 1em;
  42.       background: #ccc;
  43.       margin: .5em;
  44.       font-weight: bold;
  45.     }
  46.     label {
  47.       display: block;
  48.       margin: 1em;
  49.     }
  50.     .slider {
  51.       cursor: pointer;
  52.     }
  53.     .dots {
  54.       border: none;
  55.     }
  56.     .dots div {
  57.       height: 100%;
  58.       width: ${100 / NUM}%;
  59.       float: left;
  60.       transition: background 400ms ease-out;
  61.       background: transparent;
  62.       border-radius: 500px;
  63.       box-shadow: inset 0 0px 0 3px blue;
  64.     }
  65.     .dots div:nth-child(1) {
  66.       background: red;
  67.     }
  68.  
  69.   </style>
  70. `;
  71. document.body.appendChild(ui);
  72.  
  73. // select everything with a `data-ref`
  74. const els = {};
  75. ;[...document.querySelectorAll('[data-ref]')]
  76.   .forEach(el => {
  77.     els[el.classList[0]] = el;
  78.   });
  79.  
  80. function update(e) {
  81.  
  82.   // normal prog bar
  83.   const val = e.target.value;
  84.   els.progressFill.style.width = `${val}%`;
  85.   els.text.innerHTML = `
  86.     ${val}% of 100%
  87.   `;
  88.  
  89.   // segmented dot prog bar
  90.   const idx = Math.floor(val / (100 / NUM));
  91.   if (idx < NUM) { 
  92.     for (let i = 0; i < NUM; i++) {
  93.       els.dots.children[i]
  94.         .style.background = i <= idx ? 'red' : 'white'
  95.     }
  96.   }
  97. }
  98.  
  99. els.slider.addEventListener('input', update);
  100. els.slider.addEventListener('change', update);

Drag slider and watch two progress bars and a text readout update. There are some interesting vanilla tricks in this one.

This trick and a variety of variations on it is pretty powerful:

  1. const els = {};
  2. [...document.querySelectorAll('[data-ref]')].forEach(el => {
  3.   els[el.classList[0]] = el;
  4. });
// css // dom // graphics // javascript // math // strings // tricks // ui

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

Line to Line Intersection

  1. // line to line intersection https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
  2. function lineIntersection(p1, p2, p3, p4) {
  3.   let ip = { x: 0, y: 0 };
  4.   // rare case where I use `_` in a non-constant variable name
  5.   // to make this easy to read to with the wikipedia info
  6.   const x4_x3 = p4.x - p3.x;
  7.   const y4_y3 = p4.y - p3.y;
  8.   const x2_x1 = p2.x - p1.x;
  9.   const y2_y1 = p2.y - p1.y;
  10.   const x1_x3 = p1.x - p3.x;
  11.   const y1_y3 = p1.y - p3.y;
  12.   let nx, ny, dn;
  13.  
  14.   nx = x4_x3 * y1_y3 - y4_y3 * x1_x3;
  15.   ny = x2_x1 * y1_y3 - y2_y1 * x1_x3;
  16.   dn = y4_y3 * x2_x1 - x4_x3 * y2_y1;
  17.  
  18.   nx /= dn;
  19.   ny /= dn;
  20.  
  21.   // has intersection
  22.   if (nx >= 0 && nx <= 1 && ny >= 0 && ny <= 1) {
  23.     ny = p1.y + nx * y2_y1;
  24.     nx = p1.x + nx * x2_x1;
  25.     ip.x = nx;
  26.     ip.y = ny;
  27.   } else {
  28.     // no intersection
  29.     ip = null;
  30.   }
  31.   return ip;
  32. }
  33.  
  34. const el = document.body.appendChild(
  35.   document.createElement('div'));
  36.  
  37. // hard coding line values for simplicity and ease of understanding
  38. el.innerHTML = `
  39.   <svg width="100%" height="100%" viewBox="0 0 550 496">
  40.     <path id='path' d="M 10 10 L 300 300 M 100 10 L 160 320" stroke="black" fill='none' vector-effect="non-scaling-stroke"/>
  41.     <rect id="intersecton" x="0" y="0" width="10" height="10" fill="red" />
  42.   </svg>
  43.   <style>
  44.     svg, div, body, html {
  45.       overflow: visible; 
  46.       height: 100%; 
  47.       width: 100%;
  48.       margin: 0; padding: 0;
  49.     }
  50.   </style>
  51. `;
  52.  
  53. const loc = lineIntersection(
  54.   {x: 10, y: 10}, {x: 300, y:300},
  55.   {x: 100, y: 10}, {x: 160, y:320}
  56. );
  57.  
  58. // subtract half the size of the rect from both axis to center it
  59. intersecton.x.baseVal.value = loc.x - 5;
  60.  
  61. // @NOTE: using the `id` global `window.intersection` 
  62. // is just good for demos - little risky for real stuff 
  63. // since it lends itself to easy collision
  64. window.intersecton.y.baseVal.value = loc.y - 5;

Line to line intersection rendered with SVG.

Mini Markdown Parser

  1. function mark(str) {
  2.   const lines = str.split`\n`;
  3.   let wasLi;
  4.  
  5.   for (let i = 0; i < lines.length; i++) {
  6.     const curr = lines[i];
  7.     const line = curr.trim();
  8.     const hdr = line.split`#`.length - 1;
  9.  
  10.     if (line === '') { 
  11.       lines[i] = '<br>';
  12.     } else if (hdr > 0) {
  13.       lines[i] = `<h${hdr} style="margin-bottom:0">
  14.         ${curr.replace(/#/g, '')}</h${hdr}>`;
  15.     } else if (line === '***') {
  16.       lines[i] = '<hr>';
  17.     } else if (line.startsWith('- ')) {
  18.       lines[i] = `${!wasLi ? '<ul>' : ''}
  19.         <li>${curr.replace(/-\s+/, '')}</li>`;
  20.       wasLi = true;
  21.     } else if (wasLi) {
  22.       lines[i - 1] += '</ul>\n';
  23.       wasLi = false;
  24.     }
  25.   }
  26.  
  27.   return lines.join`\n`
  28.     .replace(/\*\*(.+)\*\*/gm, '<b>$1</b>')
  29.     .replace(/_(.+)_/gm, '<i>$1</i>')
  30.     .replace(/~~(.+)~~/gm, '<strike>$1</strike>')
  31.     .replace(/`(.+)`/gm, '<code>$1</code>');
  32. }
  33.  
  34. // try it out
  35.  
  36. const md = document.body.appendChild(document.createElement('div'));
  37. md.style.fontFamily = 'sans-serif';
  38. md.innerHTML = `# Mini Markdown Subset
  39.  
  40. This is a subset of markdown stuff
  41.  
  42. ## It includes
  43.  
  44. - headers
  45. - **bold styles**
  46. - _italic styles, <br> multiline fine..._
  47. - \`code style\`
  48.  
  49. Other than ~~strikethrough~~ that is pretty much it... oh and **hr** tags
  50. ***
  51. ***
  52. _here is some italic text with some bold text **nested** <br>within it etc..._
  53. `;
  54. md.innerHTML = mark(md.innerHTML);

This code takes a string and formats a few parts of it with markdown syntax. This only handles a small subset of actual markdown.

I was thinking about markdown in code comments last night as I was working on a forthcoming snippet.zone github repo. Anyway, I decided to speed-code the main things I find myself using in markdown so that maybe I can do markdown formatted comments at some point…

Random Negative or Positive

  1. Math.round(Math.random()) * 2 - 1;

Randomly generate either -1 or 1. There are many ways to do this.

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