d3 拖动事件不会在 Firefox 中终止

d3 dragging event does not terminate in Firefox

本文关键字:Firefox 终止 拖动 事件 d3      更新时间:2023-09-26

我正在使用 d3 力定向图形动画。

重现问题的步骤:

  1. 启动火狐浏览器
  2. 访问 provemath.org
  3. 单击右上角的 X 或登录(此时应显示节点(
  4. 单击任何节点
  5. 点击左上角的后退箭头

结果是您单击的节点仍然附加到鼠标上,就像您拖动它一样。 期望的结果是这不会发生:)

见解:

这只发生在火狐中。

D3相关代码:当数据绑定到节点时,我们使用 .call(gA.drag) where gA.drag = gA.force.drag() ,在 d3 库本身中,我们有:

    force.drag = function() {
      if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
      if (!arguments.length) return drag;
      this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
    };
    function dragmove(d) {
      d.px = d3.event.x, d.py = d3.event.y;
      force.resume();
    }
    return d3.rebind(force, event, "on");
  };
  function d3_layout_forceDragstart(d) {
    d.fixed |= 2;
  }
  function d3_layout_forceDragend(d) {
    d.fixed &= ~6;
  }
  function d3_layout_forceMouseover(d) {
    d.fixed |= 4;
    d.px = d.x, d.py = d.y;
  }
  function d3_layout_forceMouseout(d) {
    d.fixed &= ~4;
  }

此外,当数据绑定到节点时,我使用 .on('mousedown', mousedown).on('mouseup', mouseup) 。 我编写了这些函数,它们是:

function mousedown(node) {
    node.time_before = getShortTime(new Date())
    node.client_x_before = d3.event.clientX
    node.client_y_before = d3.event.clientY
    // d3.event.stopPropagation() // need cancelBubble for MS
}
function mouseup(node) {
    if( mod(getShortTime(new Date()) - node.time_before, 60) < 0.85
            && cartesianDistance([node.client_x_before, node.client_y_before], [d3.event.clientX, d3.event.clientY]) < 55
        ) {
        $.event.trigger({ type: 'node-click', message: node.id })
    }
    delete node.time_before
    delete node.client_x_before
    delete node.client_y_before
}
function getShortTime(date) {
  return date.getSeconds() + date.getMilliseconds()/1000
}
function mod(m, n) {
    return (m % n + n) % n;
}

我已经尝试在代码的不同点使用d3.event.stopPropagation()d3.event.dataTransfer.setData('text', 'anything'),如这个问题中建议的那样,无济于事。 setData代码似乎具有在线路运行后立即停止事件在其轨道上死亡的效果,这对我来说没有意义。

一种可能但不完全令人满意的解决方案可能是在用户单击后退箭头时手动查找并销毁拖动事件。

更新:我正在包含更多代码摘录:

main.py

$(document).on('node-click', function(Event){
    current_node = graph.nodes[Event.message] // graph.nodes is a DICTIONARY of nodes
    updateNodeTemplateLearnedState()
    blinds.open({ // in this module, new DOM elements are added with jQuery's .append() method
        object: current_node,
    })
    hide('svg')
    hide('#overlay')
    show('#node-template') // This DOM element is the container that blinds.open() populated.  Event WITHOUT adding new DOM elements, it is possible that the mere putting of this guy in front of the vertices is causing the issue
    if( false /*mode !== 'learn'*/){
        ws.jsend({ command: "re-center-graph", central_node_id: current_node.id })
    }
})
function show(css_selector) { // this stuff fails for svg when using .addClass, so we can just leave show and hide stuff in the JS.
    let $selected = $(css_selector)
    if( !_.contains(css_show_hide_array, css_selector) ){
        $selected.css('height', '100%')
        $selected.css('width', '100%')
        $selected.css('overflow', 'scroll')
    }else{
        // $selected.removeClass('hidden')
        $selected.css('visibility', 'visible')
    }
}

Meetamit 建议使用超时,即使时间为"0":

setTimeout(function() {
            $.event.trigger({ type: 'node-click', message: node.id })
        }, 0);

实际上是有效的,所以我认为他的理论是正确的。

如果没有访问完整代码以及插入调试调用和测试潜在修复程序的能力,很难诊断此问题。理想情况下,你会有一个jsFiddle来重现这个问题,同时隔离相关的代码(如果需要,可以使用假的硬编码数据(。如果你能创建那个jsFiddle,我很乐意尝试在那里修复它并在这里修改我的答案。否则,这里是:

我怀疑问题是在Firefox d3中完全错过了dragend事件,因为mouseup是在它之前触发的,并且从mouseup触发node-click。我看不到更远的地方,但我猜立即触发node-click(意味着同步(会导致 DOM 发生变化,使另一个元素出现在拖动节点的前面,从而导致错过dragend。这只是一个理论,可能只是部分准确,为什么错过dragend的细节更加微妙。

可能有一个适当的修复程序,但如前所述,这需要一个 jsFiddle 来隔离问题。但是,我猜还有以下黑客可以解决此问题:将$.event.trigger调用包装在setTimeout中,类似于

function mouseup(node) {
  if( mod(getShortTime(new Date()) - node.time_before, 60) < 0.85
        && cartesianDistance([node.client_x_before, node.client_y_before], [d3.event.clientX, d3.event.clientY]) < 55
    ) {
    setTimeout(function() {
      $.event.trigger({ type: 'node-click', message: node.id })
    }, 100);
  }
  delete node.time_before
  delete node.client_x_before
  delete node.client_y_before

}

使用 setTimeout 会稍微延迟 node-click 事件,使浏览器和/或 d3 有机会在修改 DOM 之前完成拖动业务。它并不漂亮,通常有更好的方法来修复不涉及setTimeout的同步问题,这往往会堆积新问题而不是避免它。但也许你会很幸运,这将解决它而不会引起新的问题 ̄''_(ツ)_/̄

setTimeout的第二个参数(显示为100(是您应该尝试的。可能是0可以工作,或者可能需要大于 100。

此外,可能还需要将 delete 语句移动到 setTimeout 函数处理程序中。不确定,因为目前还不清楚他们是做什么的。

你按原样使用d3.event.dataTransfer.setData('text', 'anything')吗?当您将text设置为MIME类型时,Firefox会中断,您需要使用text/plain

PSA:在IE11中,情况正好相反。事实上,当你将除'Text'以外的任何东西设置为哑剧类型时,IE11 就会中断!