d3拖动事件不会在Firefox

时间:2016-02-01 19:39:49

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

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

重现问题的步骤:

  1. 启动Firefox浏览器
  2. 访问provemath.org
  3. 点击右上角的x或登录(此时节点应显示)
  4. 点击任意节点
  5. 点击左上角的后退箭头
  6. 结果是您单击的节点仍然附加到鼠标,就像您要拖动它一样。理想的结果是不会发生这种情况:)

    见解:

    这只发生在Firefox中。

    d3相关代码: 当数据绑定到节点时,我们使用.call(gA.drag) 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;
    }
    

    我尝试在this问题的建议中使用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);
    

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

2 个答案:

答案 0 :(得分:1)

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

PSA:在IE11中,它是另一种方式。事实上,当你设置'Text'作为mime-type时,IE11会中断!

答案 1 :(得分:1)

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

我怀疑问题是在Firefox d3中完全错过了dragend事件,因为mouseup在它之前触发,而mouseup触发了node-click node-click 。我无法进一步看到,但我猜测立即触发dragend(意味着同步)会导致DOM的更改,使另一个元素出现在拖动的节点前面,从而导致错过{{1 }}。这只是一个理论,它可能只是部分准确,并且错过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函数处理程序中。不确定,因为不清楚他们做了什么。