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

Creative Coding Greyscale Circle Texture

  1. const canvas = document.createElement('canvas'),
  2.   c = canvas.getContext('2d');
  3.  
  4. document.body.appendChild(canvas);
  5. document.body.style.margin = 0;
  6.  
  7. function brush() {
  8.   let pnts = [];
  9.   let cx = innerWidth * Math.random(),
  10.     cy = innerHeight * Math.random();
  11.   let x = y = 0;
  12.   let ro = innerWidth / (1 + Math.random() * 2);
  13.   let oro = ro;
  14.   let r2 = innerWidth / (2 + Math.random() * 2);
  15.   let rx = innerWidth / 4;
  16.   let ry = rx;
  17.  
  18.   ro = Math.min(300, ro);
  19.  
  20.   let t = 0;
  21.   let vt = 0.05;
  22.   let wt = 0.05;
  23.   let fric = 0.99;
  24.   let t2 = 0;
  25.   let x2 = 0;
  26.   let y2 = 0;
  27.   let vx2 = 0;
  28.   let vy2 = 0;
  29.   let col = ['black', 'white'][Math.floor(Math.random() * 2)];
  30.   let cc = 0; //[0, 255][Math.floor(Math.random() * 2)];
  31.   let s = 0.05 + Math.random() * 0.25;
  32.  
  33.   function draw() {
  34.     c.save();
  35.     c.translate(cx, cy);
  36.     c.scale(s, s);
  37.     for (let i = 0; i < 50; i++) {
  38.       if (Math.random() < 0.13) {
  39.         vt += Math.random() * 0.1 - 0.05;
  40.       }
  41.       if (Math.random() < 0.13) {
  42.         wt += Math.random() * 0.1 - 0.05;
  43.       }
  44.       vt *= 0.99;
  45.       wt *= 0.98;
  46.       rx += (vt + wt * r2 + ro - rx) / 12;
  47.       ry += (vt + wt * r2 + ro - ry) / 12;
  48.  
  49.       if (Math.random() < 0.001) {
  50.         ro += 100 * Math.sin(t2 / 5);
  51.         if (Math.abs(ro) < 50) ro = 50;
  52.       }
  53.       t2 += 0.01;
  54.       x = rx * Math.cos(t / 10) + y2;
  55.       y = ry * Math.sin(t / 10) + x2;
  56.  
  57.       if (Math.random() < 0.01) {
  58.         vx2 += Math.random() * 0.2 - 0.1;
  59.       }
  60.       if (Math.random() < 0.01) {
  61.         vy2 += Math.random() * 0.2 - 0.1;
  62.       }
  63.  
  64.       vx2 *= fric;
  65.       vy2 *= fric;
  66.  
  67.       x2 += vx2;
  68.       y2 += vy2;
  69.       t += vt;
  70.  
  71.       c.fillStyle = col;
  72.       c.fillRect(x, y, 4, 4);
  73.       pnts.push([x, y]);
  74.  
  75.       if (Math.random() < 0.1) {
  76.         const a = pnts[Math.floor(Math.random() * pnts.length)];
  77.         const b = pnts[Math.floor(Math.random() * pnts.length)];
  78.         c.strokeStyle = `rgba(${cc}, ${cc}, ${cc}, ${Math.random() / 2})`;
  79.         c.beginPath();
  80.         c.moveTo(a[0], a[1]);
  81.         c.lineTo(b[0], b[1]);
  82.         c.stroke();
  83.       }
  84.     }
  85.     c.restore();
  86.   }
  87.   return draw;
  88. }
  89.  
  90. const NUM = 20;
  91. let brushes = [];
  92.  
  93. function resize() {
  94.   canvas.width = innerWidth;
  95.   canvas.height = innerHeight;
  96.   c.fillStyle = 'gray';
  97.   c.fillRect(0, 0, canvas.width, canvas.height);
  98.  
  99.   brushes = [];
  100.   for (let i = 0; i < NUM; i++) {
  101.     brushes.push(brush());
  102.   }
  103. }
  104. addEventListener('resize', resize);
  105. resize();
  106.  
  107. function loop() {
  108.   c.globalAlpha = 1;
  109.   c.fillStyle = 'rgba(155, 155, 155, 0.0018)';
  110.   c.fillRect(0, 0, canvas.width, canvas.height);
  111.   c.globalAlpha = 0.5;
  112.   for (let i = 0; i < brushes.length; i++) {
  113.     brushes[i]();
  114.   }
  115.   requestAnimationFrame(loop);
  116. }
  117. loop();

I learned how to code making things like this in Director Lingo. This speed coded snippet draws some shaky circles and lines. (Make sure to view it in fullscreen).

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.

Save Large Canvas

  1. // https://stackoverflow.com/questions/38781968/problems-downloading-big-filemax-15-mb-on-google-chrome
  2. function dataUriToBlob(dataURI) {
  3.   const binStr = atob(dataURI.split(',')[1]);
  4.   const len = binStr.length;
  5.   const arr = new Uint8Array(len);
  6.   const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
  7.   for (let i = 0; i < len; i++) {
  8.     arr[i] = binStr.charCodeAt(i);
  9.   }
  10.  
  11.   const blob = new Blob([arr], {
  12.     type: mimeString
  13.   });
  14.  
  15.   return URL.createObjectURL(blob);
  16. }
  17.  
  18. const save = document.createElement('a');
  19. save.innerText = 'save this big image';
  20. document.body.appendChild(save);
  21.  
  22. const canvas = document.createElement('canvas');
  23. const c = canvas.getContext('2d');
  24.  
  25. canvas.width = window.innerWidth * 4;
  26. canvas.height = window.innerHeight * 4;
  27. canvas.style.transformOrigin = '0 0';
  28. canvas.style.transform = `scale(0.14)`;
  29. document.body.appendChild(canvas);
  30.  
  31. c.fillStyle = 'black';
  32. c.fillRect(0, 0, canvas.width, canvas.height);
  33.  
  34. const size = Math.max(window.innerWidth, window.innerHeight);
  35.  
  36. // draw some rectangles
  37. c.globalCompositeOperation = 'difference'
  38. for (let i = 0; i < 100; i++) {
  39.   const angle = Math.random() * 180 + 180;
  40.   const color = `hsl(${angle}deg, 50%, 50%)`;
  41.   c.fillStyle = color;
  42.   const width = Math.random() * size + 100;
  43.   const height = Math.random() * size + 100;
  44.   const x = Math.random() * canvas.width - width / 2;
  45.   const y = Math.random() * canvas.height - height / 2;
  46.   c.fillRect(
  47.     x, 
  48.     y, 
  49.     width, 
  50.     height);
  51. }
  52.  
  53. save.download = 'big-image';
  54. save.href = dataUriToBlob(canvas.toDataURL());

Weirdly if you try to save a massive data URI to a file (> 15mb) it will just fail. In order to do this properly, you need to convert to a blob first and then save. I encountered this at some point and learned of the solution from user doppelgreener over at stack overflow. Take a look at the post here.

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
snippet.zone ~ 2021-24 /// {s/z}