Search code examples
javascriptsvginkscape

Get element by ID and change its color in SVG Inkscape


I have a simple drawing I created using Inkscape, this is the Inkscape file:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   width="847.18634"
   height="635.83612"
   viewBox="0 0 224.15139 168.23164"
   version="1.1"
   id="svg5"
   inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
   sodipodi:docname="drawing.svg"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <script
     xlink:href="script.js"
     id="script2" />
  <sodipodi:namedview
     id="namedview7"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:showpageshadow="2"
     inkscape:pageopacity="0.0"
     inkscape:pagecheckerboard="false"
     inkscape:deskcolor="#d1d1d1"
     inkscape:document-units="px"
     showgrid="false"
     showborder="true"
     shape-rendering="auto"
     inkscape:zoom="0.69947917"
     inkscape:cx="711.24348"
     inkscape:cy="274.48995"
     inkscape:window-width="1920"
     inkscape:window-height="1054"
     inkscape:window-x="0"
     inkscape:window-y="0"
     inkscape:window-maximized="1"
     inkscape:current-layer="layer1" />
  <defs
     id="defs2" />
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(-66.199001,-70.460732)">
    <circle
       style="fill:#ff0000;stroke-width:1;stroke-linejoin:round;paint-order:markers fill stroke"
       id="circle"
       cx="97.842422"
       cy="117.80233"
       r="31.643421" />
    <rect
       style="fill:#ff0000;stroke-width:1;stroke-linejoin:round;paint-order:markers fill stroke"
       id="square"
       width="58.322342"
       height="58.322342"
       x="197.28148"
       y="70.460732" />
    <path
       sodipodi:type="star"
       style="fill:#ff0000;stroke-width:1;stroke-linejoin:round;paint-order:markers fill stroke"
       id="star"
       inkscape:flatsided="false"
       sodipodi:sides="7"
       sodipodi:cx="246.21613"
       sodipodi:cy="196.8262"
       sodipodi:r1="44.441872"
       sodipodi:r2="23.465311"
       sodipodi:arg1="-1.6774691"
       sodipodi:arg2="-1.2286702"
       inkscape:rounded="-3.469447e-18"
       inkscape:randomized="0"
       d="m 241.48437,152.63694 12.60415,22.08392 23.72596,-9.14564 -9.40734,23.62341 21.94324,12.84749 -24.33491,7.374 3.63682,25.1662 -20.9378,-14.42819 -17.4082,18.53425 -1.7741,-25.36566 -25.34449,-2.05437 18.72554,-17.20226 -14.19587,-21.09601 25.12446,3.91478 z"
       inkscape:transform-center-x="2.9669026"
       inkscape:transform-center-y="-4.1349791" />
  </g>
</svg>

and looks like this: enter image description here

I want to do things with those elements using a JavaScript script, similarly as you would do in an HTML file. I created the following script:

console.log("I am alive!");

var circle = document.getElementById('circle');
circle.style.fill = "#0000ff"; // Make it blue.

console.log(circle);

and embedded it in the SVG file using Inkscape→File→Document properties→Scripting→External scripts→my_script.js. It prints I am alive! but then circle is null. Inspired by this link I also tried

console.log("I am alive!");

console.log(svgDocument.getElementById("circle"));

which prints Uncaught ReferenceError: svgDocument is not defined. Following this link I tried

console.log("I am alive!");

var svgObject = document.getElementById('svg-object').contentDocument;
var element = svgObject.getElementById('circle');

console.log(element);

and

console.log("I am alive!");

var svgObject = document.getElementById('svg5').contentDocument;
var element = svgObject.getElementById('circle');

console.log(element);

and

console.log("I am alive!");

var svgObject = document.getElementById('svg5');
var element = svgObject.getElementById('circle');

console.log(element);

but they all fail.

What is the way of writing this JavaScript such that when I open the SVG with a web browser this works?


Solution

  • Make sure that you wait for the DOM to load. I inserted the following script as an embedded script in Inkscape:

    document.addEventListener('DOMContentLoaded', e => {
      document.getElementById('path1').style.fill = 'blue';
    });
    

    And got the following result, saving the document as a plain SVG document:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <!-- Created with Inkscape (http://www.inkscape.org/) -->
    
    <svg
       width="200"
       height="200"
       viewBox="0 0 200 200"
       version="1.1"
       id="svg1"
       xmlns="http://www.w3.org/2000/svg"
       xmlns:svg="http://www.w3.org/2000/svg">
      <script
         id="script1">document.addEventListener('DOMContentLoaded', e =&gt; {
      document.getElementById('path1').style.fill = 'blue';
    });</script>
      <defs
         id="defs1" />
      <g
         id="layer1">
        <path
           style="fill:#ff0000;stroke-width:14.844;stroke-linecap:round"
           id="path1"
           d="M 90.234723,183.97224 A 82.22879,83.852211 0 0 1 11.095695,100.59093 82.22879,83.852211 0 0 1 89.427955,16.421145 82.22879,83.852211 0 0 1 175.22041,92.6532 82.22879,83.852211 0 0 1 104.17524,183.29802" />
      </g>
    </svg>

    For some reason Inkscape is not escaping the JavaScript code in a CDATA element, but only escaping characters like the > that becomes &gt;. In my book a proper script element containing JavaScript in a SVG document would look like this:

    <script>
      //<![CDATA[
        document.addEventListener('DOMContentLoaded', e => {
          document.getElementById('path1').style.fill = 'blue';
        });
      //]]>
    </script>