Search code examples
javascriptperformancesvgdom

Script-generated SVGs in HTML page: Pros & Cons of DOM manipulation vs. "SVG code"


I want dozens of diagrams rendered, each with hundreds of uniform vertical lines: same colour, (scaled) width & start&end decoration.
I use inline SVG, markers, "problem domain" coordinates and specify transforms intending to keep SVG code processing in check; easy human interpretation a welcome bonus.

I managed to have concise SVG code generated, but on second thought, generating text to be interpreted feels indirect compared to manipulating DOM objects.

A miniscule example:

<svg style="background-color: #FFF;" width="300" height="180">
  <title>Bert Dobbelaere N9L25D7</title>
  <defs><marker id="dotB" markerWidth="4" markerHeight="4" refX="2" refY="2"><circle cx="2" cy="2" r="2" fill="black"></circle></marker></defs>
  <g stroke="black">
    <path stroke-width="0.05" d="M-.5 1m1-1v1m1-1v1m1-1v1m1-1v1m1-1v1m1-1v1m1-1v1m1-1v1m1-1v1"
     transform="matrix(0,20,333,0, 0,0)" /></g>
  <g marker-start="url(#dotB)" marker-mid="url(#dotB)" marker-end="url(#dotB)" stroke="black" fill="black" stroke-width="0.1">
    <path d="M0 1m0 0v6m.5-3v4m0-8v3m.5-1v3m1.7-5v7m.5-4v5m.5-6v2m0 1v1m1.7-5v2m0 4v1m0-4v1m.5-5v2m1.7 1v3m.5-5v3m0 1v2m1.7-1v2m0-5v2m0-5v1m.5 1v2m1.7 2v1m0-3v1m0-3v1m1.7 2v1m0-3v1m0-3v1"
     transform="scale(20)translate(0.65,0.5)" />
  </g>
</svg>

What are the pros and cons of EITHER

A) manipulating DOM objects   - OR -
1) generating SVG code?

from preliminary tinkering, "the DOM code" would be several times the size of the script to generate the SVG code plus that SVG code combined, so what is saved interpreting SVG may be more than compensated by more script to interpret.


Solution

  • I want dozens of inline SVGs rendered, each with hundreds of uniform vertical lines

    Immediatly smells like a Web Component with shadowDOM to me

    • A Web Component (you write once) can be re-used (they are your own HTML tags)

    • ShadowDOM makes sure there are no duplicate ID issues in the page

    • User Browser/CPU does all the work creating SVG
      Unless you are doing 60FPS animation simultanously, that CPU is barely doing anything.

    • Creating SVG HTML (strings) (which the Browser parses when inserted in the page) is way easier than creating SVG DOM nodes,
      no NameSpace issues, shorter code to manipulate.
      Only when you need to do more after the SVG is in the DOM you want to work with DOM.
      But you can grab them SVG DOM Nodes after .innerHTML instertion in the page with document.querySelector as well.

    You only have to come up with some syntax to draw the vertical lines,
    I went for linenrstart to linenumberend

    So Web Component <svg-lines>

    <svg-lines lines="1-9/2-8/3-7/4-6/5-5/6-4/7-3/8-2/9-1"></svg-lines>

    draws one:

    And

        <svg-lines lines="2-8/1-4,5-9/3-6/0/0/1-8/4-9/3-5,6-7/0/0/0/2-4,5-6,8-9/1-3/0/0/4-7/2-5,6-8/0/0/1-2,4-6,7-9/3-5/0/0/0/3-4,5-6,7-8/0/0/2-3,4-5,6-7"></svg-lines>
        <svg-lines lines="1-9/2-8/3-7/4-6/5-5/6-4/7-3/8-2/9-1"></svg-lines>
        <svg-lines lines="1-2/2-3/3-4/4-5/5-6/6-7/7-8/8-9"></svg-lines>
    

    draws all 3:

    All code required

    Disclaimer: I most likely made a logic error in the line calculations

    <script>
      customElements.define('svg-lines', class extends HTMLElement {
        connectedCallback() {
          const [xstart, ystart, linecount, gapx] = [15, 10, 9, 20];
          const line = ({ x1 = 0, x2 = x1, y1, y2 = y1 }) => `<line x1=${x1} y1=${y1} x2=${x2} y2=${y2} />`;
          this.attachShadow({ mode: 'open' }).innerHTML = 
    `<svg width="200" viewBox="0 0 300 180">
      <defs><marker id=M markerWidth=4 markerHeight=4 refX=2 refY=2><circle cx=2 cy=2 r=2 fill=green /></defs>
      <g stroke="red">${Array(linecount).fill(0).map((_, idx) => line({ y1: ystart + idx * gapx, x2: "100%" })).join('')}</g>
      <g marker-start="url(#M)" marker-end="url(#M)" stroke="blue" stroke-width="2">
       ${(this.getAttribute("lines")).split('/')
         .map((vlines, x) => vlines.split(',').map((vline, ys) => {
           let [y1, y2] = vline.split('-');
           if (y2) return line({ x1: xstart + x * gapx / 2, y1: y1 * gapx - ystart, y2: y2 * gapx - ystart });
           else return '';
         })).join('')}
      </g>
     </svg>`;
        }
      });
    </script>
    <svg-lines lines="2-8/1-4,5-9/3-6/0/0/1-8/4-9/3-5,6-7/0/0/0/2-4,5-6,8-9/1-3/0/0/4-7/2-5,6-8/0/0/1-2,4-6,7-9/3-5/0/0/0/3-4,5-6,7-8/0/0/2-3,4-5,6-7"></svg-lines>
    <svg-lines lines="1-9/2-8/3-7/4-6/5-5/6-4/7-3/8-2/9-1"></svg-lines>
    <svg-lines lines="1-2/2-3/3-4/4-5/5-6/6-7/7-8/8-9"></svg-lines>