Search code examples
javascriptsvglayerz-order

How can I map click events to elements in multiple layers?


I have multiple SVG elements that are in separate groups. They overlap each other. Example:

<svg id="board" width="100%" height="80%">
   <g id="terrain" class="layer">
     <path d="M-32,-32L32,-32 32,32 -32,32Z" transform="translate(0, 0)" class="mote terrain hill"></path>
   </g>
   <g id="guy" class="layer">
     <path d="M-21...Z" transform="translate(192, 448)" class="mote guy"></path>
   </g>
</svg>

When an x, y position that matches both is clicked, I want to know all that both were clicked. If I bind each to the 'click' event, only the event handlers for one on top gets called. Which is reasonable, although not what I want here.

I'm thinking of creating a topmost layer and having that catch all clicks, then figure out which elements in the other layers should be notified. That's a lot of tracking that I'd like to avoid, if possible. Are there simpler approaches to this?


Solution

  • From The SVG spec

    "By default, pointer-events must not be dispatched on the clipped (non-visible) regions of a shape. For example, a circle with a radius of 10 which is clipped to a circle with a radius of 5 will not receive 'click' events outside the smaller radius. Later versions of SVG may define new properties to enable fine-grained control over the interactions between hit testing and clipping."

    However, there is a way of getting a list of svg shapes that intersect at a particular point. The "getIntersectionList" function returns a list of items.

    I've created one of those jsfiddle things jsfiddle.net/uKVVg/1/ Click on the intersection of the circles to get a list of ID's. Manually send events to that list.

    Javascript follows:

    function s$(a) {
        return document.getElementById(a);
    }
    
    var list 
    
    function hoverElement(evt) {
        var root = s$("canvas");
    
        var disp = s$("pointer");
        disp.setAttribute("x",evt.clientX);
        disp.setAttribute("y",evt.clientY);
    
        rpos = root.createSVGRect();
        rpos.x = evt.clientX;
        rpos.y = evt.clientY;
        rpos.width = rpos.height = 1;
        list = root.getIntersectionList(rpos, null);
        s = "clicked: "
        $.each(list,function(item,val){
            if (val.id != "pointer") {
                s = s + (val.id) + " ";
            }
        })
        alert(s);
    }
    
    var root = s$("canvas");
    root.addEventListener("click", hoverElement, false);
    

    There's some javascript that could probably be tidied up, but hopefully it answers your question.