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

IT技术 javascript drag-and-drop d3.js
2021-02-26 18:51:04

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

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记录事件,并且在mouseupclick事件被记录下来。

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

3个回答

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

另请参阅此问题的答案

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

您可以在区分clickdragstart,但它是一个更难区分mousdowndragstart

dragstart将在您开始拖动操作时触发,这意味着当您执行mousedown. 这就是为什么。无论何时你clickdragstart都会triggered(aclickmousedown+ 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');
}

请参阅更新版本现在,拖动时click不再触发 。

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

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

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