3

I have been struggling for days about this but it seems that I will not solve this on my own. I hope someone can help...or just tell me it is not possible at all and I will find another way :)

Here is a simplified version of my problem:

.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>

When hovering over an ellipse, its opacity is modified. This is OK.

What I would like to achieve is when hovering over an intersection of two ellipses, the two defined :hover of the concerned ellipses are triggered. Currently, when the mouse pointer is over the red ellipse AND the blue ellipse (in the intersection), only the red one is concerned by the hover.

I cannot group them because:

  • All 3 ellipses will be considered as hovered all the time
  • The :hover effect differs

I thought the whole point of 'pointer-events' was to deal with multiple overlapping shapes at a time but I have been trying to use that property in every possible way, without success. I am using Reactjs so any possible hint on a Javascript solution would help.

2
  • This is very similar to stackoverflow.com/questions/29377443/… and I'd almost suggest this as a duplicate; however, this doesn't have to be solved cross-browser for all major browsers yet, as it doesn't work in Firefox yet. Commented Apr 29, 2019 at 15:55
  • Hey. Thanks for your comment @Connum. I did see this one and really hoped for a better (and cross-browser) way to achieve this since the answers are from 2015 :( The code snippets in the question are still not working in Firefox.
    – Adhonores
    Commented Apr 29, 2019 at 16:15

2 Answers 2

3

I love the solution @Connum came with but I think it can be simplified:

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
  • I tried this method on a larger SVG and it is working like a charm. Thank you so much to you and @Connum
    – Adhonores
    Commented May 2, 2019 at 13:09
1

Using getIntersectionList() as demonstrated in this very similar question is probably the cleanest and most performant solution. However, it is not yet supported by Firefox, so I came up with a solution based on a slightly adapted function taken from this answer to another question.

But caution: This is probably very performance-hungry due to the combination of the mousemove event with two forEach loops iterating over DOM elements, combined with the re-rendering that might be caused due to hiding/showing the elements for a minimal amount of time, depending on how the client will handle and optimize this. So this will possibly cause very poor performance on weaker devices. Having said that, it seems to work in all major browsers (tested in Firefox, Chrome and Edge; I haven't tried IE though).

In the comments to the answer in the second link I provided, there's a suggestion for another function using CSS' pointer-events instead of hiding the elements. One would have to compare the performance of those two approaches to decide which one to use best.

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>

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.