How to iterate through SVG nodes and add event to it (JS)?

Viewed 24

I have a simple SVG file that I need to load and iterate through shapes nodes to add an onclick event. The problem is that it is not finding the shape nodes in my SVG file after it loads. What am I doing wrong?

This is my SVG:

<?xml version="1.0" encoding="windows-1252"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 width="336.652px" height="379.156px" viewBox="0 0 336.652 379.156" enable-background="new 0 0 336.652 379.156"
 xml:space="preserve">

<shapes id="shapes">
<shape id="shape1">
    <path fill="#999999" d="M190.96,240.98c0,0.011-0.01,0.011-0.01,0.011c-3.16,2.14-8.869,3.069-12.561,2.069l-11.48-1.681
        c-0.03-0.01-0.09-0.021-0.12-0.03c-0.03-0.01-0.05-0.029-0.07-0.05c-0.02,0-0.03-0.01-0.04-0.03c-0.14-0.119-0.2-0.33-0.12-0.51
        l2.88-6.68l3.32-8.53l0.069-0.149l-0.061,0.149c4.68,2.11,13.771,6.261,18.9,8.63l-0.33,6.2
        C191.341,240.6,191.181,240.87,190.96,240.98z"/>

</shape>
<shape id="shape2">
    <path fill="#CCCCCC" d="M305.425,65.506c0.171,0.181,0.271,0.53,0.23,0.78c-0.302,3.109-1.83,8.561-3.201,11.37
        c11.01,2.86,30.551,2.75,41.541-0.239c0.34-0.091,0.778,0.069,1,0.351L305.425,65.506z"/>
</shape>
</shapes>
</svg>

And this is my code:

<html>
<body>
<object data="test.svg" id="svgholder" type="image/svg+xml"></object>
<script>
    var svgholder = document.getElementById("svgholder");
    svgholder.onload = function () {
        console.log("svg loaded");
        var shapes = svgholder.getElementsByTagName("shape");
        console.log(shapes.length)

        for (var i = 0; i < shapes.length; i++) {
            shapes[i].addEventListener("click", showshape, this.id, false);
        }
    }

    function showshape(id) {
        console.log(id);
    }
</script>
</body>
</html>

1 Answers

Your code is missing some points:

  • you've already implemented to load the object data/src
  • But: you also need to parse the loaded content via svgholder.contentDocument to access child elements
  • your svg markup contains invalid tags/elemnts: <shape> is not a standard compliant element – unless you are creating/registering something like a web component
  • the click/hover area will be defined by a geometry element like <path> or primitives like <circle>, <rect> etc. The closest thing to your nested <shape> element would be a group <g> element.

Try this code:

HTML

<object data="test.svg" id="svgholder" type="image/svg+xml"></object>

object data/svg

<svg  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
  viewBox="0 0 336.652 379.156" >
    <g id="shapesGroup0">
        <g id="shapesGroup0-0">
            <path class="shape" id="shape1" fill="#999999" d="M190.96,240.98c0,0.011-0.01,0.011-0.01,0.011c-3.16,2.14-8.869,3.069-12.561,2.069l-11.48-1.681
                c-0.03-0.01-0.09-0.021-0.12-0.03c-0.03-0.01-0.05-0.029-0.07-0.05c-0.02,0-0.03-0.01-0.04-0.03c-0.14-0.119-0.2-0.33-0.12-0.51
                l2.88-6.68l3.32-8.53l0.069-0.149l-0.061,0.149c4.68,2.11,13.771,6.261,18.9,8.63l-0.33,6.2
                C191.341,240.6,191.181,240.87,190.96,240.98z"/>
        </g>
        <g id="shapesGroup1-0">
            <path class="shape" id="shape2" fill="#CCCCCC" d="M305.425,65.506c0.171,0.181,0.271,0.53,0.23,0.78c-0.302,3.109-1.83,8.561-3.201,11.37 c11.01,2.86,30.551,2.75,41.541-0.239c0.34-0.091,0.778,0.069,1,0.351L305.425,65.506z"/>
        </g>
    </g>
</svg>

JS
let svgholder = document.getElementById("svgholder");

//load external object content
svgholder.onload = function () {
    //get object's svg DOM
    let doc = svgholder.contentDocument; 
    let shapes = doc.querySelectorAll(".shape");

    //set click event listeners for each element
    for (let i = 0; i < shapes.length; i++) {
        shapes[i].addEventListener("click", function(e){
          let current = e.currentTarget;
          let parentGroup = current.parentNode.closest('g');
          //if there is a parent group - get the id - otherwise return empty string
          let parentGroupID = parentGroup ? parentGroup.id : '';
          let grandParentGroup = parentGroup.parentNode.closest('g');
          let grandParentGroupID = grandParentGroup ? grandParentGroup.id : '';
          console.log(`clicked element: "#${current.id}" parent group: "#${parentGroupID}" grandparent group: "#${grandParentGroupID}"`)
        }
        );
    }
}

Final thoughts:

Actually I strongly recommend to to switch to a more modern approach than <object>
like inlined svg (the HTML overhead can actually improve your sites performance),
a smart use of externally referenced <use> elements or
native web components. (I guess some other users could elaborate on this ...)

Related