如何在SVG中的两个重叠形状上产生:hover效果?

时间:2019-04-29 15:50:50

标签: javascript css svg

我为此一直苦苦挣扎了几天,但看来我不会自己解决这个问题。我希望有人能帮忙...或者只是告诉我这根本不可能,我会找到另一种方式:)

这是我的问题的简化版本:

.left {
  fill: yellow;
  pointer-events: visible;
}

.left:hover {
  opacity: 0.3;
}

.middle {
  fill: red;
  pointer-events: visible;
}

.middle:hover {
  opacity: 0.8;
  pointer-events: visible;
}

.right {
  fill: blue;
}

.right:hover {
  opacity: 0.6;
  pointer-events: visible;
}
 <svg class="test" width="500px" height="500px">
   <g name="Layer" class="group">
     <ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
     <ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
     <ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
   </g>
</svg>

将鼠标悬停在椭圆上时,其不透明度会被修改。可以。

我要实现的是,将鼠标悬停在两个椭圆的交点上时,将触发相关的两个已定义的:hover。当前,当鼠标指针悬停在红色椭圆和蓝色椭圆(在相交处)上方时,悬停仅关注红色椭圆。

我无法将它们分组,因为:

  • 所有3个椭圆将一直被认为是悬停的
  • :悬停效果不同

我认为“指针事件”的重点是一次处理多个重叠的形状,但是我一直试图以各种可能的方式使用该属性,但没有成功。 我正在使用Reactjs,因此有关Javascript解决方案的任何可能提示都会有所帮助。

2 个答案:

答案 0 :(得分:2)

我喜欢@Connum附带的解决方案,但我认为它可以简化:

let ellipses = document.querySelectorAll("ellipse")

function getAllElementsFromPoint(rootEl, x, y) { 
  var item = document.elementFromPoint(x, y); 
  //in this case is tagName == "ellipse" but you can find something else in commun, like a class - for example.
  while (item && item.tagName == "ellipse") {
    item.classList.add("hover")
    item.style.pointerEvents = "none";
    item = document.elementFromPoint(x, y);
  }
}

var svg = document.querySelector('svg.test');
svg.addEventListener('mousemove', function(ev) {
  // first add pointer-events:all and remove the class .hover from all elements 
  ellipses.forEach(e=> {
    e.style.pointerEvents = "all";
    e.classList.remove('hover');
  });
  // then get all elements at the mouse position
  // and add the class "hover" to them
  getAllElementsFromPoint(svg, ev.clientX, ev.clientY)
  });
.left {
  fill: yellow;
}

.left.hover {
  opacity: 0.3;
}

.middle {
  fill: red;
}

.middle.hover {
  opacity: 0.8;
}

.right {
  fill: blue;
}

.right.hover {
  opacity: 0.6;
}

svg {
  border: 1px solid;
}
<svg class="test" width="500px" height="500px">
   <g name="Layer" class="group">
     <ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
     <ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
     <ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
   </g>
</svg>

答案 1 :(得分:1)

使用this very similar question中演示的getIntersectionList()可能是最干净,性能最高的解决方案。但是,Firefox尚不支持它,因此我提出了一个基于this answer to another question的稍微改编的功能的解决方案。

但是请注意:由于mousemove事件与两个在DOM元素上迭代的forEach循环的组合,加上可能由于以下原因而导致的重新渲染,这可能会非常耗费性能在最短的时间内隐藏/显示元素,具体取决于客户端如何处理和优化它。因此,这可能会在较弱的设备上导致非常差的性能。话虽如此,它似乎可以在所有主流浏览器中运行(在Firefox,Chrome和Edge中进行了测试;不过我没有尝试过IE)。

在我提供的第二个链接的答案注释中,建议使用CSS'pointer-events而不是隐藏元素来实现另一个功能。必须将这两种方法的性能进行比较,才能确定哪种方法最合适。

function getAllElementsFromPoint(rootEl, x, y) {
  var elements = [];
  var display = [];
  var item = document.elementFromPoint(x, y);
  while (item && item !== document.body && item !== window && item !== document && item !== document.documentElement && item !== rootEl) {
    elements.push(item);
    display.push(item.style.display);
    item.style.display = "none";
    item = document.elementFromPoint(x, y);
  }
  // restore display property
  for (var i = 0; i < elements.length; i++) {
    elements[i].style.display = display[i];
  }
  return elements;
}

var svg = document.querySelector('svg.test');
svg.addEventListener('mousemove', function(ev) {
  // first remove the class .hover from all elements
  svg.querySelectorAll('*').forEach(function(subEl) {
    subEl.classList.remove('hover');
  });
  // then get all elements at the mouse position
  // and add the class "hover" to them
  getAllElementsFromPoint(svg, ev.clientX, ev.clientY).forEach(function(hoveredEl) {
    hoveredEl.classList.add('hover');
  })
});
.left {
  fill: yellow;
  pointer-events: visible;
}

.left:hover,
.left.hover {
  opacity: 0.3;
}

.middle {
  fill: red;
  pointer-events: visible;
}

.middle:hover,
.middle.hover {
  opacity: 0.8;
  pointer-events: visible;
}

.right {
  fill: blue;
}

.right:hover,
.right.hover {
  opacity: 0.6;
  pointer-events: visible;
}
<svg class="test" width="500px" height="500px">
   <g name="Layer" class="group">
     <ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
     <ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
     <ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
   </g>
</svg>