SVG element loses event handlers if moved around the DOM

前端 未结 3 1108
陌清茗
陌清茗 2021-01-01 20:37

I use this D3 snippet to move SVG g elements to top of the rest element as SVG render order depends on the order of elements in DOM, and there is no z index:

3条回答
  •  孤城傲影
    2021-01-01 20:59

    One solution could be to use event delegation. This fairly simple paradigm is commonplace in jQuery (which gave me the idea to try it here.)

    By extending the d3.selection prototype with a delegated event listener we can listen for events on a parent element but only apply the handler if the event's target is also our desired target.

    So instead of:

    d3.select('#targetElement').on('mouseout',function(){})
    

    You would use:

    d3.select('#targetElementParent').delegate('mouseout','#targetElement',function(){})
    

    Now it doesn't matter if the events are lost when you move elements or even if you add/edit/delete elements after creating the listeners.

    Here's the demo. Tested on Chrome 37, IE 11 and Firefox 31. I welcome constructive feedback but please note that I am not at all familiar with d3.js so could easily have missed something fundamental ;)

    //prototype. delegated events
    d3.selection.prototype.delegate = function(event, targetid, handler) {
        return this.on(event, function() {
            var eventTarget = d3.event.target.parentNode,
                target = d3.select(targetid)[0][0];
            if (eventTarget === target) {//only perform event handler if the eventTarget and intendedTarget match
                handler.call(eventTarget, eventTarget.__data__);
            }
        });
    };    
    //add event listeners insead of .on() 
    d3.select('#svg').delegate('mouseover','#g2',function(){
        console.log('mouseover #g2');
    }).delegate('mouseout','#g2',function(){
        console.log('mouseout #g2');
    })    
    //initial move to front to test that the event still works
    d3.select('#g2').moveToFront();
    

    http://jsfiddle.net/f8bfw4y8/

    Updated and Improved...

    Following Makyen's useful feedback I have made a few improvements to allow the delegated listener to be applied to ALL matched children. EG "listen for mouseover on each g within svg"

    Here's the fiddle. Snippet below.

    //prototype. move to front
    d3.selection.prototype.moveToFront = function () {
      return this.each(function () {
        this.parentNode.appendChild(this);
      });
    };
    
    //prototype. delegated events
    d3.selection.prototype.delegate = function(event, targetselector, handler) {
        var self = this;
        return this.on(event, function() {
            var eventTarget = d3.event.target,
                target = self.selectAll(targetselector);
            target.each(function(){ 
                //only perform event handler if the eventTarget and intendedTarget match
                if (eventTarget === this) {
                    handler.call(eventTarget, eventTarget.__data__);
                } else if (eventTarget.parentNode === this) {
                    handler.call(eventTarget.parentNode, eventTarget.parentNode.__data__);
                }
            });
        });
    };
    
    
    var testmessage = document.getElementById("testmessage");
    //add event listeners insead of .on() 
    //EG: onmouseover/out of ANY  within #svg:
    d3.select('#svg').delegate('mouseover','g',function(){
        console.log('mouseover',this);
        testmessage.innerHTML = "mouseover #"+this.id;
    }).delegate('mouseout','g',function(){
        console.log('mouseout',this);
        testmessage.innerHTML = "mouseout #"+this.id;
    });
    
    /* Note: Adding another .delegate listener REPLACES any existing listeners of this event on this node. Uncomment this to see. 
    //EG2 onmouseover of just the #g3
    d3.select('#svg').delegate('mouseover','#g3',function(){
        console.log('mouseover of just #g3',this);
        testmessage.innerHTML = "mouseover #"+this.id;
    });
    //to resolve this just delegate the listening to another parent node eg:
    //d3.select('body').delegate('mouseover','#g3',function(){...
    */
    
    
    //initial move to front for testing. OP states that the listener is lost after the element is moved in the DOM.
    d3.select('#g2').moveToFront();
    svg {height:300px; width:300px;}
    rect {fill: pink;}
    #g2 rect {fill: green;}
    #testmessage {position:absolute; top:50px; right:50px;}
    
    
        
        
        
    
    

    As with all delegated listeners if you move the target element outside of the parent you have delegated the listening to then naturally the events for that child are lost. However, there is nothing to stop you delegating the event listening to the body tag as you'll never move a child outside of that. EG:

    d3.select('body').delegate('mouseover','g',function(){...
    

提交回复
热议问题