如果您正在重新讨论此问题,我已将所有更新移至底部,因此它实际上是个更好的问题。
问题
使用D3
处理浏览器事件时,我遇到了一个奇怪的问题。不幸的是,这存在于一个很大的应用程序中,并且由于我完全不知道原因是什么,所以我一直在努力寻找一个小的可复制示例,因此我将尽可能多地提供有用的信息。
所以我的问题是,对于某些DOM元素,click
事件似乎无法可靠地触发。我有两组不同的元素填充的圈子和白色圈子。您可以在下面的屏幕快照中看到1002和1003是白色圆圈,而Suppliers是实心圆圈。
现在这个问题只有出现在我不了解的白色圆圈上。下面的屏幕截图显示了当我单击圆圈时会发生什么。点击顺序通过红色数字显示,并与它们相关联的日志记录。本质上,您看到的是:
这个问题有点零星。我设法找到了可实现的再现,但是在刷新浏览器几次之后,现在很难再现了。如果我交替单击1002和1003,那么我会不断收到
mousedown
和mouseup
事件,但从未收到click
。如果我第二次单击其中一个,则会收到click
事件。如果我继续单击同一按钮(此处未显示),则仅每隔一次单击就会触发click
事件。如果我用供应商这样的实心圆圈重复相同的过程,那么它将正常工作,并且每次都触发
click
。如何创建圆
因此,圆(在我的代码中也称为行星)已被创建为模块化组件。在那里循环数据并为每个数据创建一个实例
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);
});
这并不能告诉您太多信息,在
Force Directed
图的下方用于计算位置。 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);
在这里,我们可以看到附加的圆圈(请注意,每个Planet实际上只是一个圆圈)。 custom.d3.Events的构造和调用是针对刚刚添加到DOM中的圆。该代码用于实心圆和白色圆,唯一的区别是类中的细微差异。为每个对象生成的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>
custom.d3.events有什么作用?
其背后的想法是提供比默认情况下更丰富的事件系统。例如,允许双击(不会触发单击)和长按等。
当使用
circle
容器调用事件时,将执行以下操作,并使用D3设置一些raw
事件。这些与Planet.append()
函数中连接的对象不同,因为events
对象公开了它自己的自定义分派(dispatch)。这些是我用于调试/记录的事件;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");
}
因此,在这里,我将介绍一些事件。以相反的顺序查看它们:
点击
将click函数设置为仅记录我们正在处理的值
function clicked(d, i) {
console.log("clicked", d3.event.srcElement);
// don't really care what comes after
}
鼠标向上
mouseup函数实际上记录并清除了一些全局窗口对象,这将在下面讨论。
function mouseup(d, i) {
console.log("mouseup", d3.event.srcElement);
dispose_window_events();
}
按下鼠标
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"); }
}
更新1
我应该补充一点,我在Chrome,IE11和Firefox中已经经历过这一过程(尽管这似乎是最可靠的浏览器)。
不幸的是,经过一些刷新和代码更改/恢复后,我一直在努力获得可靠的再现。但是我发现奇怪的是,以下顺序可以产生不同的结果:
1002
有时,这会触发
mousedown
,mouseup
和click
。有时它会错过click
。在同一页面的两个不同负载之间偶尔会发生此问题,这似乎很奇怪。我还应该补充说,我已经尝试了以下方法:
mousedown
失败,并验证click
是否仍然触发,以确保mousedown
中的偶发错误不会引起此问题。如果click
中有错误,我可以确认mousedown
将触发事件。 mousedown
中插入一个长的阻塞循环来完成此操作的,并且可以确认mouseup
和click
事件将在相当长的延迟后触发。因此,事件确实确实按预期执行。 更新2
@CoolBlue发表评论后的快速更新是,向我的事件处理程序添加 namespace 似乎没有任何区别。以下仍然偶尔遇到此问题:
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;
};
另外,
css
是我尚未提到的东西。两种不同类型之间的CSS应该相似。完整的集合如下所示,尤其是仅将圆圈中间的标签的point-events
设置为none
。我已经避免在某些测试中单击它,但据我所知,它似乎并没有太大区别。/* 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;
}
更新3
因此,我尝试排除其他问题。一种是更改CSS,以使
white
圈子1002和1003现在使用与Suppliers相同的类,因此使用与CSS相同的CSS,而CSS是起作用的类。您可以看到下面的图像和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>
我还决定修改
custom.d3.event
代码,因为这是事件处理中最复杂的部分。我将其剥离回去只是简单地记录: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;
};
现在看来这仍然无法解决问题。下面是一个痕迹(现在我不确定为什么每次都会触发两个click.test事件-如果有人能解释它,请欣赏……但是现在将其作为规范)。您会看到,在突出显示的位置上,没有记录
click.test
,我不得不再次单击-因此在单击之前注册了两次mousedown.test
。更新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); });
我还指出了drag event的
d3
代码库,其中有suppressClick
标志。因此,我对此做了些微修改,以查看这是否抑制了我期望的点击次数。return function (suppressClick) {
console.log("supressClick = ", suppressClick);
w.on(name, null);
...
}
这样的结果有点奇怪。我将所有日志记录合并在一起,以说明4个不同的示例:
suppressClick
是错误的。 suppressClick
仍然为假。 suppressClick
仍然为假,但有意外 Action 。我不知道为什么这不同于以前的红色。 suppressClick
设置为true,但单击没有触发。 更新5
因此,再深入研究
D3
代码,我真的无法解释我在更新4中详述的行为中看到的不一致之处。我只是尝试了一些不同的尝试,以查看它是否做了我所希望的预期的。基本上,我是在将D3
强制设为,而从禁止取消点击。所以在drag eventreturn function (suppressClick) {
console.log("supressClick = ", suppressClick);
suppressClick = false;
w.on(name, null);
...
}
完成此操作后,我仍然设法失败了,这引发了一个问题,即是否确实是引起它的preventClick标志。这也可能通过更新#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()
函数,并注意到了d3.event.sourceEvent.stopPropagation();
。这些功能没有更多的东西(通常只是开始/停止力导向图和更新线的位置)。我想知道这是否会影响点击行为。如果我取出此
stopPropagation
,则我的整个表面开始平移,这是不希望的,因此这可能不是答案,但可能是另一种调查途径。更新7
我忘记添加到原始问题中的一种可能的明显排放。可视化还支持缩放/平移。
self.__zoom = d3.behavior
.zoom()
.scaleExtent([minZoom, maxZoom])
.on("zoom", function () { self.__zoomed(d3.event.translate, d3.event.scale); });
现在要实现此目的,实际上所有内容的顶部都有一个大矩形。所以我的顶层
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>
当我在
d3.event.sourceEvent.stopPropagation();
上的drag
事件的回调中关闭d3.behaviour.drag
时,我记得这一点。这阻止了进入我的圈子的任何单击事件,这使我有些困惑,然后我在检查DOM时记得那个大矩形。我不太确定为什么现在重新启用传播会阻止点击。
最佳答案
我最近又遇到了这个问题,幸运的是,我们设法隔离了问题并解决了问题。
实际上,这是由于mousedown
事件中注册了某些内容,该事件将DOM元素svg:circle
根据z顺序移至顶部。它通过将其取出DOM并在适当的位置重新插入来实现。
这将产生如下所示的内容:
问题在于,就浏览器而言,
mousedown
和mouseup
几乎发生在DOM中的不同元素上,移动它已使事件模型困惑。因此,在我的情况下,如果原始
click
发生在同一元素中,我就通过手动在mouseup
上触发mousedown
事件来应用此修复程序。
关于javascript - 为什么点击事件不总是触发?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31243959/