Eventlistener not working when mouse entering ID within svg file

69 Views Asked by At

I have created a simple code to mess around and learn with SVG files and eventlisteners.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SVG Mouse Pointer Change</title>
<style>
    #svg5 {
        width: 50%; /* Adjust the width as needed */
        height: auto; /* Maintain aspect ratio */
    }
</style>
<script>
    window.onload = function() {
        var svgObject = document.getElementById('svg5');
        svgObject.addEventListener('load', function() {
            var svgDocument = svgObject.contentDocument;
            var rect = svgDocument.getElementById('rect237');

          // Function to change mouse pointer to crosshair
            function changeCursorToCrosshair() {
                console.log("Mouse entered rect237");
                document.body.style.cursor = 'crosshair';
            }

            // Function to change mouse pointer to default
            function changeCursorToDefault() {
                console.log("Mouse left rect237");
                document.body.style.cursor = 'default';
            }

            // Add event listener for mouse enter
            rect.addEventListener('mouseenter', function() {
                console.log("Mouse entered rect237");
                changeCursorToCrosshair();
            });

            // Add event listener for mouse leave
            rect.addEventListener('mouseleave', function() {
                console.log("Mouse left rect237");
                changeCursorToDefault();
            });

        });
    };
</script>
</head>
<body>

<object id="svg5" type="image/svg+xml" data="./img/simple_pitch.svg"></object>

</body>
</html>

However when testing the code nothing occurs. I have added some console logs but nothing is shown. I hope someone can let me know what I'm missing.

Thanks

3

There are 3 best solutions below

0
Kaiido On

Two things: Since the <object>'s data is set inline in the markup, the window.load event will wait for the <object>'s load event before it fires. So when you are in the window.load event handler, the <object>'s one has already fired and won't again until its data attribute changes.
This means that the svgObject's load event won't fire and your callback won't be called.
So remove the line svgObject.addEventListener('load', ... and call directly your code in the window.onload event handler.

Then, document.body.style.cursor = 'crosshair'; will set the cursor of the outer HTML document's <body> tag, but the element you are hovering is the inner SVG document's <rect> element. Both documents are isolated and the <object>'s inner document will capture the pointer events, including the setting of the cursor.
If you want to change the cursor, you can set it to svgDocument.documentElement.style.cursor = ....
(Obviously you could also have achieved the same with a simple rect { cursor: crosshair } CSS rule, but I get it's just a test to use events.)

2
chrwahl On

It is not clear from the question if you are running the HTML and SVG document from a web server. To make this work you need to run a web server. You cannot get access to the contentDocument if you just open the HTML file from the file system.

And in regards to the comments about the order of events. You must wait till the DOM is loaded before you can add an event listener to the object element. Using window.onload will wait till the document and all the resources has been loaded (so, after the resource in the object element is loaded), whereas document.addEventListener('DOMContentLoaded', e => {}) will fire when the DOM is ready.

If the SVG ./img/simple_pitch.svg has been cached by the browser it may not fire the load event. Therefor you can also test for the readystate of the contentDocument. If the readystate is complete, you know that the SVG has already been loaded.

Here is an example, where I changed the way that the crosshair style is set. The style of the cursor is not "tied to" the events of the mouse entering and leaving an element. If an element has a cursor style, then the cursor will change when the element is hovered. And then you can have the enter and leave events do something else (like, typically change the DOM).

<!DOCTYPE html>
<html>

<head>
  <script>
    document.addEventListener('DOMContentLoaded', e => {
      let svgObject = document.querySelector('object');

      if (svgObject.contentDocument.readystate == 'complete') {
        loadSVGHandler(svgObject);
      } else {
        svgObject.addEventListener('load', e => loadSVGHandler(e.target));
      }
    });

    function loadSVGHandler(svgObject) {
      let svgDocument = svgObject.contentDocument;

      let rect = svgDocument.getElementById('rect237');
      rect.style.cursor = 'crosshair';
      rect.addEventListener('mouseenter', mouseEnterHandler);
      rect.addEventListener('mouseleave', mouseLeaveHandler);
    }

    function mouseEnterHandler(e) {
      console.log("Mouse entered " + e.target.id);
    }

    function mouseLeaveHandler(e) {
      console.log("Mouse left " + e.target.id);
    }
  </script>
</head>

<body>
  <object width="400" height="200" data="./img/simple_pitch.svg" type="image/svg+xml"></object>
</body>

</html>
2
Danny '365CSI' Engelman On

Another alternactive, when you build DOM with a native Web Component you have full control on when an Element enters the DOM (the default connectedCallback fires)

You can create SVG upfront in shadowDOM (just those pesky SVG NameSpaces to be aware of)

You can then keep interaction closely tied to your Element without needing addEventListener

Creating one SVG element. with attributes and properties

Eventlisteners are set as properties

        createElement("circle", {
          cx: ~~((Math.random() + 1) * 100) % 100 + "%",
          cy: ~~((Math.random() + 1) * 100) % 100 + "%",
          r: "2%",
          fill: "red",
          stroke: "blue"
        }, {
          onmouseenter: (evt) => this.mouseenter(evt),
          onmouseleave: (evt) => this.mouseleave(evt),
        }) // createElement
Complete Web Component

const NS = "http://www.w3.org/2000/svg";
const createElement = (tag, attrs = {}, props = {}) => {
  let el = document.createElementNS(NS, tag);
  Object.entries(attrs).map(([key, value]) => el.setAttributeNS(null, key, value));
  return Object.assign(el, props);
}
customElements.define("svg-object", class extends HTMLElement {
  constructor() {
    super()
      .attachShadow({ mode: "open"})
      .append(
        this.svg = createElement("svg", {
          viewBox: "0 0 400 400",
          style: "background:pink;height:160px"
        }, {
          onclick: (evt) => this.addCircle()
        }) //createElement
      ) //append
    this.addCircle();
    this.addCircle();
  } //costructor
  mouseenter(evt) {
    this.style.cursor = "crosshair";
    this.addCircle();
  }
  mouseleave(evt) {
    this.style.cursor = "default";
  }
  addCircle() {
    this.svg.append(
      createElement("circle", {
        cx: ~~((Math.random() + 1) * 50) % 100 + "%",
        cy: ~~((Math.random() + 1) * 50) % 100 *.5 + "%",
        r: "4%",
        fill: "red",
        stroke: "blue"
      }, {
        onmouseenter: (evt) => this.mouseenter(evt),
        onmouseleave: (evt) => this.mouseleave(evt),
      }) // createElement
    ) //append
  }
});
<svg-object></svg-object>

JSFiddle: https://jsfiddle.net/WebComponents/53cmegLd/

For putting a circle at the exact click location see:
Can not translate screen x,y coordinates to svg coordinates
takes some extra line of CTM code