[英]Why doesn't click event always fire?
If you're revisiting this question I've moved all the updates to the bottom so it actually reads better as a question. 如果您正在重新审视此问题,我已将所有更新移至底部,因此它实际上更好地作为一个问题。
I've got a bit of a strange problem handling browser events using D3
. 我使用
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. 所以我的问题是
click
事件似乎不能为某些DOM元素可靠地触发。 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.
您可以在下面的屏幕截图中看到1002和1003是白色圆圈,而供应商是一个填充圆圈。
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:
基本上你看到的是:
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
. 如果我替换单击1002和1003然后我继续获得
mousedown
和mouseup
事件但从不click
。 If I click on one of them a second time then I do get a click
event. 如果我再次点击其中一个,那么我会收到
click
事件。 If I keep clicking on the same one (not shown here) only every other click fires the click
event. 如果我一直点击同一个(此处未显示),则只有每次点击都会触发
click
事件。
If I repeat the same process with a filled circle like Suppliers then it works fine and click
is fired every single time. 如果我像供应商那样用填充的圆圈重复相同的过程,那么它工作正常并且每次
click
都会被click
。
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. 这并没有告诉你那么多,在
Force Directed
图表下方用于计算位置。 The code within the Planet.append()
function is as follows: Planet.append()
函数中的代码如下:
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.
构造并调用custom.d3.Events用于刚刚添加到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:
为每个产生的DOM看起来像:
<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>
<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>
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. 当使用
circle
容器调用事件时,执行以下操作,使用D3设置一些raw
事件。 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. 这些与
Planet.append()
函数中的连接不同,因为events
对象公开了它自己的自定义调度。 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:
以相反的顺序查看它们:
The click function is set to simply log the value that we're dealing with click函数设置为只记录我们正在处理的值
function clicked(d, i) {
console.log("clicked", d3.event.srcElement);
// don't really care what comes after
}
The mouseup function essentially logs, and clear up some global window objects, that will be discussed next. mouseup函数基本上记录并清除一些全局窗口对象,这将在下面讨论。
function mouseup(d, i) {
console.log("mouseup", d3.event.srcElement);
dispose_window_events();
}
The mousedown function is a little more complex and I'll include the entirety of it. mousedown函数稍微复杂一点,我将包含它的全部内容。 It does a number of things:
它做了很多事情:
Fires the mousedown dispatch that lives on the custom.d3.event object 触发生成在custom.d3.event对象上的mousedown分派
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 更新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). 我应该补充一点,我在Chrome,IE11和Firefox中经历过这种情况(虽然这似乎是最可靠的浏览器)。
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:
我注意到的是奇怪的是,以下序列可以产生不同的结果:
1002
1002
Sometimes this triggeres mousedown
, mouseup
and then click
. 有时候这个
mousedown
mouseup
,然后click
。 Othertimes it misses off the click
. Othertimes它错过了
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: 我还要补充一点,我尝试过以下方法:
mousedown
to fail and verify that click
still fires, to ensure a sporadic error in mousedown
could not be causing the problem. mousedown
失败并确认click
仍然触发,以确保mousedown
的零星错误无法导致问题。 I can confirm that click
will fire event if there is an error in mousedown
. mousedown
有错误,我可以确认click
会触发事件。 mousedown
and can confirm that the mouseup
and click
events will fire after a considerable delay. mousedown
插入一个长阻塞循环来做到这一点,并且可以确认mouseup
和click
事件会在相当长的延迟后触发。 So the events do look to be executing sequentially as you'd expect. Update 2 更新2
A quick update after @CoolBlue's comment is that adding a namespace to my event handlers doesn't seem to make any difference. @ CoolBlue评论之后的快速更新是,为我的事件处理程序添加命名空间似乎没有任何区别。 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. css
也是我尚未提及的东西。 The css should be similar between the two different types. 两种不同类型的CSS应该相似。 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. 完整集如下所示,特别是
point-events
仅针对圆圈中间的标签设置为none
。 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 更新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. 一种是改变CSS,使得
white
圆圈1002和1003现在使用相同的类,因此与供应商相同的CSS是有效的。 You can see the image and CSS below as proof: 您可以在下面看到图像和CSS作为证据:
<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. 我还决定修改
custom.d3.event
代码,因为这是最复杂的事件。 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).
下面是一个跟踪(现在我不知道为什么我每次都会发出两次click.test事件 - 如果有人能够解释它,那就明白了......但现在把它作为常态)。 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. 你可以看到,在
click.test
突出显示, click.test
没有被记录,我不得不再次点击 - 因此在mousedown.test
之前双mousedown.test
。
Update 4 更新4
So after a suggestion from @CoolBlue I tried looking into the d3.behavior.drag
that I've got set up. 所以在得到@CoolBlue的建议后,我试着查看我已经设置的
d3.behavior.drag
。 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. 我还指向了拖拽事件的
d3
代码库,其中有一个suppressClick
标志。 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:
我已将所有日志记录合并在一起,以说明4个不同的示例:
suppressClick
was false. suppressClick
为false。 suppressClick
was still false. suppressClick
仍然是假的。 suppressClick
was still false but there was an accidental move. suppressClick
仍然是假的但是有一个意外的移动。 I don't know why this differs from the previous red one. suppressClick
to true and the click didn't fire. suppressClick
为true并且点击没有触发。 Update 5 更新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. 所以深入研究
D3
代码,我真的无法解释我在更新4中详述的行为中看到的不一致。我只是尝试了一些不同的机会,看看它是否做了我预期。 Basically I'm forcing D3
to never suppress the click. 基本上我强迫
D3
永远不会抑制点击。 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. 在这样做之后,我仍然设法失败,这引发了关于它是否真的是导致它的suppressClick标志的问题。 This might also explain the inconsistencies in the console via update #4.
这也可以通过更新#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. 我也尝试在那里增加
setTimeout(off, 0)
,这并没有阻止所有的点击像我期望的那样发射。
So I believe this suggests maybe the suppressClick
isn't actually the problem. 所以我相信这表明
suppressClick
实际上可能不是问题所在。 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 更新6
I've found another bit of code that may well be relevant to this problem (but I'm not 100% sure). 我发现了另一些可能与这个问题相关的代码(但我不是100%肯定)。 Where I hook up to the
d3.behavior.drag
I use the following: 我连接到
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); });
So I've just been looking into the self.__dragstart()
function and noticed a d3.event.sourceEvent.stopPropagation();
所以我一直在研究
self.__dragstart()
函数并注意到了一个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. 如果我把这个停止
stopPropagation
,那么我的整个表面开始平移,这是不可取的,所以这可能不是答案,但可能是另一个调查的途径。
Update 7 更新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
实际上看起来像:
<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();
当我关闭
d3.event.sourceEvent.stopPropagation();
时,我记得这个d3.event.sourceEvent.stopPropagation();
in the callback for the drag
event on d3.behaviour.drag
. 在
d3.behaviour.drag
上的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. 这阻止了任何点击事件进入我的圈子,这让我有些困惑,然后在检查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. 这实际上是由于在
mousedown
事件中注册了一些东西,它正在基于z顺序将DOM元素svg:circle
移动到顶部。 It does this by taking it out the DOM and re-inserting it at the appropriate place. 它通过将DOM取出并在适当的位置重新插入来完成此操作。
This produces something that flows like this: 这产生了像这样流动的东西:
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. 问题是,就浏览器而言,
mousedown
和mouseup
几乎出现在DOM中的不同元素上,移动它已经弄乱了事件模型。
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. 因此,在我的情况下,如果原始
mousedown
在同一元素中发生,我通过在mouseup
手动触发click
事件来应用修复。
var events = function(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 g;
};
Returning g instead of events might resolve the problem. 返回g而不是事件可能会解决问题。
The problem could be with the rectangle layer too or with the way you call your event suppressor. 问题可能在于矩形图层,也可能与您调用事件抑制器的方式有关。 It may simply cause your keyup event to hang instead of being cancelled so your first key up event returns after the second click.
它可能只会导致您的keyup事件挂起而不是被取消,因此您的第一个按键事件会在第二次单击后返回。 You should implement a counter on your events to verify this theory and return the value of the counter with the event name in the log.
您应该在事件上实现一个计数器来验证此理论,并使用日志中的事件名称返回计数器的值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.