为什么不点击事件总是火? [英] Why doesn't click event always fire?

查看:96
本文介绍了为什么不点击事件总是火?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果您正在重新审视此问题,我已将所有更新移至底部,因此它实际上更好地作为一个问题。



问题



我使用 D3 处理浏览器事件时遇到了一些奇怪的问题。不幸的是,这是一个相当大的应用程序,因为我完全迷失了原因,我正在努力寻找一个可重复的小例子,所以我将尽可能多地提供有用的信息。



所以我的问题是点击事件似乎不能为某些DOM元素可靠地触发。我有两组不同的元素填充圈子和白色圈子。您可以在下面的屏幕截图中看到 1002 1003 是白色圆圈,而供应商是一个实心圆圈。





现在这个问题发生在我不理解的白色圆圈上。下面的屏幕截图显示了点击圈子时会发生什么。点击顺序通过红色数字和与之关联的日志记录显示。基本上你看到的是:




  • mousedown

  • mouseup

  • 有时点击



这个问题有点零星。我曾设法追踪一个可靠的复制品,但经过浏览器的一些刷新后,它现在更难以重现。如果我替换点击 1002 1003 ,那么我会继续 mousedown mouseup 事件,但从不点击。如果我再次点击其中一个,那么我会得到点击事件。如果我一直点击同一个(此处未显示),则只有每次点击都会触发点击事件。



如果我使用像供应商这样的实心圆圈重复相同的过程,那么它可以正常工作,每次点击点击






如何创建圈子



所以圈子(我的代码中也称为行星)已创建为模块化组件。循环访问数据并创建每个实例

  data.enter()
.append( g)
.attr(class,function(d){return d.promoted?collection moon-group:collection planet-group;})
.call(drag)
.attr(transform,function(d){
var scale = d.size / 150;
returntranslate(+ [dx,dy] +)scale( + [scale] +);
})
.each(function(d){

//为每个项目创建一个新行星
d。 planet = new d3.landscape.Planet()
.data(d,function(){return d.id;})
.append(this,d);
});

这并没有告诉你那么多,在 Force Directed 图表用于计算头寸。 Planet.append()函数中的代码如下:

  d3.landscape.Planet.prototype.append = function(target){
var self = this;

//将目标存储为以后
self .__ container = target;
self .__ events = new custom.d3.Events(planet)
.on(click,function(d){self .__ setSelection(d,!d.selected);})
.on(dblclick,function(d){self .__ setFocus(d,!d.focused); self .__ setSelection(d,d.focused);});

//添加圈子
var circles = d3.select(target)
.append(circle)
.attr(data-name, function(d){return d.name;})
.attr(class,function(d){return d.promoted?moon:planet;})
.attr( r,function(){return self .__ animate?0:self .__ planetSize;})
.call(self .__ events);

这里我们可以看到附加的圆圈(注意每个星球实际上只是一个圆圈)。构造并调用custom.d3.Events用于刚刚添加到DOM的圆。此代码用于填充和白色圆圈,唯一的区别是类中的轻微变化。为每个生成的DOM看起来像:



已填充



 < g class =collection planet-grouptransform =translate(683.080338895066,497.948470463691)scale(0.6666666666666666,0.6666666666666666)> 
< circle data-name =Suppliersclass =planetr =150>< / circle>
< text class =titledy =。35emstyle =font-size:63.1578947368421px;>供应商< / text>
< / g>



白色



 < g class =collection moon-grouptransform =translate(679.5720546510213,92.00957926233855)scale(0.6666666666666666,0.6666666666666666)> 
< circle data-name =1002class =moonr =150>< / circle>
< text class =titledy =。35emstyle =font-size:75px;> 1002< / text>
< / g>






custom.d3.events有什么作用?



这背后的想法是提供比默认情况下更丰富的事件系统。例如,允许双击(不触发单击)和长按等等。



使用圈子调用事件时

code> container执行以下操作,使用D3设置一些 raw 事件。这些与 Planet.append()函数中的连接不同,因为事件 object公开它自己的自定义调度。这些是我用于调试/记录的事件;

  custom.d3.Events = function(){

var dispatch = d3.dispatch(click,dblclick,longclick,mousedown,mouseup,mouseenter,mouseleave,mousemove,drag) ;

var events = function(g){
container = g;

//注册所需的原始事件
g.on(mousedown,mousedown)
.on(mouseenter,mouseenter)
.on( mouseleave,mouseleave)
.on(点击,点击)
.on(contextmenu,contextMenu)
.on(dblclick,doubleClicked);

返回事件;
};

//返回绑定事件
返回d3.rebind(events,dispatch,on);
}

所以在这里,我联系了一些事件。以相反的顺序查看它们:



单击



将click函数设置为只记录我们的值'处理

 单击功能(d,i){
console.log(clicked,d3。 event.srcElement);
//真的不在乎
之后的东西}



mouseup



mouseup函数实际上是日志,并清除了一些全局窗口对象,这将在下面讨论。

  function mouseup(d,i){
console.log(mouseup,d3.event.srcElement);
dispose_window_events();
}



mousedown



mousedown函数稍微复杂一点,我将包含它的全部内容。它做了很多事情:




  • 将mousedown记录到控制台

  • 设置窗口事件(在窗口对象上连接mousemove / mouseup)因此即使鼠标不再在触发mousedown的圆圈内也可以触发mouseup

  • 查找鼠标位置并计算一些阈值

  • 设置计时器以触发长按

  • 触发生活在custom.d3.event对象上的mousedown dispatch

      function mousedown(d,i){
    console.log(mousedown,d3.event.srcElement);

    var context = this;
    拖动= true;
    mouseDown = true;

    //在窗口上连接事件
    setup_window_events();

    //记录鼠标初始位置
    windowStartPosition = getWindowPosition();
    position = getPosition();

    //如果两次点击相隔很远(但可能很快),则禁止双击行为
    if(windowStartPosition&& windowPosition){
    var distance = mood。 math.distanceBetween(windowPosition.x,windowPosition.y,windowStartPosition.x,windowStartPosition.y);
    supressDoubleClick = distance> moveThreshold;
    }
    windowPosition = windowStartPosition;

    //只有在订阅时设置长按计时器 - 因为
    //否则我们不想抑制正常点击。
    if(events.on(longclick)){
    longTimer = setTimeout(function(){
    longTimer = null;
    supressClick = true;
    dragging = false;
    dispatch.longclick.call(context,d,i,position);
    },longClickTimeout);
    }

    //触发鼠标按下事件
    dispatch.mousedown.call(context,d,i);
    if(debug){console.log(name +:mousedown); }
    }







更新1



我应该补充一点,我在Chrome,IE11和Firefox中遇到过这种情况(尽管这似乎成为最可靠的浏览器)。



不幸的是,经过一些刷新和代码更改/恢复后,我一直在努力获得可靠的再现。我注意到的是奇怪的是,以下序列可以产生不同的结果:




  • F5刷新浏览器

  • 点击 1002



有时这个触发器 mousedown mouseup 然后点击。 Othertimes错过了点击。这个问题可能偶然发生在同一页面的两个不同负载之间,这似乎很奇怪。



我还应该补充说我尝试了以下内容:




  • 导致 mousedown 失败并验证点击仍然会触发,以确保 mousedown 中的零星错误无法导致问题。如果 mousedown 中有错误,我可以确认点击将触发事件。

  • 试图检查时间问题。我通过在 mousedown 中插入一个长阻塞循环来做到这一点,并且可以确认 mouseup 点击事件将在相当长的延迟后触发。所以事件看起来像你期望的那样按顺序执行。






更新2



在@ CoolBlue的评论之后快速更新是为我的事件处理程序添加命名空间似乎没有任何区别。以下仍然偶尔会遇到这个问题:

  var events = function(g){
container = g;

//注册所需的原始事件
g.on(mousedown.test,mousedown)
.on(mouseenter.test,mouseenter)
.on(mouseleave.test,mouseleave)
.on(click.test,点击)
.on(contextmenu.test,contextMenu)
.on(dblclick .test,doubleClicked);

返回事件;
};

css 也是我的意思尚未提及。两种不同类型的CSS应该相似。完整的设置如下所示,特别是点事件设置为仅适用于中间的标签圈子。我已经注意避免点击我的一些测试,但据我所知它似乎并没有太大的区别。

  / * Mixins * / 
/ *这里评论* /
.collection .planet {
fill:#8bc34a;
stroke:#ffffff;
stroke-width:2px;
stroke-dasharray:0;
过渡:笔画宽度0.25s;
-webkit-transition:stroke-width 0.25s;
}
.collection .title {
fill:#ffffff;
text-anchor:middle;
pointer-events:none;
-webkit-touch-callout:none;
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
font-weight:normal;
}
.collection.related .planet {
stroke-width:10px;
}
.collection.focused .planet {
stroke-width:22px;
}
.collection.selected .planet {
stroke-width:22px;
}

.moon {
fill:#ffffff;
中风:#8bc34a;
stroke-width:1px;
}
.moon-container .moon {
过渡:stroke-width 1s;
-webkit-transition:stroke-width 1s;
}
.moon-container .moon:hover circle {
stroke-width:3px;
}
.moon-container text {
fill:#8bc34a;
text-anchor:middle;
}
.collection.moon-group .title {
fill:#8bc34a;
text-anchor:middle;
pointer-events:none;
font-weight:normal;
}
.collection.moon-group .moon {
stroke-width:3px;
过渡:笔画宽度0.25s;
-webkit-transition:stroke-width 0.25s;
}
.collection.moon-group.related .moon {
stroke-width:10px;
}
.collection.moon-group.focused .moon {
stroke-width:22px;
}
.collection.moon-group.selected .moon {
stroke-width:22px;
}
.moon:悬停{
stroke-width:3px;
}






更新3



所以我试过把不同的东西排除在外。一种是更改CSS,使白色 1002 1003 现在使用相同的类,因此相同的CSS作为供应商,这是有效的。您可以在下面看到图片和CSS作为证据:



 < g class =collection planet-grouptransform = translate(1132.9999823040162,517.9999865702812)scale(0.6666666666666666,0.6666666666666666)> 
< circle data-name =1003class =planetr =150>< / circle>
< text class =titledy =。35emstyle =font-size:75px;> 1003< / text>
< / g>

我还决定修改 custom.d3.event 代码,因为这是最复杂的事件。我把它剥离回来只是记录:

  var events = function(g){
container = g ;

//注册所需的原始事件
g.on(mousedown.test,function(d){console.log(mousedown.test);})
.on(click.test,function(d){console.log(click.test);});

返回事件;
};

现在看来这仍然无法解决问题。下面是一个跟踪(现在我不知道为什么我每次都会发出两次click.test事件 - 如果有人能够解释它,那就明白了......但现在把它作为常态)。你可以看到的是,在突出显示的情况下, click.test 没有被记录,我不得不再次点击 - 因此双重 mousedown。在点击注册之前测试








更新4



所以在收到@CoolBlue的建议后,我试着查看我已经设置的 d3.behavior.drag 。我已经尝试删除拖动行为的连线,这样做后我看不到任何问题 - 这可能表明存在问题。这旨在允许在力导向图内拖动圆。所以我在拖动中添加了一些记录,所以我可以关注最新情况:

  var drag = d3。 behavior.drag()
.on(dragstart,function(){console.log(dragstart); self .__ dragstart();})
.on(drag,function( d,x,y){console.log(drag,d3.event.sourceEvent.x,d3.event.sourceEvent.y); self .__ drag(d);})
.on(dragend ,function(d){console.log(dragend); self .__ dragend(d);});

我还指向 d3 代码

 返回函数(suppressClick){
console.log(supressClick =,suppressClick);
suppressClick = false;
w.on(name,null);
...
}

这样做后我仍然设法得到了失败,这引发了关于它是否真的是导致它的suppressClick标志的问题。这也可以通过更新#4解释控制台中的不一致。我还尝试在那里增加 setTimeout(off,0),这并没有阻止所有点击的触发,就像我期望的那样。



所以我认为这表明 suppressClick 可能实际上不是问题。这是一个控制台日志作为证据(我也有一个同事仔细检查,以确保我在这里没有遗漏任何东西):








更新6



我发现了另外一些可能与此问题相关的代码(但我不是100%肯定)。在我连接 d3.behavior.drag 的地方,我使用以下内容:

  var drag = d3.behavior.drag()
.on(dragstart,function(){self .__ dragstart();})
.on(drag,function(d) ){self .__ drag(d);})
.on(dragend,function(d){self .__ dragend(d);});

所以我一直在调查 self .__ dragstart() function并注意到 d3.event.sourceEvent.stopPropagation(); 。这些函数中没有更多(通常只是启动/停止力导向图和更新线的位置)。



我想知道这是否会影响点击行为。如果我把这个 stopPropagation 拿出去,那么我的整个表面开始平移,这是不可取的,所以这可能不是答案,但可能是另一种调查途径。






更新7



一个可能的瞪眼我忘记在原始问题中添加的排放量。可视化还支持缩放/平移。

  self .__ zoom = d3.behavior 
.zoom()
.scaleExtent([minZoom,maxZoom ])
.on(zoom,function(){self .__ zoom(d3.event.translate,d3.event.scale);});

现在要实现这一点,实际上在所有内容的顶部都有一个大矩形。所以我的顶级 svg 实际上看起来像:

 < svg class =galaxy> 
< g width =1080height =1795>
< rect class =zoomwidth =1080height =1795style =fill:none; pointer-events:all;>< / rect>
< g class =galaxy-backgroundwidth =1080height =1795transform =translate(-4,21)scale(1)>< / g>
< g class =galaxy-mainwidth =1080height =1795transform =translate(-4,21)scale(1)>
...所有圈子都在这里
< / g>
< / svg>

当我关闭 d3.event.sourceEvent.stopPropagation时,我记得这个(); d3.behaviour.drag 上的拖动事件的回调中。这阻止了任何点击事件进入我的圈子,这让我有些困惑,然后在检查DOM时我记得那个大矩形。我不太确定为什么重新启用传播会阻止此刻点击。

解决方案

我最近又遇到了这个问题。 ,幸运的是已设法隔离问题并解决它。



这实际上是由于在 mousedown event,它将基于z顺序的DOM元素 svg:circle 移动到顶部。它通过将DOM取出并在适当的位置重新插入它来实现这一点。



这会生成如下所示的内容:




  • mouseenter

  • mousedown


    • (移动DOM元素但是保持相同的事件包装器)

    • mouseup




问题是,就浏览器而言, mousedown mouseup 几乎出现在DOM,移动它搞砸了事件模型。



因此,在我的情况下,我通过触发点击 mousedown 在同一元素中发生,则在 mouseup 上手动执行$ c>事件。


If you're revisiting this question I've moved all the updates to the bottom so it actually reads better as a question.

The Problem

I've got a bit of a strange problem handling browser events using D3. Unfortunately this sits in quite a large application, and because I'm completely lost on what the cause is I'm struggling to find a small reproduceable example so I'm going to provide as much hopefully useful information as I can.

So my problem is that click events don't seem to fire reliably for certain DOM Elements. I have two different sets of elements Filled circles and White circles. You can see in the screenshot below 1002 and 1003 are white circles, while Suppliers is a filled circle.

Now this problem only occurs for the white circles which I don't understand. The screenshot below shows what happens when I click the circles. The order of clicks is shown via the red numbers, and the logging associated with them. Essentially what you see is:

  • mousedown
  • mouseup
  • sometimes a click

The issue is a bit sporadic. I had managed to track down a realiable reproduction but after a few refreshes of the browser it's now much harder to reproduce. If I alternate click on 1002 and 1003 then I keep getting mousedown and mouseup events but never a click. If I click on one of them a second time then I do get a click event. If I keep clicking on the same one (not shown here) only every other click fires the click event.

If I repeat the same process with a filled circle like Suppliers then it works fine and click is fired every single time.


How the Circles are created

So the circles (aka Planets in my code) have been created as a modular component. There for the data is looped through and an instance for each is created

data.enter()
    .append("g")
    .attr("class", function (d) { return d.promoted ? "collection moon-group" : "collection planet-group"; })
    .call(drag)
    .attr("transform", function (d) {
        var scale = d.size / 150;
        return "translate(" + [d.x, d.y] + ") scale(" + [scale] + ")";
    })
    .each(function (d) {

        // Create a new planet for each item
        d.planet = new d3.landscape.Planet()
                              .data(d, function () { return d.id; })
                              .append(this, d);
    });

This doesn't tell you all that much, underneath a Force Directed graph is being used to calculate positions. The code within the Planet.append() function is as follows:

d3.landscape.Planet.prototype.append = function (target) {
    var self = this;

    // Store the target for later
    self.__container = target;
    self.__events = new custom.d3.Events("planet")
                                    .on("click", function (d) { self.__setSelection(d, !d.selected); })
                                    .on("dblclick", function (d) { self.__setFocus(d, !d.focused); self.__setSelection(d, d.focused); });

    // Add the circles
    var circles = d3.select(target)
                    .append("circle")
                    .attr("data-name", function (d) { return d.name; })
                    .attr("class", function(d) { return d.promoted ? "moon" : "planet"; })
                    .attr("r", function () { return self.__animate ? 0 : self.__planetSize; })
                    .call(self.__events);

Here we can see the circles being appended (note each Planet is actually just a single circle). The custom.d3.Events is constructed and called for the circle that has just been added to the DOM. This code is used for both the filled and the white circles, the only difference is a slight variation in the classes. The DOM produced for each looks like:

Filled

<g class="collection planet-group" transform="translate(683.080338895066,497.948470463691) scale(0.6666666666666666,0.6666666666666666)">   
  <circle data-name="Suppliers" class="planet" r="150"></circle>
  <text class="title" dy=".35em" style="font-size: 63.1578947368421px;">Suppliers</text>   
</g>

White

<g class="collection moon-group" transform="translate(679.5720546510213,92.00957926233855) scale(0.6666666666666666,0.6666666666666666)">      
  <circle data-name="1002" class="moon" r="150"></circle>   
  <text class="title" dy=".35em" style="font-size: 75px;">1002</text>
</g>


What does custom.d3.events do?

The idea behind this is to provide a richer event system than you get by default. For example allowing double-clicks (that don't trigger single clicks) and long clicks etc.

When events is called with the circle container is executes the following, setting up some raw events using D3. These aren't the same ones that have been hooked up to in the Planet.append() function, because the events object exposes it's own custom dispatch. These are the events however that I'm using for debugging/logging;

custom.d3.Events = function () {

   var dispatch = d3.dispatch("click", "dblclick", "longclick", "mousedown", "mouseup", "mouseenter", "mouseleave", "mousemove", "drag");

   var events = function(g) {
       container = g;

       // Register the raw events required
       g.on("mousedown", mousedown)
        .on("mouseenter", mouseenter)
        .on("mouseleave", mouseleave)
        .on("click", clicked)
        .on("contextmenu", contextMenu)
        .on("dblclick", doubleClicked);

       return events;
   };

   // Return the bound events
   return d3.rebind(events, dispatch, "on");
}

So in here, I hook up to a few events. Looking at them in reverse order:

click

The click function is set to simply log the value that we're dealing with

 function clicked(d, i) {
    console.log("clicked", d3.event.srcElement);
    // don't really care what comes after
 }

mouseup

The mouseup function essentially logs, and clear up some global window objects, that will be discussed next.

 function mouseup(d, i) {
    console.log("mouseup", d3.event.srcElement);
    dispose_window_events();
 }

mousedown

The mousedown function is a little more complex and I'll include the entirety of it. It does a number of things:

  • Logs the mousedown to console
  • Sets up window events (wires up mousemove/mouseup on the window object) so mouseup can be fired even if the mouse is no longer within the circle that triggered mousedown
  • Finds the mouse position and calculates some thresholds
  • Sets up a timer to trigger a long click
  • Fires the mousedown dispatch that lives on the custom.d3.event object

    function mousedown(d, i) {
       console.log("mousedown", d3.event.srcElement);
    
       var context = this;
       dragging = true;
       mouseDown = true;
    
       // Wire up events on the window
       setup_window_events();
    
       // Record the initial position of the mouse down
       windowStartPosition = getWindowPosition();
       position = getPosition();
    
       // If two clicks happened far apart (but possibly quickly) then suppress the double click behaviour
       if (windowStartPosition && windowPosition) {
           var distance = mood.math.distanceBetween(windowPosition.x, windowPosition.y, windowStartPosition.x, windowStartPosition.y);
           supressDoubleClick = distance > moveThreshold;
       }
       windowPosition = windowStartPosition;
    
       // Set up the long press timer only if it has been subscribed to - because
       // we don't want to suppress normal clicks otherwise.
       if (events.on("longclick")) {
           longTimer = setTimeout(function () {
               longTimer = null;
               supressClick = true;
               dragging = false;
               dispatch.longclick.call(context, d, i, position);
           }, longClickTimeout);
       }
    
       // Trigger a mouse down event
       dispatch.mousedown.call(context, d, i);
       if(debug) { console.log(name + ": mousedown"); }
    }
    


Update 1

I should add that I have experienced this in Chrome, IE11 and Firefox (although this seems to be the most reliable of the browsers).

Unfortunately after some refresh and code change/revert I've struggled getting the reliable reproduction. What I have noticed however which is odd is that the following sequence can produce different results:

  • F5 Refresh the Browser
  • Click on 1002

Sometimes this triggeres mousedown, mouseup and then click. Othertimes it misses off the click. It seems quite strange that this issue can occur sporadically between two different loads of the same page.

I should also add that I've tried the following:

  • Caused mousedown to fail and verify that click still fires, to ensure a sporadic error in mousedown could not be causing the problem. I can confirm that click will fire event if there is an error in mousedown.
  • Tried to check for timing issues. I did this by inserting a long blocking loop in mousedown and can confirm that the mouseup and click events will fire after a considerable delay. So the events do look to be executing sequentially as you'd expect.

Update 2

A quick update after @CoolBlue's comment is that adding a namespace to my event handlers doesn't seem to make any difference. The following still experiences the problem sporadically:

var events = function(g) {
    container = g;

    // Register the raw events required
    g.on("mousedown.test", mousedown)
     .on("mouseenter.test", mouseenter)
     .on("mouseleave.test", mouseleave)
     .on("click.test", clicked)
     .on("contextmenu.test", contextMenu)
     .on("dblclick.test", doubleClicked);

    return events;
};

Also the css is something that I've not mentioned yet. The css should be similar between the two different types. The complete set is shown below, in particular the point-events are set to none just for the label in the middle of the circle. I've taken care to avoid clicking on that for some of my tests though and it doesn't seem to make much difference as far as I can tell.

/* Mixins */
/* Comment here */
.collection .planet {
  fill: #8bc34a;
  stroke: #ffffff;
  stroke-width: 2px;
  stroke-dasharray: 0;
  transition: stroke-width 0.25s;
  -webkit-transition: stroke-width 0.25s;
}
.collection .title {
  fill: #ffffff;
  text-anchor: middle;
  pointer-events: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-weight: normal;
}
.collection.related .planet {
  stroke-width: 10px;
}
.collection.focused .planet {
  stroke-width: 22px;
}
.collection.selected .planet {
  stroke-width: 22px;
}

.moon {
  fill: #ffffff;
  stroke: #8bc34a;
  stroke-width: 1px;
}
.moon-container .moon {
  transition: stroke-width 1s;
  -webkit-transition: stroke-width 1s;
}
.moon-container .moon:hover circle {
  stroke-width: 3px;
}
.moon-container text {
  fill: #8bc34a;
  text-anchor: middle;
}
.collection.moon-group .title {
  fill: #8bc34a;
  text-anchor: middle;
  pointer-events: none;
  font-weight: normal;
}
.collection.moon-group .moon {
  stroke-width: 3px;
  transition: stroke-width 0.25s;
  -webkit-transition: stroke-width 0.25s;
}
.collection.moon-group.related .moon {
  stroke-width: 10px;
}
.collection.moon-group.focused .moon {
  stroke-width: 22px;
}
.collection.moon-group.selected .moon {
  stroke-width: 22px;
}
.moon:hover {
  stroke-width: 3px;
}


Update 3

So I've tried ruling different things out. One is to change the CSS such that the white circles 1002 and 1003 now use the same class and therefore same CSS as Suppliers which is the one that worked. You can see the image and CSS below as proof:

<g class="collection planet-group" transform="translate(1132.9999823040162,517.9999865702812) scale(0.6666666666666666,0.6666666666666666)">
   <circle data-name="1003" class="planet" r="150"></circle>
   <text class="title" dy=".35em" style="font-size: 75px;">1003</text>
</g>

I also decided to modify the custom.d3.event code as this is the most complex bit of eventing. I stripped it right back down to simply just logging:

var events = function(g) {
    container = g;

    // Register the raw events required
    g.on("mousedown.test", function (d) { console.log("mousedown.test"); })
     .on("click.test", function (d) { console.log("click.test"); });

    return events;
};

Now it seems that this still didn't solve the problem. Below is a trace (now I'm not sure why I get two click.test events fired each time - appreciate if anyone can explain it... but for now taking that as the norm). What you can see is that on the ocassion highlighted, the click.test did not get logged, I had to click again - hence the double mousedown.test before the click was registered.


Update 4

So after a suggestion from @CoolBlue I tried looking into the d3.behavior.drag that I've got set up. I've tried removing the wireup of the drag behaviour and I can't see any issues after doing so - which could indicate a problem in there. This is designed to allow the circles to be dragged within a force directed graph. So I've added some logging in the drag so I can keep an eye on whats going on:

var drag = d3.behavior.drag()
             .on("dragstart", function () { console.log("dragstart"); self.__dragstart(); })
             .on("drag", function (d, x, y) { console.log("drag", d3.event.sourceEvent.x, d3.event.sourceEvent.y); self.__drag(d); })
             .on("dragend", function (d) { console.log("dragend"); self.__dragend(d); });

I was also pointed to the d3 code base for the drag event which has a suppressClick flag in there. So I modified this slightly to see if this was suppressing the click that I was expecting.

return function (suppressClick) {
     console.log("supressClick = ", suppressClick);
     w.on(name, null);
     ...
}

The results of this were a bit strange. I've merged all the logging together to illustrate 4 different examples:

  • Blue: The click fired correctly, I noted that suppressClick was false.
  • Red: The click didn't fire, it looks like I'd accidentally triggered a move but suppressClick was still false.
  • Yellow: The click did fire, suppressClick was still false but there was an accidental move. I don't know why this differs from the previous red one.
  • Green: I deliberately moved slightly when clicking, this set suppressClick to true and the click didn't fire.


Update 5

So looking in depth at the D3 code a bit more, I really can't explain the inconsistencies that I see in the behavior that I detailed in update 4. I just tried something different on the off-chance to see if it did what I expected. Basically I'm forcing D3 to never suppress the click. So in the drag event

return function (suppressClick) {
    console.log("supressClick = ", suppressClick);
    suppressClick = false;
    w.on(name, null);
    ...
}

After doing this I still managed to get a fail, which raises questions as to whether it really is the suppressClick flag that is causing it. This might also explain the inconsistencies in the console via update #4. I also tried upping the setTimeout(off, 0) in there and this didn't prevent all of the clicks from firing like I'd expect.

So I believe this suggests maybe the suppressClick isn't actually the problem. Here's a console log as proof (and I also had a colleague double check to ensure that I'm not missing anything here):


Update 6

I've found another bit of code that may well be relevant to this problem (but I'm not 100% sure). Where I hook up to the d3.behavior.drag I use the following:

 var drag = d3.behavior.drag()
             .on("dragstart", function () { self.__dragstart(); })
             .on("drag", function (d) { self.__drag(d); })
             .on("dragend", function (d) { self.__dragend(d); });

So I've just been looking into the self.__dragstart() function and noticed a d3.event.sourceEvent.stopPropagation();. There isn't much more in these functions (generally just starting/stopping the force directed graph and updating positions of lines).

I'm wondering if this could be influencing the click behavior. If I take this stopPropagation out then my whole surface begins to pan, which isn't desirable so that's probably not the answer, but could be another avenue to investigate.


Update 7

One possible glaring emissions that I forgot to add to the original question. The visualization also supports zooming/panning.

 self.__zoom = d3.behavior
                        .zoom()
                        .scaleExtent([minZoom, maxZoom])
                        .on("zoom", function () { self.__zoomed(d3.event.translate, d3.event.scale); });

Now to implement this there is actually a large rectangle over the top of everything. So my top level svg actually looks like:

<svg class="galaxy">
   <g width="1080" height="1795">
      <rect class="zoom" width="1080" height="1795" style="fill: none; pointer-events: all;"></rect>
   <g class="galaxy-background" width="1080" height="1795" transform="translate(-4,21)scale(1)"></g>
   <g class="galaxy-main" width="1080" height="1795" transform="translate(-4,21)scale(1)">
   ... all the circles are within here
   </g>
</svg>

I remembered this when I turned off the d3.event.sourceEvent.stopPropagation(); in the callback for the drag event on d3.behaviour.drag. This stopped any click events getting through to my circles which confused me somewhat, then I remembered the large rectangle when inspecting the DOM. I'm not quite sure why re-enabling the propagation prevents the click at the moment.

解决方案

I recently came across this again, and fortunately have managed to isolate the problem and work around it.

It was actually due to something being registered in the mousedown event, which was moving the DOM element svg:circle to the top based on a z-order. It does this by taking it out the DOM and re-inserting it at the appropriate place.

This produces something that flows like this:

  • mouseenter
  • mousedown
    • (move DOM element but keep same event wrapper)
    • mouseup

The problem is, as far as the browser is concerned the mousedown and mouseup occurred almost on different elements in the DOM, moving it has messed up the event model.

Therefore in my case I've applied a fix by firing the click event manually on mouseup if the original mousedown occured within the same element.

这篇关于为什么不点击事件总是火?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆