如何组织我的SVG以便包含交互式悬停效果,并保留对可点击事件的DOM访问?

huangapple go评论74阅读模式
英文:

How do I organize my svg so that I can include interactive hover effects, as well as preserve the DOM access to clickable events?

问题

我试图实现的目标是当我悬停在视框的任何部分(不仅仅是SVG本身)上时,SVG会改变颜色,并且当单击视框的任何部分时可以调用JavaScript或jQuery函数。我已经让这些事情的某些方面起作用了,但将它们集成起来却很困难,而且解决方案似乎互相矛盾。

如果我使用<img>标签添加SVG,我可以轻松地为其添加点击事件,调用JavaScript函数,也可以覆盖整个视框,但我不再能够访问SVG的悬停效果。

如果我使用<object>标签导入SVG,或者使用<svg>内联,我可以轻松地使用CSS为SVG添加悬停效果。这些悬停效果局限于SVG的填充或描边,但我可以在SVG的其他元素组中添加一个<rect>,并设置fill-opacity: 0,从而实现在整个视框上工作的悬停效果。问题是,我无法为<object><svg>标签添加点击事件。有一些解决方案可以解决这个问题,比如像我之前提到的在<img>标签中使用SVG,或者为对象标签添加pointer-events: 0(这是一个类似的Stack Overflow问题这里给出的解决方案,还有另一个类似的问题这里),但这两个解决方案都会取消SVG的悬停效果交互性。

我想知道如何实现既允许悬停效果又能处理点击事件的折中方法。

英文:

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.

答案1

得分: 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.

所以,你可以使用<object><iframe>,但这有点复杂。稍后我会给出一个例子,但首先让我们看看最合理的方法——将SVG内联化。

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.

所以,这里有两个例子。一个(#svg01)没有pointer-events: all;,另一个(#svg02)有。在我看来(在Firefox中),它们之间没有区别。如果你点击它们中的任何一个的白色空白处,都会触发点击事件。

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

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

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

<!-- language: lang-css -->

#svg01 {
}

#svg02 {
pointer-events: all;
}

svg {
cursor: pointer;
}

svg:hover circle {
fill: green;
}

<!-- language: lang-html -->

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

<!-- end snippet -->

&lt;object&gt; and &lt;iframe&gt; example

如果你想要复杂化事情,下面是一个示例,说明如何监听SVG文档内的点击事件,并基于此调用父文档上的函数。此代码无法在ST上运行,因此您需要设置一个Web服务器,而且HTML和SVG文档必须具有相同的来源。

HTML文档

SVG要么嵌入在HTML文档中使用&lt;object&gt;&lt;iframe&gt;。然后我们需要在窗口上创建一个可以调用的东西。在这里,我创建了一个简单的“API”。

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

SVG文档

在SVG文档中,我们首先需要在父窗口中找到API。之后我们可以调用API上的函数。在这种情况下,我们监听整个文档上的点击事件。

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

<details>
<summary>英文:</summary>

You _can_ use either `&lt;object&gt;` or `&lt;iframe&gt;` 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.

&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    document.getElementById(&#39;svg01&#39;).addEventListener(&#39;click&#39;, e =&gt; {
      console.log(&#39;click&#39;);
    });

    document.getElementById(&#39;svg02&#39;).addEventListener(&#39;click&#39;, e =&gt; {
      console.log(&#39;click&#39;);
    });

&lt;!-- language: lang-css --&gt;

    #svg01 {
    }

    #svg02 {
      pointer-events: all;
    }

    svg {
      cursor: pointer;
    }

    svg:hover circle {
      fill: green;
    }

&lt;!-- language: lang-html --&gt;

    &lt;svg id=&quot;svg01&quot; height=&quot;100&quot; viewBox=&quot;0 0 200 100&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
      &lt;circle cx=&quot;40&quot; cy=&quot;60&quot; r=&quot;40&quot; fill=&quot;orange&quot; /&gt;
      &lt;circle cx=&quot;100&quot; cy=&quot;20&quot; r=&quot;20&quot; fill=&quot;orange&quot; /&gt;
      &lt;circle cx=&quot;150&quot; cy=&quot;70&quot; r=&quot;30&quot; fill=&quot;orange&quot; /&gt;
    &lt;/svg&gt;

    &lt;svg id=&quot;svg02&quot; height=&quot;100&quot; viewBox=&quot;0 0 200 100&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
      &lt;circle cx=&quot;40&quot; cy=&quot;60&quot; r=&quot;40&quot; fill=&quot;orange&quot; /&gt;
      &lt;circle cx=&quot;100&quot; cy=&quot;20&quot; r=&quot;20&quot; fill=&quot;orange&quot; /&gt;
      &lt;circle cx=&quot;150&quot; cy=&quot;70&quot; r=&quot;30&quot; fill=&quot;orange&quot; /&gt;
    &lt;/svg&gt;

&lt;!-- end snippet --&gt;

## `&lt;object&gt;` and `&lt;iframe&gt;` 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 `&lt;object&gt;` or `&lt;iframe&gt;`. And then we need something on the window that we can call. Here, I create a simple &quot;API&quot;.

<!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 &quot;API&quot; is similar to the pattern used in [SCORM API Discovery](https://scorm.com/scorm-explained/technical-scorm/run-time/api-discovery-algorithms/) (don&#39;t ask :-)).
</details>

huangapple
  • 本文由 发表于 2023年6月13日 08:45:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76461077.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定