Fede
Fede

Reputation: 91

document.addEventListener not working on loaded svg

I'm trying to build a project in which the user can click on an image and then an interactive .svg is loaded (inside an object tag). For that, I need to track the mouse movement on the entire screen (not only the .svg) after the loading has happened.

However, neither document.addEventListener nor window.addEventListener seem to work on the .svg. They just work outside its boundaries. Is there a way to fix this without having to combine document.addEventListener and the specific interactiveSVG.addEventListener?

Here is my code (edited):

let firstImage = document.querySelector('#my-first-img');
let objectSVG = document.querySelector('#svg-obj');
let interactiveSVG;

firstImage.addEventListener('click', e => {
        objectSVG.data = `mySVG.svg`;
        objectSVG.addEventListener('load', e => {
            interactiveSVG = objectSVG.getSVGDocument();
            document.addEventListener('mousemove', e => {
                console.log(e.clientX);
            });
        });
});

Upvotes: 2

Views: 1295

Answers (1)

zer00ne
zer00ne

Reputation: 44088

AFAIK, a SVG within an <object> limits you somewhat. There are exstensive features for the CSS property pointer-events that can apply to SVG elements, which unfortanately an <object> cannot provide. But if you assign pointer-events: none to the SVG it'll be ignored so pointer events will fire directly over the SVG but the event.target will be the closest ancestor DOM element of the SVG.

For SVG in <object>, you need an actual <style> tag within the <object> and add pointer-events: none:

<object class="map" data="path/to/image.svg" type="image/svg+xml">
  <style>
    .map {
      pointer-events: none;
    }
  </style>
</object>

The example below demonstrates normal pointer behavior when hovering over or clicking on the SVG. Keep in mind with pointer-events: none, what is actually happening is all interactions are in the DOM.

Usage

  • Hover over the entire page and observe the XY coords displayed in the lefthand corner. The SVG is outlined in a red dashed line, note that mousemove events fire when the cursor is over the SVG.

  • Click outside and inside of the SVG. There will be a flag icon set at every click (hover over a flag and a hint will popup with it's XY coords). This is possible because each flag is position: absolute and the body is position: relative.

Note: the coords of the flgs and the coods displayed in the corner are not exact because the flags are on a offset of .getBoundingClientRect() measurements and clientX/Y.

<!DOCTYPE html>
<html lang="en">

<head>
  <title></title>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <style>
    html {
      font: 1.5ch/1 Consolas;
    }
    
    html,
    body {
      width: 100%;
      height: 100%;
    }
    
    body {
      position: relative;
    }
    
    fieldset {
      width: max-content;
      padding: 0;
    }
    
    mark {
      position: absolute;
      background: transparent;
    }
    
    mark:after {
      content: '\1f6a9';
      font-size: 1.25rem;
    }
  </style>
</head>

<body>
  <fieldset>
    <legend><output class="view"></output></legend>
    <object class="map" data="https://upload.wikimedia.org/wikipedia/commons/4/41/Simple_world_map.svg" type="image/svg+xml">
          <style>
            .map {
              transform: scale(0.75);
              outline: 1px dashed red;
              pointer-events: none;
            }
          </style>
        </object>
  </fieldset>
  <script>
    document.onmousemove = trackPoint;
    document.onclick = markPoint;

    function trackPoint(e) {
      const view = document.querySelector('.view');
      view.value = `X: ${e.clientX.toString().padStart(3, '0')} : Y: ${e.clientY.toString().padStart(3, '0')}`;
    }

    function markPoint(e) {
      const mark = document.createElement('mark');
      document.body.appendChild(mark);
      getCoords.call(mark, e);
    }

    function getCoords(e) {
      const clk = e.target;
      const rect = clk.getBoundingClientRect();
      const oX = Math.floor(e.clientX - rect.left);
      const oY = Math.floor(e.clientY - rect.top);
      this.style.left = oX + 'px';
      this.style.top = oY + 'px';
      this.title = `[${oX}, ${oY}]`;
    }
  </script>
</body>

</html>

Upvotes: 4

Related Questions