D3区分具有拖动行为的元素的单击和拖动

Posted

技术标签:

【中文标题】D3区分具有拖动行为的元素的单击和拖动【英文标题】:D3 Differentiate between click and drag for an element which has a drag behavior 【发布时间】:2013-11-24 17:27:54 【问题描述】:

我无法成功区分使用 D3.js v3 绑定到两者的元素上的 click 事件和 drag 事件。下面代码中的圆圈被分配了一个拖动行为和一个click 监听器。 Demo here

var dragGroup = d3.behavior.drag()
    .on('dragstart', function () 
    console.log('Start Dragging Group');
)
    .on('drag', function (d, i) 
    d.x += d3.event.dx;
    d.y += d3.event.dy;
    d3.select(this).attr("transform", "translate(" + d.x + "," + d.y + ")");
);

var dragCircle = d3.behavior.drag()
    .on('dragstart', function () 
    d3.event.sourceEvent.stopPropagation();
    d3.event.sourceEvent.preventDefault();
    console.log('Start Dragging Circle');
)
    .on('drag', function (d, i) 
    d.cx += d3.event.dx;
    d.cy += d3.event.dy;
    d3.select(this).attr('cx', d.cx).attr('cy', d.cy);
);

var svg = d3.select('body').append('svg').attr('viewBox', '-50 -50 300 300');
var g = svg.selectAll('g').data([
    x: 10,
    y: 10
])
    .enter().append('g').call(dragGroup);

g.append('rect').attr('width', 100).attr('height', 100);

g.selectAll('circle').data([
    cx: 90,
    cy: 80
]).enter()
    .append('circle')
    .attr('cx', function (d) 
    return d.cx;
)
    .attr('cy', function (d) 
    return d.cy;
)
    .attr('r', 30)
    .call(dragCircle)
    .on('click', function () 
    console.log('clicked circle');
);

每当我单击示例中的圆圈时,我都会让控制台记录drag 事件以及click 事件。拖动时我也会遇到相同的行为,首先记录drag 事件,然后在mouseup 上记录click 事件。

分别处理这些事件的正确方法是什么? 用例是尝试在树布局中处理节点单击和节点拖放。

【问题讨论】:

你试过在dragend上做preventDefault()等吗? @LarsKotthoff 是的,我有,也无济于事:( 你见过this question吗? 嗨 Lars,非常感谢您指出正确的方向。这是解决方案,如果你愿意,也许可以把它放在类似于@leMoisela 的答案中,这样我可以相信你?感谢您的所有帮助和关注。 @LarsKotthoff 感谢您让我朝着正确的方向前进!正在拉扯我的头发,试图找出如何在 d3 中捕获拖动事件 【参考方案1】:

缺少的关键位是检查是否已阻止事件的默认行为。也就是说,d3.event.preventDefault() 有一个匹配的兄弟姐妹 -- d3.event.defaultPrevented。您需要在 click 处理程序中检查这一点,以查看是否正在进行任何拖动操作。

另请参阅this question 的答案。

【讨论】:

不幸的是 d3.event.defaultPrevented 不能始终如一地工作。至少在 Chrome 中,我观察到,尽管有此检查,但有时仍会在拖动时执行点击处理程序。 @BruceHill 可能值得发布一个单独的问题,说明您遇到的具体问题,因为这个问题已经很老了。 @BruceHill 我在 Windows 上的 Chrome 中发现了同样的问题,它并没有始终阻止默认设置。 我也有这个问题,也在 Chrome 中观察到。我相信这与我们的可拖动节点有关,这些节点很复杂,包含多个路径和文本元素。我通过更改 d3 “修复”了这个问题:我的 dragRestore 调用(从 v3.5.8 开始的第 1235 行)读取为 dragRestore(dragRestore);而不是 dragRestore(dragged && d3.event.target === target); 我评论了github.com/mbostock/d3/issues/1005,维护者正在通过github.com/mbostock/d3/pull/2616修复这个不一致问题【参考方案2】:

您可以区分clickdragstart,但很难区分mousdowndragstart

dragstart 将在您开始拖动操作时触发,即当您执行mousedown 时。这就是为什么。每当你clickdragstart 将是triggered。 (clickmousedown + mouseup)。

所以应该可以防止点击被触发。在您的代码中,您应该按照 Lars Kotthoff 的提示添加 preventDefault。但不要放在dragstart函数中:

var dragCircle = d3.behavior.drag()
    .on('dragstart', function () 
    d3.event.sourceEvent.stopPropagation();
    d3.event.sourceEvent.preventDefault(); <-- Remove This
    console.log('Start Dragging Circle');
)

并将其添加到正确的位置(在点击功能中),并用d3正确写入(d3.event.defaultPrevented

g.selectAll('circle').data([
    cx: 90,
    cy: 80
]).enter()
    .append('circle')
    .attr('cx', function (d) 
    return d.cx
)
    .attr('cy', function (d) 
    return d.cy
)
    .attr('r', 30)
    .call(dragCircle)
    .on('click', click);

function click(d) 
  if (d3.event.defaultPrevented) return; <-- Add d3.event.defaultPrevented
  console.log('clicked');

见the updated version。现在,当拖动时,click 不再被触发。

请记住,单击时,dragstart 仍会被触发。 (但不是drag

【讨论】:

【参考方案3】:

d3.event.sourceEvent.preventDefault() 没有按预期工作,或者说它不一致。

我遇到了这个问题,为了区分这两个事件,我在 onDrag 事件中使用了布尔值 isDragged。因此,如果设置了此值,则在对象上执行 drag 事件,否则将执行 click 事件。 对对象的正常点击也会触发其dragstartdragend 事件,但不会触发onDrag 事件。

【讨论】:

以上是关于D3区分具有拖动行为的元素的单击和拖动的主要内容,如果未能解决你的问题,请参考以下文章

停止 Chrome 的单击并按住/拖动图像默认行为

jQuery可拖动+可排序:奇怪的鼠标偏移行为

jQuery UI:使用容差触摸放置对象时的可拖动行为

更改 react-beautiful-dnd 可拖动点击区域

iOS 相机应用程序中的可拖动 UIButtons

JavaFX区分拖动和单击