【问题标题】:How to make an :hover effect on two overlapping shapes in SVG?如何在 SVG 中的两个重叠形状上制作:悬停效果?
【发布时间】:2019-04-30 03:05:17
【问题描述】:

我已经为此苦苦挣扎了好几天,但似乎我不会自己解决这个问题。我希望有人可以提供帮助......或者只是告诉我这根本不可能,我会找到另一种方法:)

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

.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 个椭圆都将被视为一直悬停
  • :hover 效果不同

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

【问题讨论】:

  • 这与stackoverflow.com/questions/29377443/… 非常相似,我几乎建议这是重复的;但是,这还不是所有主流浏览器都必须跨浏览器解决的问题,因为它还不能在 Firefox 中运行。
  • 嘿。感谢您的评论@Connum。我确实看到了这个,并且真的希望有一种更好(和跨浏览器)的方法来实现这一点,因为答案来自 2015 年 :( 问题中的代码 sn-ps 在 Firefox 中仍然不起作用。

标签: javascript css svg


【解决方案1】:

我喜欢 @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>

【讨论】:

  • 我在一个更大的 SVG 上尝试了这种方法,它的工作原理非常棒。非常感谢你和@Connum
【解决方案2】:

this very similar question 所示,使用getIntersectionList() 可能是最干净和最高效的解决方案。但是,Firefox 尚不支持它,所以我想出了一个基于从this answer to another question 获取的稍微改编的函数的解决方案。

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

在我提供的第二个链接的答案的 cmets 中,建议使用 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>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-28
    • 1970-01-01
    • 2014-03-26
    • 1970-01-01
    • 2013-10-15
    • 1970-01-01
    相关资源
    最近更新 更多