防止循环 JavaScript 事件

3
这个问题涉及到D3特定的函数,但不是D3特有的问题。我创建了两个带缩放功能的D3图表,并把它们连接在一起,让它们的缩放比例和位置保持一致,这样当一个图表被缩放时,另一个图表将自动缩放到相同的比例和位置。为了做到这一点,我在一个图表被缩放时触发了一个自定义事件,以便我可以自动调整另一个图表:
// Within the chart definition:
var svg = ...; // The chart body definition
var zoom =  d3.behavior.zoom()
    .on("zoom", function(e){
        // do stuff...
        $("#chart-div").trigger({
            type: "my.zoom",
            translate: d3.event.translate,
            scale: d3.event.scale
        });
    });
svg.call(zoom);

然后我创建的方法允许我手动调整每个图表的缩放级别,在此过程中触发D3的zoom事件:

var adjustZoom = function(t,s){
    svg.transition().duration(500).call(zoom.scale(s).translate(t).event);
};

接下来,我会为每个图表添加事件监听器,这样当我与其中一个进行交互并导致其缩放级别发生变化时,其他图表会自动更新:

$("#chart-div1").on("my.zoom", function(e){
    chart2.adjustZoom(e.translate, e.scale);
});
$("#chart-div2").on("my.zoom", function(e){
    chart1.adjustZoom(e.translate, e.scale);
});

明显的问题是,调用adjustZoom 方法会触发 D3 的 zoom 事件,因此在两个图表上附加事件监听器会创建循环事件,因为每个图表都试图无限调整另一个图表。
1. chart1 [manual zoom]      --> trigger d3.zoom --> triggers my.zoom
2. chart2 [my.zoom listener] --> trigger d3.zoom --> triggers my.zoom
3. chart1 [my.zoom listener] --> trigger d3.zoom --> triggers my.zoom
etc...

我想要实现的是一种检查my.zoom事件是否已经触发的方法,以便在决定触发另一个my.zoom事件时,防止事件的循环触发(防止上述示例中的第3步),但我不知道如何去做。有没有一种方法让函数或事件意识到触发它的事件,并停止自身?
2个回答

1
如果translatescale不是模糊值,脑海中首先想到的选项是使当前translatescale调用adjustZoom成为无操作。因此,如果chart1的值发生变化,它会引发事件,改变chart2的值,然后chart2引发事件,但chart1会打破这个循环,因为它的translatescale已经是那些值。

另一个选项是在事件中传递更改的源元素,如果这是启动它的图表,则不在该图表上触发它。

例如,在触发时:

$("#chart-div").trigger({
    type: "my.zoom",
    translate: d3.event.translate,
    scale: d3.event.scale,
    source: this              // <=== The new bit
});

然后当处理时:

if (chart2[0] !== e.source) {
    chart2.adjustZoom(e.translate, e.scale);
}

请注意,该代码示例假定chart1chart2是jQuery包装器,围绕单个元素;如果不是,请相应调整。

有趣的想法,但我不认为这会解决我的问题。事件仍然会来回反弹,因为每个图表都会触发一个新的 my.zoom,并将自己作为源。我可能需要在每个事件处理程序中传递初始的 source 值,以确保图表在响应其最初触发的事件链时不会触发事件。这可能有效,但不太优雅。 - woemler
1
@willOEM:是的,你需要使用echo source命令--但我并不认为这有什么不雅之处。 - T.J. Crowder

0

我找到了一个针对D3的问题的特定解决方案:

D3事件对象 d3.event 包含一个嵌套对象 sourceEvent,该对象代表触发D3事件的先前事件堆栈。此嵌套对象中的底部事件是一个简单的DOM事件。例如,在缩放时,这可以是鼠标滚轮或点击事件。在我的示例中,自定义事件表示为带有所有常规事件键和属性的普通对象,因此我修改了我的缩放事件处理程序,如下所示:

var zoom =  d3.behavior.zoom()
.on("zoom", function(e){
    // do stuff...
    if (typeof d3.event.sourceEvent.sourceEvent != 'object'){
        $("#chart-div").trigger({
            type: "my.zoom",
            translate: d3.event.translate,
            scale: d3.event.scale
        });
    }
});

这个新的测试的作用是检查sourceEvent是否有父事件,如果有的话,确保它不是一个普通对象(我的自定义事件)。现在,当我缩放一个图表时,它会触发一个my.zoom事件,该事件会触发第二个图表上的缩放功能,但不会触发第二个图表自己的my.zoom事件。
知道是否有类似的通用方法来处理JavaScript事件会很好,但对于我的用例来说,这个解决方案可能已经足够了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接