How can I loop through SVG elements and attach an event listener to each one in JavaScript?

I am struggling with loading a simple SVG file and iterating through the shape nodes to add an onclick event. Unfortunately, after the SVG file is loaded, it cannot find the shape nodes. What could be the issue here?

Here is the SVG code snippet:

<?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>

Now, let's take a look at the implementation 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>

Answer №1

You're missing some key points in your code:

  • You've successfully implemented loading the object data/src
  • However, you also need to parse the loaded content through svgholder.contentDocument to access child elements
  • Your SVG markup contains invalid tags/elements: <shape> is not a standard compliant element, unless you are creating/registering a web component
  • The click/hover area should be defined by a geometry element like <path> or primitives such as <circle>, <rect>, etc. The closest alternative to your nested <shape> element would be a group <g> element.

Try using 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:

I highly recommend switching to a more modern approach than <object>, such as inlined SVG (which can actually improve site performance), smart use of externally referenced <use> elements, or native web components. Other users can provide more insight on this topic.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I'm looking to extract various data from a SQLite table using the URL with ExpressJS. What's the best way to achieve

I need to access data from a SQLite database table that contains information on accounts, movies, and reviews. Specifically, the structure of the reviews-table is as follows: CREATE TABLE IF NOT EXISTS reviews ( id INTEGER PRIMARY KEY, authorId INT ...

What causes a never-ending loading cycle on a website when a 404 error occurs?

My Express.js/Node.js website is hosted on Heroku. I am facing an issue where the server does not properly send a 404 error when a file cannot be found. Instead, the page keeps loading endlessly. How can I make the server stop loading when it encounters a ...

The HTML div captured with html2canvas is incomplete

I am currently developing a meme editor website utilizing the html2canvas library available at Below is the HTML code for the div I aim to capture: <div class="container"> <div id="theUserMeme"> <div class=& ...

Steps for adding a React Class Component into a Modal that is not within the React Tree

Our project is built using PHP MVC Framework and we initially used jQuery as our main JavaScript framework for handling UI activities. However, we have now transitioned to using React.js. My query is about how to inject or append a React Functional/Class-b ...

Ways to identify and differentiate user clicks on various buttons

I have generated 3 different plan options from an array of objects retrieved from the backend. Depending on whether the plan is cheaper, the user's subscription, the corresponding button will display "downgrade", more expensive, the button will show ...

Troubleshooting issue with Bootstrap collapse functionality failing with dynamic IDs

Having trouble creating dynamic ids for bootstrap collapsing functionality. I want each topic in an ng-repeat to collapse and display its respective question list when clicked. The issue is that when I click on a second topic, the question list data from ...

Updating a React application that was originally built using Node v16 to the latest version of Node, v18,

I have a React project that was originally built on node v16 and now I need to update it to node v18. How can I do this quickly without changing dependencies or causing other issues? When I tried installing the dependencies in node 18, everything seemed f ...

SmartCollection in Meteor generating unpredictable outcomes

When executing News.insert({name: 'Test'}) in the browser JS console, it caused {{count}} to increase from 0 to 1. Checking in mongo console using mrt mongo, db.news.find().count() also returns 1. However, after adding a record through the mongo ...

Order objects in a JavaScript array

Recently, I came across a list of student data fetched from an API: const Studentdata = { "StudentName1": { "active": true, "gender": "male" }, "StudentName2": { "active": false, "gender": "male" }, "S ...

Retrieve JSON details for various games including their properties

I am currently working on a project that involves accessing Casino Games and their properties from a JSON object to display them on a website. Here is my progress so far: var x = $.getJSON("http://api.bosurl.net/V1/Progressive.asmx/GetProgressiveGames?for ...

Ways to take an item out of your shopping cart

Do you have any ideas on how to handle cart redirection in JavaScript? My specific request involves a cart with a function that removes products. What approach should I take to redirect to the main page if the cart becomes empty? Here is the delete produc ...

Encountering a CSS issue during the edit process with PHP and JavaScript

I'm encountering an issue when clicking on the edit button, as my data fetched from the DB should be displayed inside a text field. However, I'm facing a CSS-related error: Uncaught TypeError: Cannot read property 'style' of null Belo ...

Including file format extension in the absence of one

I have created a script that loops through every image source on the page, extracting the extension. If the extension is not recognized or does not exist, I automatically add .jpg to the end. Strangely, the if statement always evaluates to true no matter ...

Customize the background color of highlighted text using HTML and jQuery

Recently, I modified an existing code to divide plain text into four classes by selecting a portion of the text and coloring it. Afterwards, I extracted the text of each class and stored it in my database. While this code works well, I am looking for a way ...

unable to retrieve / interpret data from herdsmen using fetch

When sending a request to a node server, the server responds with headers and a blank body. Despite being able to view the headers in the network activity panel within dev-tools, I faced difficulties reading them using the following code: let uploaded ...

Executing an external JavaScript function from within an internal JavaScript code block

Currently, I am dealing with 2 JavaScript blocks. One is contained within my HTML and handles touch functionality, while the other is an external file serving as a content slider. My goal is to utilize touch events to control the slider - allowing users to ...

Closing a dropdown menu when opening another one

As a beginner in Vue.js, I am currently working on a CRUD project for my coursework. One issue I am facing is with the behavior of the dropdown menu. Can anyone provide assistance? https://i.sstatic.net/1MozuN3L.png I have specific requirements: The dr ...

Navigating through Switch cases and If Statements with multiple arguments can be complex for beginners in the world of JavaScript

Hi there, I have a specific case that I'm struggling to find a solution for. I am using Angular with a Firebase back-end to retrieve true or false values from a variable called ref. The variable contains 4 properties with either true or false values - ...

What is causing all Vuejs requests to fail in production with the error message "javascript enabled"?

My vuejs application interacts with a REST API in Node.js (Express, MongoDB Atlas). Everything runs smoothly when I run the Vue app on localhost while the Node.js app is on the server. However, when I deploy my dist folder to the server, although the app ...

There seems to be an issue with Node.js/Express: the page at /

Recently, I've been working on some code (specifically in app.js on the server). console.log("Server started. If you're reading this then your computer is still alive."); //Just a test command to ensure everything is functioning correctly. var ...