繁体   English   中英

为什么不点击事件总是火?

[英]Why doesn't click event always fire?

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

问题

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

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

在此输入图像描述

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

  • 鼠标按下
  • 鼠标松开
  • 有时点击

这个问题有点零星。 我曾设法追踪一个可靠的复制品,但经过浏览器的一些刷新后,它现在更难以重现。 如果我替换单击10021003然后我继续获得mousedownmouseup事件但从不click 如果我再次点击其中一个,那么我会收到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);

在这里我们可以看到附加的圆圈(注意每个星球实际上只是一个圆圈)。 构造并调用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对象公开了它自己的自定义调度。 这些是我用于调试/记录的事件;

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函数稍微复杂一点,我将包含它的全部内容。 它做了很多事情:

  • 将mousedown记录到控制台
  • 设置窗口事件(在窗口对象上连接mousemove / mouseup),这样即使鼠标不再在触发mousedown的圆圈内也可以触发mouseup
  • 查找鼠标位置并计算一些阈值
  • 设置计时器以触发长按
  • 触发生成在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"); } } 

更新1

我应该补充一点,我在Chrome,IE11和Firefox中经历过这种情况(虽然这似乎是最可靠的浏览器)。

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

  • F5刷新浏览器
  • 点击1002

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

我还要补充一点,我尝试过以下方法:

  • 导致mousedown失败并确认click仍然触发,以确保mousedown的零星错误无法导致问题。 如果mousedown有错误,我可以确认click会触发事件。
  • 试图检查时间问题。 我通过在mousedown插入一个长阻塞循环来做到这一点,并且可以确认mouseupclick事件会在相当长的延迟后触发。 所以事件看起来像你期望的那样顺序执行。

更新2

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

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圆圈10021003现在使用相同的类,因此与供应商相同的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突出显示, click.test没有被记录,我不得不再次点击 - 因此在mousedown.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); });

我还指向了拖拽事件d3代码库,其中有一个suppressClick标志。 所以我稍微修改了一下,看看这是否会抑制我期待的点击。

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

结果有点奇怪。 我已将所有日志记录合并在一起,以说明4个不同的示例:

  • 蓝色:点击正确,我注意到suppressClick为false。
  • 红色:点击没有触发,看起来我不小心触发了一次移动,但是suppressClick仍然是假的。
  • 黄色:点击确实触发, suppressClick仍然是假的但是有一个意外的移动。 我不知道为什么这与之前的红色不同。
  • 绿色:点击时我故意轻微移动,此设置suppressClick为true并且点击没有触发。

在此输入图像描述


更新5

所以深入研究D3代码,我真的无法解释我在更新4中详述的行为中看到的不一致。我只是尝试了一些不同的机会,看看它是否做了我预期。 基本上我强迫D3 永远不会抑制点击。 所以在拖动事件中

return function (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()函数并注意到了一个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();时,我记得这个d3.event.sourceEvent.stopPropagation(); d3.behaviour.drag上的drag事件的回调中。 这阻止了任何点击事件进入我的圈子,这让我有些困惑,然后在检查DOM时我记得那个大矩形。 我不太确定为什么重新启用传播会阻止此刻的点击。

我最近又遇到了这个问题,幸运的是,我已经成功地解决了这个问题并解决了这个问题。

这实际上是由于在mousedown事件中注册了一些东西,它正在基于z顺序将DOM元素svg:circle移动到顶部。 它通过将DOM取出并在适当的位置重新插入来完成此操作。

这产生了像这样流动的东西:

  • 的mouseenter
  • 鼠标按下
    • (移动DOM元素但保留相同的事件包装器)
    • 鼠标松开

问题是,就浏览器而言, mousedownmouseup几乎出现在DOM中的不同元素上,移动它已经弄乱了事件模型。

因此,在我的情况下,如果原始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;
};

返回g而不是事件可能会解决问题。

问题可能在于矩形图层,也可能与您调用事件抑制器的方式有关。 它可能只会导致您的keyup事件挂起而不是被取消,因此您的第一个按键事件会在第二次单击后返回。 您应该在事件上实现一个计数器来验证此理论,并使用日志中的事件名称返回计数器的值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM