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

2x mod 1 Map

  1. function twoXmodMap(start = 0.2) {
  2.   const seed = () => Math.random() * Math.random() * start
  3.   let xn = seed()
  4.   let xn1
  5.   let iter = 0
  6.   return () => {
  7.     iter++
  8.     if (iter > 50) {
  9.       xn = seed()
  10.       iter = 0
  11.     }
  12.  
  13.     // this is the key part:
  14.     xn1 = (2 * xn) % 1
  15.     xn = xn1
  16.  
  17.     return xn1
  18.   }
  19. }
  20.  
  21. const el = document.body.appendChild(document.createElement('div'))
  22. el.innerHTML = `
  23.   <svg>
  24.     <path d="M 0 0 L 1 1" fill="none" stroke="red" />
  25.   </svg>
  26.   <style>
  27.     svg, body, html {
  28.       width: 100%;
  29.       height: 100%;
  30.       overflow: visible;
  31.     }
  32.   </style>
  33. `
  34.  
  35. const path = el.querySelector('path')
  36. let pathStr = ''
  37.  
  38. const map = twoXmodMap()
  39.  
  40. let x = 0
  41. let yo = 0
  42. let y;
  43. function loop() {
  44.   y = map()
  45.   x += 2
  46.  
  47.   if (pathStr == '' || x > 300) {
  48.     x = 10
  49.     yo += 50
  50.     pathStr += `M 10 ${yo}`
  51.   } else {
  52.     pathStr += ` L ${x} ${yo + y * 30} `
  53.   }
  54.  
  55.   path.setAttribute('d', pathStr)
  56.   window.requestAnimationFrame(loop)
  57. }
  58. loop()

Drawing the 2x mod 1 map.

// javascript // math // svg

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

SVG Drawing Program

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.   <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  6.  
  7.     <title>Drawing</title>
  8.     <style>
  9.       body,
  10.       html {
  11.         user-select: none;
  12.         height: 100%;
  13.         margin: 0;
  14.         font-family: sans-serif;
  15.         background: white;
  16.       }
  17.  
  18.       body {
  19.         position: fixed;
  20.         top: 0;
  21.       }
  22.  
  23.       .ui {
  24.         position: fixed;
  25.         display: flex;
  26.         bottom: 0;
  27.         width: 100%;
  28.         margin-bottom: .2em;
  29.       }
  30.  
  31.       .ui .tool {
  32.         font-size: 2em;
  33.         flex: 1;
  34.         text-align: center;
  35.       }
  36.  
  37.       .tool span {
  38.         cursor: pointer;
  39.         display: inline-block;
  40.         transform: scale(0.7);
  41.         transition: transform 250ms ease-out;
  42.       }
  43.  
  44.       .tool span.selected {
  45.         transform: scale(1);
  46.       }
  47.  
  48.       svg {
  49.         overflow: visible;
  50.         height: 100%;
  51.         width: 100%;
  52.         margin: 0;
  53.         padding: 0;
  54.       }
  55.  
  56.       input[type=color] {
  57.         position: absolute;
  58.         left: 0;
  59.         top: 0;
  60.         height: 100%;
  61.         opacity: 0;
  62.         cursor: pointer;
  63.       }
  64.  
  65.       .message {
  66.         position: fixed;
  67.         left: 0;
  68.         top: 0;
  69.         padding: 1em;
  70.         width: 100%;
  71.         text-align: center;
  72.         font-size: 1.2em;
  73.         transition: opacity 250ms ease-out;
  74.         pointer-events: none;
  75.       }
  76.  
  77.       .message.hide {
  78.         opacity: 0;
  79.       }
  80.     </style>
  81.   </head>
  82.   <body>
  83.  
  84.     <svg class="paper" width="100%" height="100%">
  85.       <style>
  86.         path {
  87.           stroke-linecap: round;
  88.           stroke-linejoin: round;
  89.           fill: none;
  90.         }
  91.       </style>
  92.     </svg>
  93.     <div class="ui">
  94.       <div class="tool"><span class="pencil" class="pencil icon selected">&#9999;&#65039;</span></div>
  95.       <div class="tool"><span class="brush icon">&#128396;&#65039;</span></div>
  96.       <div class="tool"><span class="colors icon">&#x1f3a8;
  97.         <input class="color" type="color"></span></div>
  98.     </div>
  99.     <div class="message">
  100.       click/tap and drag to draw
  101.     </div>
  102.  
  103.     <script>
  104.       (() => { 
  105.         // no scrolling on mobile
  106.         document.addEventListener('touchmove', e => e.preventDefault(), {
  107.           passive: false
  108.         });
  109.  
  110.         function touchify(e) {
  111.           const touch = [];
  112.           touch.x = touch.y = 0;
  113.  
  114.           if (e.touches != null && e.touches.length > 0) {
  115.             touch.x = e.touches[0].clientX;
  116.             touch.y = e.touches[0].clientY;
  117.             for (let i = 0; i < e.touches.length; i++) {
  118.               touch[i] = e.touches[i];
  119.             }
  120.           } else {
  121.             touch.x = e.clientX;
  122.             touch.y = e.clientY;
  123.             touch[0] = { clientX: e.clientX, clientY: e.clientY };
  124.           }
  125.           return touch;
  126.         }
  127.  
  128.         const els = {};
  129.  
  130.         [...document.querySelectorAll('[class]')].forEach(el => {
  131.           els[el.classList[0]] = el;
  132.         });
  133.  
  134.         const { paper, pencil, color, brush, message } = els;
  135.  
  136.         let lastSelected = pencil;
  137.         let strokeWidth = 1;
  138.         let downCount;
  139.         let down;
  140.         let currPath;
  141.         let pathPoints;
  142.  
  143.         const onDown = e => {
  144.           down = true;
  145.           downCount = 0;
  146.           pathPoints = [];
  147.         };
  148.  
  149.         const onMove = e => {
  150.           const touch = touchify(e);
  151.  
  152.           if (down) {
  153.             downCount++;
  154.  
  155.             if (downCount === 1) {
  156.               currPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  157.               currPath.setAttribute('stroke', color.value);
  158.               currPath.setAttribute('stroke-width', strokeWidth);
  159.               currPath.setAttribute('d', `M ${touch.x} ${touch.y}`);
  160.               pathPoints.push([touch.x, touch.y]);
  161.               paper.appendChild(currPath);
  162.             } else {
  163.               let path = `M ${pathPoints[0][0]} ${pathPoints[0][1]} L `;
  164.               pathPoints.push([touch.x, touch.y]);
  165.               for (let i = 1; i < pathPoints.length; i++) {
  166.                 path += `${pathPoints[i][0]} ${pathPoints[i][1]} `;
  167.               }
  168.               currPath.setAttribute('d', path);
  169.             }
  170.  
  171.             if (downCount === 10) {
  172.               message.classList.add('hide');
  173.             }
  174.           }
  175.         };
  176.  
  177.         const onUp = e => {
  178.           down = false;
  179.         };
  180.  
  181.         const onClick = e => {
  182.           if (e.target.classList.contains('pencil')) {
  183.             strokeWidth = 1;
  184.           } else if (e.target.classList.contains('brush')) {
  185.             strokeWidth = 10;
  186.           }
  187.           if (e.target.classList.contains('icon')) {
  188.             if (lastSelected) lastSelected.classList.remove('selected');
  189.             e.target.classList.add('selected');
  190.             lastSelected = e.target;
  191.           }
  192.         };
  193.  
  194.         document.addEventListener('mousedown', onDown);
  195.         document.addEventListener('touchstart', onDown);
  196.  
  197.         document.addEventListener('mousemove', onMove);
  198.         document.addEventListener('touchmove', onMove);
  199.  
  200.         document.addEventListener('mouseup', onUp);
  201.         document.addEventListener('touchend', onUp);
  202.  
  203.         document.addEventListener('click', onClick);
  204.         document.addEventListener('touchend', onClick);
  205.       })();
  206.     </script>
  207.   </body>
  208. </html>

SVG based drawing program.

// css // javascript // svg // ui

Animation Along Path SVG

  1. const el = document.body.appendChild(
  2.   document.createElement('div'));
  3.  
  4. el.innerHTML = `
  5.   <svg width="100%" height="100%" viewBox="0 0 550 496">
  6.     <path 
  7.       d="M 20 10 C 100 100 300 00 300 100 200 200 150 300 20 10" 
  8.       stroke="black" fill='none' vector-effect="non-scaling-stroke"/>
  9.     <circle cx="20" cy="10" r="6" fill="red" />
  10.   </svg>
  11.   <style>
  12.     svg, div, body, html {
  13.       overflow: visible; 
  14.       height: 100%; 
  15.       width: 100%;
  16.       margin: 0; padding: 0;
  17.     }
  18.   </style>
  19.  `;
  20.  
  21. const circle = el.querySelector('circle');
  22. const path = el.querySelector('path');
  23. const length = path.getTotalLength();
  24. let pos = 0;
  25.  
  26. function loop() {
  27.   pos += 3;
  28.   if (pos > length) {
  29.     pos = 0;
  30.   }
  31.   const point = path.getPointAtLength(pos);
  32.   circle.cx.baseVal.value = point.x;
  33.   circle.cy.baseVal.value = point.y;
  34.   requestAnimationFrame(loop);
  35. }
  36. loop();

SVG has an easy way to animate something along an arbitrary path… getPointAtLength

Ellipse Path SVG

  1. const ellipse = (cx, cy, rx, ry) => { 
  2.   if (ry == null) ry = rx;
  3.   return `
  4.     M${cx-rx} ${cy}a${rx} ${ry} 
  5.     0 1 0 ${rx*2} 0a${rx},${ry} 
  6.     0 1 0 -${rx*2},0`
  7. }
  8.  
  9. const svg = `
  10. <svg style="overflow:hidden;stroke:black;fill:none">
  11.   <path d="${ellipse(80, 50, 30)}" />
  12.   <path d="${ellipse(80, 120, 60, 20)}" />
  13.   <path d="${ellipse(150, 60, 20, 40)}" />
  14. </svg>`
  15.  
  16. document.body.innerHTML = svg;

Draw an ellipse inside an svg path. I saw this method over on this stackoverflow thread at some point and tweaked it a little bit. Very nice simple solution from user yogi

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