Захват событий наведения мыши на два перекрывающихся элемента - PullRequest
2 голосов
/ 27 октября 2019

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

let chartWindow = svg
  .append("g");

/* this holds axis groups, and cadlestick group*/
let candleStickWindow = chartWindow.append("g")
  //this event never fires
  .on('mousemove', ()=>console.log('mouse move'));

let candlesCrosshairWindow = chartWindow
  .append("rect")
  .attr("class", "overlay")

  .attr("height", innerHeight)
  .attr("width", innerWidth)
  .on("mouseover", function() {
    crosshair.style("display", null);
  })
  .on("mouseout", function() {
    crosshair.style("display", "none");
    removeAllAxisAnnotations();
  })
  .on("mousemove", mousemove);

CrosshairWindow имеет свойство CSSpointer-events: all. Если я уберу это, я получу мои события на candleStickWindow, но не на CrosshairWindow. Как я могу получить события мыши на оба элемента ??

Спасибо за любую помощь!

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

enter image description here

1 Ответ

1 голос
/ 28 октября 2019

Одно решение, которое приходит на ум, может использовать всплывающее сообщение , которое, однако, работает, только если события могут всплыть вдоль одного и того же поддерева DOM. Если в вашей структуре DOM прямоугольник перекрестия и другие элементы не имеют общего предка, к которому вы могли бы разумно присоединить такого слушателя, вам нужно либо переосмыслить свой DOM, либо прибегнуть к какому-либо другому решению. Для этого ответа я выложу альтернативный подход, который более применим.

Вы можете разместить свой полноразмерный rect в самом низу вашего SVG и установить для pointer-events значение all. ,Таким образом, вы можете легко прикрепить обработчик mousemove к нему, чтобы контролировать движения перекрестия, охватывающие весь видовой экран. Однако, как вы уже заметили, это не работает, если есть элементы выше, к которым прикреплены прослушиватели для этого конкретного типа события. Потому что в этом случае, как только событие достигнет своей цели, нет способа распространить его дальше к нижележащему прямоугольнику для обработки компонента перекрестия. Обойти это легко, поскольку вы можете клонировать событие и отправить его прямо в прямоугольник.

Клонирование события выполняется с помощью конструктора MouseEvent()в сведениях о событии из ссылки d3.event:

new MouseEvent(d3.event.type, d3.event)

Затем вы можете отправить вновь созданный объект события в элемент перекрестия rect, используя метод .dispatchEvent()интерфейс EventTarget, который реализован с помощью SVGRectElement:

.dispatchEvent(new MouseEvent(d3.event.type, d3.event));

Из-за отсутствия полного примера в вашем вопросе я сам создал рабочую демонстрациюиллюстрирующий подход. Вы можете перетащить синий кружок, который является развернутой версией компонента перекрестия. Обратите внимание, как круг можно плавно перемещать, даже если он находится под оранжевыми прямоугольниками. Чтобы продемонстрировать обработчики событий, прикрепленные к этим маленьким прямоугольникам, они будут переходить в зеленый и обратно в оранжевый при вводе или оставлении их с помощью указателя мыши.

const width = 500;
const height = 500;
const radius = 10;
const orange = d3.hsl("orange");
const steelblue = d3.hsl("steelblue");
const limegreen = d3.hsl("limegreen");

const svg = d3.select("body")
  .append("svg")
    .attr("width", width)
    .attr("height", height);
    
const target = svg.append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height)
    .attr("fill", "none")
    .attr("pointer-events", "all")
    .on("mousemove", () => {
      circle.attr("cx", d3.event.clientX - radius);
      circle.attr("cy", d3.event.clientY - radius);
    });

const circle = svg.append("circle")
    .attr("r", radius)
    .attr("fill", steelblue)
    .attr("pointer-events", "none");
    
const rect = svg.selectAll(null)
  .data(d3.range(3).map(d => [Math.random() * width, Math.random() * height]))
  .enter().append("rect")
    .attr("x", d => d[0])
    .attr("y", d => d[1])
    .attr("width", 50)
    .attr("height", 50)
    .attr("fill", orange)
    .attr("opacity", 0.5)
    .on("mouseover", function() { 
      d3.select(this).transition().attr("fill", limegreen);
    })
    .on("mousemove", function() { 
      target.node().dispatchEvent(new MouseEvent(d3.event.type, d3.event));
    })
    .on("mouseout", function() { 
      d3.select(this).transition().attr("fill", orange);
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
...