d3.js:dragstart/mousedown期间重新插入元素会干扰Chrome和Safari中的点击事件,但不会影响Firefox

d3.js:dragstart/mousedown期间重新插入元素会干扰Chrome和Safari中的点击事件,但不会影响Firefox,d3.js,D3.js,我在使用d3.js在相同的元素上实现点击和拖动功能时遇到了一个问题…如果您有任何帮助,我将不胜感激。下面描述的问题发生在Chrome和Safari上,而不是Firefox上 一些背景 当拖动一个元素时(至少使用d3),事件顺序如下: dragstart 拖 德拉根德 点击 dblclick 即使元素实际上没有被拖动(因此没有拖动事件),dragstart和dragend事件仍然会触发(在mousedown和mouseup上),并随后发生click事件 问题 我需要在相同的SVG元素上同时具有

我在使用d3.js在相同的元素上实现点击和拖动功能时遇到了一个问题…如果您有任何帮助,我将不胜感激。下面描述的问题发生在Chrome和Safari上,而不是Firefox上

一些背景 当拖动一个元素时(至少使用d3),事件顺序如下:

  • dragstart
  • 德拉根德
  • 点击
  • dblclick
即使元素实际上没有被拖动(因此没有拖动事件),dragstart和dragend事件仍然会触发(在mousedown和mouseup上),并随后发生click事件

问题 我需要在相同的SVG元素上同时具有拖放和单击(以及双击)功能。当拖动一个元素时,对于我们来说,重要的是元素总是在z索引意义上的任何其他元素之上分层(即,在拖动时,它不会被同级元素隐藏)。使用SVG,DOM中稍后出现的元素被分层在较早出现的元素之上。因此,为了保证drag元素始终位于顶部,我在dragstart事件处理程序执行期间使用以下命令将drag元素作为其父元素的最后一个子元素附加:

elem.parentNode.appendChild(elem);
不幸的是,这会导致在Chrome和Safari中不会触发click事件,如果所讨论的元素不是开始的最后一个子元素(因此被附加到它的新DOM位置)。如果元素作为最后一个子元素开始(或从上一次单击中追加),则单击事件会在后续单击中触发。mousedown和mouseup事件也会发生同样的事情(在没有拖动行为的情况下),尽管这个用例对我们并不重要。同样,在Firefox中,这些都不是问题

更复杂的是(这里很难解释),我还需要在dragend事件完成后重新绘制元素,这还涉及到将元素重新排序为其原始DOM顺序。因此,即使在一个元素被单击一次之后,一旦dragend处理程序完成执行,它也永远不会成为最后一个子元素

示范 我在这里设置了一个演示:

圆形显示拖动行为的问题,方形显示mousedown/mouseup处理程序的问题。单击圆形或方形可以切换形状的颜色。请注意,在Chrome和Safari上,只有最后一个圆圈或正方形触发单击事件,而在Firefox上它们都会触发

上面演示的JS代码如下所示:

var svg = d3.select('body > svg');

var radius = 30;

var circleData = [
    {
        position: { x: 90, y: 100 },
        value: 1
    },
    {
        position: { x: 220, y: 100 },
        value: 2
    },
    {
        position: { x: 350, y: 100 },
        value: 3
    }
];

var squareData = [
    {
        position: { x: 90, y: 350 },
        value: 4
    },
    {
        position: { x: 220, y: 350 },
        value: 5
    },
    {
        position: { x: 350, y: 350 },
        value: 6
    }
];

var setupCircles;
var setupSquares;

var reinsertElem = function (elem) {
    elem.parentNode.appendChild(elem);
};

var handleClick = function (d, i) {
    console.debug('clicked: ' + i);

    // Make lit on click only!!
    var shapeGroup = d3.select(this);
    var isLit = shapeGroup.classed('lit');
    shapeGroup.classed('lit', !isLit);
};

var handleMouseDown = function (d, i) {
    console.debug('mouseDown: ' + i);
    // Re-insert the clicked shape so that it appears on top of all other shapes
    reinsertElem(this);
};

var handleMouseUp = function (d, i) {
    console.debug('mouseUp: ' + i);
    setupSquares();
};

var originFn = function (d) {
    return d.position;
};

var dragStartFn = function (d, i) {
    console.debug('drag started: ' + i);
    // Prevent the event from bubbling up
    d3.event['sourceEvent'].stopPropagation();
    // Re-insert the drag shape so that it appears on top of all other shapes
    reinsertElem(this);
};

var dragFn = function (d, i) {
    // Constrain the drag position to keep the shape displayed in the rect area
    var newX = Math.max(radius, Math.min(600 - radius, d3.event.x));
    var newY = Math.max(radius, Math.min(250 - radius, d3.event.y));
    circleData[i].position = { x: newX, y: newY };
    d.position = { x: newX, y: newY };
    d3.select(this).attr('transform', function (d) { return 'translate(' + newX + ',' + newY + ')'; });
};

var dragEndFn = function (d, i) {
    console.debug('drag ended: ' + i);
    setupCircles();
};

var dragBehavior = d3.behavior.drag()
    .origin(originFn)
    .on('dragstart', dragStartFn)
    .on('drag', dragFn)
    .on('dragend', dragEndFn);

setupCircles = function () {
    var circlesGroup = svg.select('g.circles');
    var circleGroup = circlesGroup.selectAll('g')
        .data(circleData, function (d) { return d.value; });

    var circleGroupEnter = circleGroup.enter()
        .append('g')
        .attr('class', function (d, i) { return 'circle' + i; })
        .attr('transform', function (d) {
            var position = d.position;
            return 'translate(' + position.x + ',' + position.y + ')';
        })
        .on('click', handleClick)
        .call(dragBehavior);

    var circle = circleGroupEnter.append('circle')
        .classed('fill0', true)
        .classed('stroke1', true)
        .attr('stroke-width', '1px')
        .attr('r', radius.toString());

    var text = circleGroupEnter.append('text')
        .attr('font-family', 'Arial')
        .attr('font-size', '20px')
        .attr('dominant-baseline', 'central')
        .attr('text-anchor', 'middle')
        .text(function (d, i) { return d.value.toString(); });

    circleGroup.order();
};

setupSquares = function () {
    var squareWidth = radius * 2;
    var squaresGroup = svg.select('g.squares');
    var squareGroup = squaresGroup.selectAll('g')
        .data(squareData, function (d) { return d.value; });

    var squareGroupEnter = squareGroup.enter()
        .append('g')
        .attr('class', function (d, i) { return 'square' + i; })
        .attr('transform', function (d) {
            var positionX = d.position.x - (0.5 * squareWidth);
            var positionY = d.position.y - (0.5 * squareWidth);
            return 'translate(' + positionX + ',' + positionY + ')';
        })
        .on('click', handleClick)
        .on('mousedown', handleMouseDown)
        .on('mouseup', handleMouseUp);

    var square = squareGroupEnter.append('rect')
        .classed('fill0', true)
        .classed('stroke1', true)
        .attr('stroke-width', '1px')
        .attr('width', squareWidth.toString())
        .attr('height', squareWidth.toString());

    var text = squareGroupEnter.append('text')
        .attr('font-family', 'Arial')
        .attr('font-size', '20px')
        .attr('dominant-baseline', 'central')
        .attr('text-anchor', 'middle')
        .attr('x', function (d, i) { return (0.5 * squareWidth).toString(); })
        .attr('y', function (d, i) { return (0.5 * squareWidth).toString(); })
        .text(function (d, i) { return d.value.toString(); });

    squareGroup.order();
};

setupCircles();
setupSquares();

任何关于如何解决这个问题的建议都将不胜感激。

我也有同样的问题。解决办法是:

  • 添加一个布尔变量作为标志,指示需要重新插入
  • 在dragStartFn中,将flag设置为true,并从此处删除重新插入位
  • 在dragFn中,在顶部添加一个检查,如果标志为true,则重新插入并设置标志为false,以便只发生一次
通过这种方式,直接单击不会发生重新插入,并且事件被正确触发。然后对于实际的拖动,重新插入仍然会发生,所以你们在那里也都很好