简体   繁体   中英

d3 - drag behaviour triggered when clicking

In my svg container, I have a <g> with a rectangle and text. When dragging this group, I want to make the group disappear and just drag a small rectangle across the screen to represent the object. In order to do that, I use a <div> which I make visible when dragging starts. I make my svg group being dragged disappear.

You can see the fiddle here . And code here:

   function dragDiv2End(d) {
    console.log('ending');
    var a = d3.select(this);
    a.attr('x', initial.x).attr('y', initial.y).attr('width', initial.width).attr('height', initial.height);
    a.transition().style("opacity",1);
    d3.select('#dragid').style('visibility', 'hidden');

}

function dragDiv2Start(d) {
    d3.event.sourceEvent.stopPropagation();
    console.log('starting');
    var a = d3.select(this);
    initial = {x: a.attr('x'), y: a.attr('y'), width: a.attr('width'), height: a.attr('height')};
    a.attr('x', d3.mouse(this)[0])
    .attr('y', d3.mouse(this)[1]) .attr('width', 20)
    .attr('height', 20).style("opacity",0);

    var b = d3.select('#dragid');
    b.style({
        left: (parseInt(d3.mouse(this)[0])) + "px",
        top: d3.mouse(this)[1] + "px",
        border: "1px solid black",
        visibility: 'visible'

    });
}

function dragDiv2Move(d) {
    var b = d3.select(this);

    var a = d3.select('#dragid');
    a.transition().delay(50).style('opacity', 1);
    //console.log(d3.event.x, d3.event.y, a.style("right"));
    console.log(d3.mouse(this));
    a.style({
        left: (parseInt(d3.mouse(this)[0])) + "px",
        top: d3.mouse(this)[1] + "px"
    });
}

function doClick(d) {
    if (d3.event.defaultPrevented) return;
    console.log('clicked');
}
var initial = {};
var svg = d3.select('#div2').append('svg').attr('height', 300).attr('width', 300).style('border', 'solid 1px blue');

var g = svg.append('g').on('click', doClick);
g.append('rect').attr('x', 10).attr('y', 10).attr('width', 200).attr('height', 200).style('stroke', 'red').style('fill', 'white');
var text = g.append('text')
text.text('my test').attr('x', 50).attr('y', 50);

var dragDiv = d3.behavior.drag()
        .origin(Object)
        .on("drag", dragDivMove);

var dragDiv2 = d3.behavior.drag()
        .origin(Object)
        .on("dragstart", dragDiv2Start)
        .on("drag", dragDiv2Move)
        .on("dragend", dragDiv2End);
g.call(dragDiv2);

Trouble starts with the click event which the group should also listen to. I get this flickering effect of my svg object disappearing (behaviour of the drag event) when a simple click occurs.

I understand why it happens but this is very undesirable and I am struggling to find a solution to this problem. To see it occurring, just click and/or drag the red rectangle.

I tried preventing the event from propagating, which works fine from drag to click but not the other way around.

Any advice will be much appreciated.

I cleaned up your code a bit and used the 'Tidy' effect in this SO code snippet. to format the synatx..

First, within your Start and Move functions.. I'm making a variable object hold the d3.mouse(this) data. (ie var currentPosition = {"x" : d3.mouse(this)[0], "y" : d3.mouse(this)[1]}; ).

What I'm doing is adding a threshold value and associating it to distance. I call on the dragStart method after wrapping it in an anonymous function. This allows me to take the this instance, as well as the thresholdValue , and bind it to the dragStart that I'm calling it.

var dragDiv2 = d3.behavior.drag()
  .origin(Object)
  .on("dragstart", function(arg) {
    console.log("anonymous function - arg: %o", arg);
    dragDiv2Start.bind(this.children[0], 5)();
  })
  .on("drag", dragDiv2Move)
  .on("dragend", dragDiv2End);

However , this is actually the element g .. which does not have any attributes set to it, so I'm getting the first child - which is rect -, and binding that to the dragStart method.

I created an object variable called 'threshold' which is assigned the min.x , min.y , max.x , and max.y values in the dragStart function. I also set a flag - called IsNeeded indicating whether the dragMove function should check against the threshold, and conditionally return or apply visual changes.. the dragEnd will simply return if IsNeeded is 'still' true;

I moved your visual styling from dragStart into dragMove , and I only run it at the time threshold.isNeeded is set to false .


Sample Code Snippet

 function dragDivMove(d) { var a = d3.select(this); var data = { "event": { "dx": parseInt(d3.event.dx), "dy": parseInt(d3.event.dy) }, select: { "right": parseInt(a.style("right").replace("px", "")), "top": parseInt(a.style("top").replace("px", "")) } }; console.log(data); a.style({ "right": (data.select.right - data.event.dx) + "px", "top": (data.event.dy + data.select.top) + "px" }); } function dragDiv2End(d) { console.log('ending'); var a = d3.select(this); var b = d3.select('#dragid'); a.attr('x', initial.x) .attr('y', initial.y) .attr('width', initial.width) .attr('height', initial.height); a.transition().style("opacity", 1); b.style('visibility', 'hidden'); } function dragDiv2Start(thresholdValue) { d3.event.sourceEvent.stopPropagation(); var a = d3.select(this); console.log('starting - this: %o , d3.select(this): %o, thresholdValue: %o', this, a, thresholdValue); initial = { "x": parseInt(a.attr('x').replace("px",0)), "y": parseInt(a.attr('y').replace("px",0)), "width": parseInt(a.attr('width').replace("px","")), "height": parseInt(a.attr('height').replace("px","")) }; threshold.min = { "x": initial.x - thresholdValue, "y": initial.y - thresholdValue }; threshold.max = { "x": initial.x + thresholdValue, "y": initial.y + thresholdValue }; threshold.isNeeded = true; var currentPosition = { "x": d3.mouse(this)[0], "y": d3.mouse(this)[1] }; console.log("current: %o, initial: %o, threshold: %o", currentPosition, initial, threshold); } function dragDiv2Move(d) { var a = d3.select('#dragid'); var b = d3.select(this); var currentPosition = { "x": d3.mouse(this)[0], "y": d3.mouse(this)[1] }; if (threshold.isNeeded) { if (threshold.min.x < currentPosition.x || threshold.min.y < currentPosition.y || threshold.max.x > currentPosition.x || threshold.max.y > currentPosition.y) { threshold.isNeeded = false; b.attr('x', currentPosition.x); b.attr('y', currentPosition.y); b.attr('width', 20); b.attr('height', 20); b.style("opacity", 0); a.style({ "left" : currentPosition.x + "px", "top" : currentPosition.y + "px", "border" : "1px solid black", "visibility" : 'visible' }); } else { return; } } console.log(currentPosition); a.transition().delay(50).style('opacity', 1); a.style({ "left" : currentPosition.x + "px", "top" : currentPosition.y + "px" }); } function doClick(d) { if (d3.event.defaultPrevented) return; console.log('clicked'); } var initial = {}, threshold = { "min": { "x": 0, "y": 0 }, "max": { "x": 0, "y": 0 }, "isNeeded": true }; var svg = d3.select('#div2').append('svg').attr('height', 300).attr('width', 300).style('border', 'solid 1px blue'); var g = svg.append('g').on('click', doClick); g.append('rect').attr('x', 10).attr('y', 10).attr('width', 200).attr('height', 200).style('stroke', 'red').style('fill', 'white'); var text = g.append('text') text.text('my test').attr('x', 50).attr('y', 50); var dragDiv = d3.behavior.drag() .origin(Object) .on("drag", dragDivMove); var dragDiv2 = d3.behavior.drag() .origin(Object) .on("dragstart", function() { dragDiv2Start.bind(this.children[0], 5)(); }) .on("drag", dragDiv2Move) .on("dragend", dragDiv2End); g.call(dragDiv2); //d3.select('#dragid').call(dragDiv); 
 #dragid { background-color: lightblue; position: absolute; top: 0px; left: 0px; width: 5px; height: 5px; visibility: hidden } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <div id="dragid"> <span></span> </div> <div id="div2"></div> 


Note: there is a formula error. the initial values (of g > rect ) are being compared to the subsequent currentPosition values of the mouse in dragMove .. It should be that the currentPosition values of the mouse in dragStart are to be compared against subsequent currentPosition values of the mouse in dragMove ..

Regardlesss, It's worth noting that dragMove does not fire unless you actually drag with the mouse key down.. which is why the visuals do not flicker; even though, in the current implementation the isNeeded calculation is always satisified in dragMove ..

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