score:2

Accepted answer

This is an interesting question, although I have to admit, that I have never come across this problem in a real world application. The reason why this happens is to be found in the inner workings of D3's drag implementation.

First, it is worth mentioning, that, if you remove an element from the DOM tree, it can no longer become a target when performing hit-testing for a pointer event. Thus, no event listener registered on that element will be executed any more. This is the behavior one would expect and it is the reason for your confusion, because in your JSFiddle the listener seems to executed even though the element was successfully removed.

To understand what is going on, you have to dig into the source code of d3.drag(). On initialization the drag behavior registers various event handlers on the selection's elements:

function drag(selection) {
  selection
      .on("mousedown.drag", mousedowned)
  //...
}

This handler listening for mousedown events will not set up the rest of the drag behavior before such an event is fired on the respective element. Once an element of the drag behavior receives a mousedown event, the internal mousedowned() handler will be executed:

function mousedowned() {
  //...
  select(event.view).on("mousemove.drag", mousemoved, true).on("mouseup.drag", mouseupped, true);
  //...
}

Within this handler a "mousemove.drag" and a "mouseup.drag" listeners are registered on event.view. This view property of the MouseEvent is inherited from the UIEvent interface and—at least in browsers—points to the Window object the event happened in. Those drag handlers on the global window are used by d3-drag to do its work. And those handlers are responsible for the seemingly confusing behavior you witnessed. We will come to this soon, first let us check how the listeners are subsequently removed.

When the drag gesture eventually ends by firing a mouseup event, those handlers are removed from the window object in the function mouseupped():

function mouseupped() {
  select(event.view).on("mousemove.drag mouseup.drag", null);
}

Now, let us have another look at your code. Even though you removed the target for the drag behavior triggered by a keydown event, the aforementioned handlers on the window still exist because you are keeping the mouse button pressed whereby suppressing a mouseup event to be fired. Hence, the mouseupped() handler has not yet been executed. This will keep the drag behavior alive as mousemove events are still captured by the drag's internal handlers on window. Additionally, those internal handlers will also keep delegating to your own dragged handler causing the console output you are witnessing.

As mentioned at the very beginning of this post, I have never seen this causing any real world trouble. If you nonetheless want to avoid this behavior you could remove the internal handlers once you remove the target:

d3.select(window).on("keydown", function() {
    d3.select(".draggable-rect").remove();
    d3.select(d3.event.view)          // Remove global (internal) drag handlers 
      .on("mousemove.drag", null)
      .on("mouseup.drag", null);
})

As is always the case when it comes to fiddling with the inner workings of some library, you have to be cautious not to break other things and to keep in mind that this is in danger of breaking silently with any future release of D3.

Have a look at this working demo:

d3.select("svg").append('rect').attr('class', 'draggable-rect');
                               
d3.select(window).on("keydown", function() {
	d3.select(".draggable-rect").remove();
  d3.select(d3.event.view)
    .on("mousemove.drag", null)
    .on("mouseup.drag", null);
})

d3.select(".draggable-rect")
  .call(d3.drag().on("start", dragstarted)
  					 		 .on("drag", dragged)
      	     		 .on("end", dragended));

function dragstarted(d) {
  d3.select(this).raise().classed("active", true);
}

function dragged(d) {
	console.log("dragging")
  d3.select(this).attr("x", d3.event.x - 40).attr("y", d3.event.y - 40);
}

function dragended(d) {
  d3.select(this).classed("active", false);
}
.test-area {
  width: 400px;
  height: 400px;
  border: 1px solid black;
}

svg {
  width: 400px;
  height: 400px;
}

.draggable-rect {
  width: 80px;
  height: 80px;
  fill: green;
}
<script src="https://d3js.org/d3.v4.js"></script>
<div class="test-area">
  <svg>
  </svg>
</div>


Related Query

More Query from same tag