简体   繁体   中英

OnClick not being triggered when using empty SVG nested inside of other SVG

I have 2 svg elements, one empty inside of the other, and I need to get the inner svg element in my event target when I click on it but I can't make it work. The first problem I noticed is that when using Chrome the inner svg is displayed with 0 height and 0 width, something that doesn't happen in Firefox. The second problem is that after I create a onclick listener for my inner svg, the event never triggers.

I also tried:

  • Setting a onclick listener in my outer svg and get the event target. It always returns the outer svg and never the inner svg. Only works if I have a rect, circle, path, (...) inside.
  • Setting pointer-events to all in my inner svg and none in the outer one. Doesn't trigger the click event.

What I can't do:

  • Get the firstElementChild of my outer svg, I will have more nested svg's and I need to know exactly which one I clicked.

Example:

<div>
    <svg id="0" x="0" y="0" height="100%" width="100%" viewBox="0 0 1000 1000">
        <svg id="1" x="0" y="0" height="1000" width="1000" viewBox="0 0 1000 1000"></svg>
    </svg>
</div>

jsfiddle example here

It looks like SVG does not supports the click listeners. But have you tried to put another div around each SVG and add click listener to the div?

<div>
  <div id="svg1">
    <svg id="0" x="0" y="0" height="100%" width="100%" viewBox="0 0 1000 1000">
        <svg id="1" x="0" y="0" height="100%" width="100%" viewBox="0 0 1000 1000"></svg>
    </svg>
  </div>
</div>
document.getElementById("svg1").addEventListener("click", yourFunc)

I have the same problem.
Here are results of my investigation.
The click event on nested svg gets triggered when we click on any svg element inside the nested svg. I used two circles to test it.
The event gets not triggered if we click on the empty space withing bounding rectangle of the svg.
The behavior is the same in Chrome and Firefox.
I ended up setting event handler on outermost svg. To get mouse coordinates relative to inner svg I use utility function from d3.js, pointer(event: any, target?: any).

@Michael Mullany recommends not to nest svgs at all. I don't agree with that. I have complex graphical editor with several drawing areas and two types of svg nesting - with viewBox and without it. So, far it is working fine. Definitely better than to have hacks like invisible rectangles.

The only other minor issue I have it is that bounding rectangle of svg in Chrome debugger gets visualized with top-left corner not at its given position. Coordinates origin is correct though. So, the issue affects only debugging.

Speaking strictly, the issue of events not being triggered on empty space depends on definition of what should be considered the area of the svg. We might define the area as union of all content elements. And then, the behavior we observe is absolutely correct.

I've encountered the same problem, and the description from @alehro is absolutely right.

The problem turns out to be that the browser doesn't think the white space in the inner svg as part of it. The empty part of the inner svg is seen to be part of the outter svg . So the solution is pretty simply: add a transparent rectangle convers the whole inner svg , and it will be alright. Do something like this:

innersvg
    .append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", innerSvgWidth)
    .attr("height", innerSvgHeight)
    .attr("fill-opacity", 0)
    .attr("fill", "white");

Note: do not set fill to none , since a non-filled rect only has 4 lines but no the inner part.

That's actually the expected behaviour:

The outer/parent svg is an HTML DOM node - so it will be applied css styles like width, height or background colour.

The inner/nested svg is part of the SVG DOM.

An empty nested <svg> (as well aa <g> ) element will have a bounding box of 0 x 0 px.

Although, firefox shows width and height values while inspecting the nested svgs – it also returns 0x0px using

let bb = nestedSvg.getBBox();
console.log(bb)

Add transparent rectangles via javaScript helper

Appending a transparent background rectangle isn't a hack at all.

Set a fill="transparent" - this way, you get a solid/filled area for click/pointer events. (otherwise it's like knocking on a open window).

You can easily apply a helper method like so:

 let svgOuter = document.getElementById("svg0"); // add transparent rectangles let nestedSvgs = svgOuter.querySelectorAll("svg, g"); nestedSvgs.forEach((nestedSvg) => { let rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttribute("x", "0"); rect.setAttribute("y", "0"); rect.setAttribute("width", "100%"); rect.setAttribute("height", "100%"); rect.setAttribute("fill", "transparent"); nestedSvg.insertBefore(rect, nestedSvg.children[0]); // add click event nestedSvg.addEventListener("click", (e) => { let id = e.currentTarget.id; console.log("current id:", id); }); });
 *{ box-sizing:border-box } svg{ border:1px solid #ccc; }.svgInner{ background:red; stroke:red; stroke-width:1px; }
 <svg class="svgOuter" id="svg0" x="0" y="0" height="100%" width="100%" viewBox="0 0 1000 1000"> <svg class="svgInner" id="svg1" x="0" y="0" height="100%" width="33.333%" viewBox="0 0 333 1000"> </svg> <svg class="svgInner" id="svg2" x="33.333%" y="0" height="100%" width="33.333%" viewBox="0 0 333 1000"> </svg> <g transform="translate(666.67 0)" id="testGroup"></g> </svg>

Better use e.currentTarget since e.target will return the <rect> element.

Alternative element intersection check: Document.elementFromPoint() or Document.elementsFromPoint()

You still need a filled background area, but you can also get document.elementsFromPoint() multiple overlapping elements - might be handy in some scenarios.

 let svgOuter = document.getElementById("svg0"); /** * alternative: Document.elementFromPoint() * https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint */ svgOuter.addEventListener("click", (e) => { let el = document.elementFromPoint(e.clientX, e.clientY); let els = document.elementsFromPoint(e.clientX, e.clientY); let ids = els.map(el=>{return el.id? el.id: el.nodeName}); let parentSVG = el.closest("svg"); console.log('current element', parentSVG.id); console.log('all elements', ids); }); // add transparent rectangles let nestedSvgs = svgOuter.querySelectorAll("svg, g"); nestedSvgs.forEach((nestedSvg, i) => { let rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.id= 'rect'+i; rect.setAttribute("x", "0"); rect.setAttribute("y", "0"); rect.setAttribute("width", "100%"); rect.setAttribute("height", "100%"); rect.setAttribute("fill", "transparent"); nestedSvg.insertBefore(rect, nestedSvg.children[0]); // add click event nestedSvg.addEventListener("click", (e) => { let id = e.currentTarget.id; console.log("current id:", id); }); });
 *{ box-sizing:border-box } svg{ border:1px solid #ccc; }.svgOuter{ background:#ccc; }.svgInner{ background:red; stroke:red; stroke-width:1px; }
 <svg class="svgOuter" id="svg0" x="0" y="0" height="100%" width="100%" viewBox="0 0 1000 1000"> <svg class="svgInner" id="svg1" x="0" y="0" height="100%" width="33.333%" viewBox="0 0 333 1000"> </svg> <svg class="svgInner" id="svg2" x="33.333%" y="0" height="100%" width="33.333%" viewBox="0 0 333 1000"> </svg> <g transform="translate(666.67 0)" id="testGroup"></g> </svg>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM