0

What I am trying to achieve is an svg that changes color when I hover over any part of the viewbox(not just the svg itself), AND when clicking on any part of the viewbox can call a Javascript or Jquery function. I've gotten aspects of each of these things to work, but integrating them has proven difficult, and solutions seem to contradict each other.

If I add the svg using an <img> tag, I can easily add a click event to it that calls a Javascript function, that also covers the whole viewbox, but I no longer have access to the hover effects of the svg.

If I import the svg using an <object> tag, or inline using <svg>, I can easily add hover effects to the svg using CSS. These hover effects are local to the fill or stroke of the svg, but I can add a <rect> with fill-opacity: 0 in a group with the other elements of my svg, and I get a hover effect that works on the whole viewbox. The problem is, that I can't add the click events to the <object> or <svg> tag. There are solutions that can address this problem, such as using the svg in an <img> tag like I mentioned earlier, or adding pointer-events: 0 to the object tag(Which are the given solutions for a similar Stack Overflow question here and another similar question here), but both of these solutions get rid of the hover effect interactivity of the svg.

I want to know how I can achieve a happy medium that allows for both.

Sam Sabin
  • 553
  • 1
  • 6
  • 19
  • 1
    You can just use `pointer-events: all` on an inline `` element. That should be enough, no covering `` is needed. – ccprog Jun 13 '23 at 00:54
  • @ccprog This worked perfectly for me! If you want to post your comment as an answer then I will accept it as the correct answer. Just curious, do you know if something like this is also possible using `````` or some other kind of embedding. Inline svg works just fine for my current application, but there are many places where it would be desirable to avoid it. – Sam Sabin Jun 13 '23 at 01:15
  • _"do you know if something like this is also possible using or some other kind of embedding"_ - You won't be able to "reach into" an SVG to modify its formatting via your page stylesheet, if you embed it any other way than by putting `...` directly into the HTML. – CBroe Jun 13 '23 at 07:00
  • For loading external SVG and apply CSS/JS take inspiration from the Native JavaScript [```` Web Component](https://dev.to/dannyengelman/load-file-web-component-add-external-content-to-the-dom-1nd) I blogged about. – Danny '365CSI' Engelman Jun 13 '23 at 07:57
  • @Danny'365CSI'Engelman Interesting, I'll take a look. – Sam Sabin Jun 13 '23 at 18:27

1 Answers1

1

You can use either <object> or <iframe> if you really want to, but it is a bit complicated. Further down I have an example, but first the most sane approach -- making the SVG inline.

So, here are two examples. One (#svg01) without pointer-events: all; and the other (#svg02) without. As I see it (in Firefox) there is no difference. If you click on the white space on either of them, there will be a click event.

document.getElementById('svg01').addEventListener('click', e => {
  console.log('click');
});

document.getElementById('svg02').addEventListener('click', e => {
  console.log('click');
});
#svg01 {
}

#svg02 {
  pointer-events: all;
}

svg {
  cursor: pointer;
}

svg:hover circle {
  fill: green;
}
<svg id="svg01" height="100" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="40" cy="60" r="40" fill="orange" />
  <circle cx="100" cy="20" r="20" fill="orange" />
  <circle cx="150" cy="70" r="30" fill="orange" />
</svg>

<svg id="svg02" height="100" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="40" cy="60" r="40" fill="orange" />
  <circle cx="100" cy="20" r="20" fill="orange" />
  <circle cx="150" cy="70" r="30" fill="orange" />
</svg>

<object> and <iframe> example

If you want to complicate things, here is an example on how to listen for a click event inside of a SVG document, and based on that, call a function on the parent document. This code cannot run on ST, so you need to set up a web server and further more the HTML and SVG document must have same origin.

HTML document

The SVG is either embeded in the HTML document using <object> or <iframe>. And then we need something on the window that we can call. Here, I create a simple "API".

<!DOCTYPE html>
<html>
  <head>
    <script>
      window.API = (function(){
        let doSomething = () => {
          return "hello";
        };
        return {
          doSomething
        }
      })();
    </script>
  </head>
  <body>
    <iframe width="400" height="200" src="svg.svg"></iframe>
    <object width="400" height="200" data="svg.svg" type="image/svg+xml"></object>
  </body>
</html>

SVG document

In the SVG document we first need to find the API in the parent window. After that we can call the functions on the API. In this case we listen for click events on the entire document.

<?xml version="1.0" encoding="utf-8"?>
<svg id="svg01" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg" pointer-events="all">
  <script>//<![CDATA[
    var findAPITries = 0;
    var API;
    function findAPI(win){
      while ( (win.API == null) && (win.parent != null) && (win.parent != win) ){
        findAPITries++;
        if (findAPITries > 7){
         return null;
        }
        win = win.parent;
      }
      return win.API;
    }
    document.addEventListener('DOMContentLoaded', e => {
      API = findAPI(window);
      e.target.addEventListener('click', e => {
        console.log(API.doSomething());
      });
    });
    //]]>
  </script>
  <style>
    svg:hover circle {
      fill: green;
    }
  </style>
  <circle cx="40" cy="60" r="40" fill="orange" />
  <circle cx="100" cy="20" r="20" fill="orange" />
  <circle cx="150" cy="70" r="30" fill="orange" />
</svg>

This pattern of calling a "API" is similar to the pattern used in SCORM API Discovery (don't ask :-)).

chrwahl
  • 8,675
  • 2
  • 20
  • 30
  • Not missing anything, this is essentially the solution I ended up going with(as per ccprog's answer). Great answer, and well written. I think I was just too hyper-focused on getting it to work with an `````` tag so that I wouldn't have to include svg's inline, but it turned out to be too much of a hassle. I'd love to see how it's done with `````` but for my purposes this works great. – Sam Sabin Jun 17 '23 at 23:07
  • 1
    I updated my answer with an example of how to listen for click events in either `` or ` – chrwahl Jun 18 '23 at 18:32
  • Thanks for adding the other examples. Super helpful! – Sam Sabin Jun 19 '23 at 19:40