【问题标题】:d3 dragging event does not terminate in Firefoxd3 拖动事件不会在 Firefox 中终止
【发布时间】:2016-02-01 19:39:48
【问题描述】:

我正在使用 d3 force 有向图动画。

重现问题的步骤:

  1. 启动 Firefox 浏览器
  2. 访问provemath.org
  3. 点击右上角的 x 或登录(此时应出现节点)
  4. 点击任意节点
  5. 点击左上角的后退箭头

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

见解:

这只发生在 Firefox 中。

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') 中的this 问题,但无济于事。 setData 代码似乎具有在线路运行后立即停止事件的效果,这对我来说没有意义。

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

更新:我将包含更多代码摘录:

ma​​in.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')
    }
}

满足使用超时的建议,即使时间为“0”:

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

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

【问题讨论】:

    标签: javascript firefox d3.js event-handling dom-events


    【解决方案1】:

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

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

    可能有一个适当的修复方法,但如前所述,这需要一个 jsFiddle 来隔离问题。但是,我猜还有以下 hack 可以解决这个问题:将调用包装到 $.event.triggersetTimeout 中,类似于

    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 函数处理程序中。不确定,因为不清楚他们是做什么的。

    【讨论】:

    • 如果有帮助,您可以在github.com/ProveMath/prove-math查看整个代码
    • 点击节点后出现的水平条实际上是新的DOM元素(通过jQuery的.append()方法添加)。
    • @mareoraft 我现在才意识到你进入这个mouseup mousedown 的东西是因为你想要一种区分点击和拖动的方法,所以你最终重新实现了点击行为。那是对的吗?如果是这样,您可以切换回订阅'click' 事件,并在处理函数内部,测试d3.event.defaultPrevented 是否为true,表示节点被拖动,或者false 表示不是。见this SO post。这行得通吗?
    • 还可以查看this jsfiddle 并单击或拖动节点并查看警报消息
    • 谢谢@mareoraft!!!据我所知,没有办法调整d3.drag 以使其不那么“急切”。我想您可以将"click" 事件(即检查d3.event.defaultPrevented)与使用mousedownmouseup 的现有方式结合使用来记录拖动距离和持续时间。但是'node-click' 事件的触发应该从'click' 事件处理程序——而不是mouseup——来完成,以防止firefox 错误。这仍然避免使用setTimeout
    【解决方案2】:

    您是否按原样使用d3.event.dataTransfer.setData('text', 'anything')?将text设置为mime-type时Firefox会中断,您需要使用text/plain

    PSA:在 IE11 中,情况正好相反。事实上,当您将 'Text' 以外的任何内容设置为 mime 类型时,IE11 就会中断!

    【讨论】:

    • 你是不是建议我改用d3.event.dataTransfer.setData('text/plain', 'anything')这一行?另外,你建议我在哪里插入这个?
    • 当我将它放在 mouseup 或 mousedown 的开头时,它会完全取消点击。如果我把它放在其中一个函数的末尾,它根本没有效果。
    猜你喜欢
    • 2012-12-04
    • 2019-04-13
    • 2014-05-04
    • 2019-04-23
    • 1970-01-01
    • 1970-01-01
    • 2018-03-07
    • 1970-01-01
    • 2015-03-03
    相关资源
    最近更新 更多