Подтвердить что ты не робот

Почему событие click всегда не срабатывает?

Если вы пересматриваете этот вопрос, я переместил все обновления снизу, чтобы он действительно лучше читался в качестве вопроса.

Проблема

У меня есть немного странная проблема с обработкой событий браузера с помощью D3. К сожалению, это сидит в довольно большом приложении, и потому что я полностью потерял свою причину, я изо всех сил пытаюсь найти небольшой воспроизводимый пример, поэтому я собираюсь предоставить как можно больше полезной информации, как я могу.

Итак, моя проблема заключается в том, что события click, похоже, не срабатывают надежно для определенных элементов DOM. У меня есть два разных набора элементов: Заполненные круги и Белые круги. Вы можете видеть на скриншоте ниже 1002 и 1003 белые круги, а поставщики - заполненный круг.

enter image description here

Теперь эта проблема только возникает для белых кругов, которые я не понимаю. Снимок экрана ниже показывает, что происходит, когда я нажимаю круги. Порядок кликов отображается с помощью красных цифр и связанного с ними журнала. По существу, вы видите:

  • MouseDown
  • MouseUp
  • иногда щелчок

Проблема немного спорадическая. Мне удалось отследить реалистичное воспроизведение, но после нескольких обновлений браузера теперь гораздо сложнее воспроизвести. Если я чередую клик на 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);

Здесь мы видим, что добавляемые круги (обратите внимание, что каждая Планета на самом деле представляет собой только один круг). Custom.d3.Events сконструирован и вызван для круга, который только что был добавлен в DOM. Этот код используется как для заполненных, так и для белых кругов, единственное отличие - небольшое изменение в классах. DOM, созданный для каждого, выглядит следующим образом:

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>

Белый

<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, выполняется следующее: настройка некоторых событий raw с использованием D3. Это не те, которые были подключены к функции 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");
}

Итак, здесь я подключаюсь к нескольким событиям. Взглянув на них в обратном порядке:

нажмите

Функция щелчка устанавливается так, чтобы просто регистрировать значение, с которым мы имеем дело с

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

MouseUp

Функция mouseup по существу регистрирует и очищает некоторые глобальные объекты окна, которые будут обсуждаться далее.

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

MouseDown

Функция mousedown немного сложнее, и я буду включать ее полностью. Он делает несколько вещей:

  • Запускает консоль для консоли
  • Устанавливает события окна (прокладывает мышью/мышь на объект окна), поэтому мышь может быть запущена, даже если мышь больше не находится в круге, вызвавшем mousedown
  • Находит позицию мыши и вычисляет некоторые пороговые значения
  • Устанавливает таймер, чтобы вызвать длинный клик
  • Запускает отправку mousedown, которая живет на объекте custom.d3.event.

    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. В других случаях он пропускает click. Кажется довольно странным, что эта проблема может возникать спорадически между двумя разными нагрузками одной и той же страницы.

Я также должен добавить, что я пробовал следующее:

  • Вызвал mousedown для отказа и проверки того, что click по-прежнему срабатывает, чтобы гарантировать, что спорадическая ошибка в mousedown не может вызвать проблему. Я могу подтвердить, что click запустит событие, если в mousedown есть ошибка.
  • Пытался проверить сроки. Я сделал это, вставив длинный цикл блокировки в mousedown и подтверждая, что события mouseup и click будут срабатывать после значительной задержки. Таким образом, события действительно выглядят последовательно, как и следовало ожидать.

Обновление 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 1002 и 1003 теперь используют один и тот же класс и, следовательно, тот же CSS, что и Поставщики, который работал. Вы можете увидеть изображение и CSS ниже как доказательство:

enter image description here

<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, так как это самый сложный бит eventing. Я разделил его прямо вниз, чтобы просто просто зарегистрировать:

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, которые запускаются каждый раз, - оцените, если кто-нибудь может это объяснить... но пока это воспринимается как норма). То, что вы видите, это то, что при выделенном ocassion click.test не регистрировался, я должен был снова щелкнуть - следовательно, двойной mousedown.test до того, как был зарегистрирован клик.

enter image description here


Обновление 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 был ложным.
  • Красный: щелчок не срабатывал, похоже, что я случайно вызвал движение, но suppressClick был все еще ложным.
  • Желтый: щелчок стрелял, suppressClick был все еще ложным, но произошел случайный ход. Я не знаю, почему это отличается от предыдущего красного.
  • Зеленый: я намеренно слегка переместился, щелкнув этот набор suppressClick на true, и щелчок не загорелся.

enter image description here


Обновление 5

Таким образом, углубляясь в код D3 немного больше, я действительно не могу объяснить несоответствия, которые я вижу в поведении, которое я подробно описал в обновлении 4. Я просто попробовал что-то другое, вне возможности увидеть если бы он сделал то, что я ожидал. В основном я заставляю D3 никогда подавлять щелчок. Итак, в перетащить событие

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

После этого мне все же удалось получить сбой, что вызывает вопросы о том, действительно ли это флаг suppressClick, который вызывает его. Это также может объяснить несоответствия в консоли с помощью обновления # 4. Я также попытался подняться setTimeout(off, 0) там, и это не помешало всем щелчкам стрелять, как я ожидал.

Поэтому я считаю, что это говорит о том, что suppressClick на самом деле не проблема. Здесь консольный журнал как доказательство (и у меня также была двойная проверка коллеги, чтобы убедиться, что здесь ничего не пропало):

enter image description here


Обновить 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. Я не совсем уверен, почему повторное включение распространения предотвращает нажатие на данный момент.

4b9b3361

Ответ 1

Недавно я снова наткнулся на это, и, к счастью, удалось изолировать проблему и обойти ее.

Это было связано с тем, что что-то было зарегистрировано в событии mousedown, которое перемещало элемент DOM svg:circle в начало в зависимости от z-порядка. Он делает это, вынимая DOM и повторно вставляя его в нужное место.

Это создает что-то, что происходит так:

  • MouseEnter
  • MouseDown
    • (перемещайте элемент DOM, но сохраняйте ту же самую оболочку событий)
    • MouseUp

Проблема заключается в том, что в отношении браузера mousedown и mouseup происходит почти на разных элементах DOM, перемещая его, он испортил модель события.

Поэтому в моем случае я применил исправление, активировав событие click вручную на mouseup, если исходный mousedown произошел внутри одного и того же элемента.

Ответ 2

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 вместо событий может решить проблему.

Ответ 3

Проблема может быть связана с слоем прямоугольника или с тем, как вы вызываете свой супрессор событий. Это может привести к тому, что ваше событие keyup будет зависать, а не отменено, так что ваше первое ключевое событие возвращается после второго щелчка. Вы должны реализовать счетчик событий, чтобы проверить эту теорию и вернуть значение счетчика с именем события в журнале.