如何使 SVG 元素鼠标事件通过另一个元素冒泡?

Posted

技术标签:

【中文标题】如何使 SVG 元素鼠标事件通过另一个元素冒泡?【英文标题】:How do you make an SVG element mouse event bubble up through another element? 【发布时间】:2013-08-22 18:08:50 【问题描述】:

我有一个 D3 折线图,我在图表“后面”放置一个矩形。这个矩形附加了一个鼠标事件,但问题是我的图表还有另一个矩形覆盖在图表“上方”,该图表也附加了事件。

如何让较低的矩形鼠标事件在覆盖在顶部的较高矩形上方冒泡?非常感谢!

我在这里创建了一个小提琴:

http://jsfiddle.net/TnjCC/1/

这是我的代码。寻找“这是我需要鼠标悬停冒泡的地方”评论,看看我想冒泡哪个元素。

var data = [
  "date":"1-May-13","close":58.13,
  "date":"30-Apr-13","close":53.98,
  "date":"27-Apr-13","close":67.00,
  "date":"26-Apr-13","close":89.70,
  "date":"25-Apr-13","close":99.00,
  "date":"24-Apr-13","close":130.28,
  "date":"23-Apr-13","close":166.70,
  "date":"20-Apr-13","close":234.98,
  "date":"19-Apr-13","close":345.44,
  "date":"18-Apr-13","close":443.34,
];

var margin = top: 20, right: 50, bottom: 30, left: 50,
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var parseDate = d3.time.format("%d-%b-%y").parse,
    bisectDate = d3.bisector(function(d)  return d.date; ).left,
    formatValue = d3.format(",.2f"),
    formatCurrency = function(d)  return "$" + formatValue(d); ;

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .x(function(d)  return x(d.date); )
    .y(function(d)  return y(d.close); );

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  data.forEach(function(d) 
    d.date = parseDate(d.date);
    d.close = +d.close;

  data.sort(function(a, b) 
    return a.date - b.date;
  );

  x.domain([data[0].date, data[data.length - 1].date]);
  y.domain(d3.extent(data, function(d)  return d.close; ));

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Price ($)");

  <!-- This is where I need the mouseover to bubble up -->
  var left = x(new Date("Apr 23 2013"));
  var right = x(new Date("Apr 26 2013"));
  var wid = right - left;
  svg.append("rect")
      .attr("id", "range")
      .attr("class", "range")
      .attr("x", left)
      .attr("width", wid)
      .attr("height", height)
      .on("mouseover", function () 
          alert("I can see you!");
      )      

  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);

  var focus = svg.append("g")
      .attr("class", "focus")
      .style("display", "none");

  focus.append("circle")
      .attr("r", 4.5);

  focus.append("text")
      .attr("x", 9)
      .attr("dy", ".35em");

  svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function()  focus.style("display", null); )
      .on("mouseout", function()  focus.style("display", "none"); )
      .on("mousemove", mousemove);

  function mousemove() 
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectDate(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
    focus.select("text").text(formatCurrency(d.close));
  
);

【问题讨论】:

【参考方案1】:

您还可以使用以下样式,为鼠标事件“隐藏”某些 svg 元素。在我的例子中,我想通过mouseover 事件来冒泡:

pointer-events: none;

【讨论】:

好主意!我自己多次遇到这个问题,这是一种很好的简单方法。 迄今为止最简单、最优雅的解决方案。【参考方案2】:

为了快速修复,您可以将范围移到覆盖上方,并从该范围手动调用覆盖事件处理程序。

http://jsfiddle.net/Rk5Hp/

 svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function()  focus.style("display", null); )
      .on("mouseout", function()  focus.style("display", "none"); )
      .on("mousemove", mousemove);

  // move range above overlay and call the overlay event handlers from there
  svg.append("rect")
      .attr("id", "range")
      .attr("class", "range")
      .attr("x", left)
      .attr("width", wid)
      .attr("height", height)
      .on("mousemove", mousemove)
      .on("mouseout", function()  focus.style("display", "none"); )
      .on("mouseover", function() 
          focus.style("display", null);
          // event handling for range mouseover (alert broke mouse move)
          console.log("I can see you!");
      );

冒泡行为在 dom 级别,由于没有办法让一个 rect 成为另一个 rect 的子项,因此冒泡不会为您解决这个问题。将元素组合在一起并在组上放置一个检查事件目标的处理程序将使您无法注册事件处理程序两次,但会遇到相同的基本问题:当元素重叠时,无论在源顺序中最后声明的元素都将获得事件。

【讨论】:

【参考方案3】:

以上所有答案都是正确的,但我想再举一个例子:

    let log = console.log
let data = []
let pointStart = document.querySelector("svg").createSVGPoint()
let pointStop = document.querySelector("svg").createSVGPoint()
let divLog = d3.select("#log")
var svg = d3.select("svg")

var linearfn = d3.line()
  .x(d => d.x)
  .y(d => d.y)
  .curve(d3.curveLinear)

function logTagName(eventName, tagName) 
  divLog.html(divLog.html() + eventName + " : " + tagName + "<br/>")


svg.on("click", function() 
    log("tagName: ", event.target.tagName)
    logTagName("svg click", event.target.tagName)
    pointStart.x = event.x - 8
    pointStart.y = event.y - 8

    data.push(
      x: pointStart.x,
      y: pointStart.y
    )

    svg.selectAll("path") // SVG içinde tanımlı path elemanlarını bul
      .data([1]).enter() // 1 elemanlı dizinin eleman sayısı kadarı için enter()
      .append('path') // dizi elemanı kadar path oluştur
      .attr("fill", "none")
      .attr("stroke", "black")
      .attr("stroke-width", 8)
      .attr("d", linearfn(data))
      .on("click", function() 
        log("tagName: ", event.target.tagName)
        logTagName("path click", event.target.tagName)
        /* click event will start from path and pass to the svg element */
        // event.stopPropagation(); // letting pass event bubbling 
      )

    svg.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("cx", d => d.x + .5)
      .attr("cy", d => d.y + .5)
      .attr("r", 20)
      .attr("stroke-width", 3)
      .attr("stroke", "red")
      .attr("cursor", "move")
      .style("fill", "transparent")
      .attr("pointer-events", "all") // when clicked in/outside of circle, it'll handle events
      .on("mouseover", function() 
        log("over oldu")
        d3.select(this).style("stroke", "blue");
      )
      .on("mouseout", function() 
        log("out oldu")
        d3.select(this).style("stroke", "red");
      )
      .on("click", function() 
        event.stopPropagation(); // not letting pass event bubbling
        event.preventDefault();
        log("click oldu")
        d3.select(this).style("stroke", "black");
      )
  )
  .on("mousemove", function() 
    // fare hareketinde de çizdireceğiz ama x,y noktasını 
    // tıklanıncaya kadar diziye eklemeyeceğiz
    pointStop.x = event.x - 8
    pointStop.y = event.y - 8

    svg.select("path")
      .attr("d", linearfn(data.concat(
        x: pointStop.x,
        y: pointStop.y
      )))
  )

https://jsfiddle.net/jsfiddleCem/hnsu68jw/

【讨论】:

以上是关于如何使 SVG 元素鼠标事件通过另一个元素冒泡?的主要内容,如果未能解决你的问题,请参考以下文章

SVG 阻止触摸/鼠标滚轮事件

javaScript事件委托

在悬停另一个元素时更改 SVG 填充颜色

解决内部元素onMouseOver/onMouseOut事件冒泡触发父元素的相应事件

Javascript事件冒泡,没有想象中那么糟糕

canvas一段背景色鼠标移入后