Как сделать эффект: hover для двух перекрывающихся фигур в SVG? - PullRequest
2 голосов
/ 29 апреля 2019

Я боролся с этим несколько дней, но, похоже, я не решу это самостоятельно.Я надеюсь, что кто-то может помочь ... или просто сказать мне, что это вообще невозможно, и я найду другой способ:)

Вот упрощенная версия моей проблемы:

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

При наведении на эллипс его непрозрачность изменяется.Это нормально.

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

Я не могу сгруппировать их, потому что:

  • Все 3 эллипса будут постоянно зависать
  • Эффект: hover отличается

Я думал, что весь смысл 'событий-указателей' заключается в том, чтобы справиться с множественным перекрытиемформы, но я пытался использовать это свойство всеми возможными способами, но безуспешно.Я использую Reactjs, так что любой возможный намек на решение Javascript поможет.

Ответы [ 2 ]

2 голосов
/ 29 апреля 2019

Мне нравится решение @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 голос
/ 29 апреля 2019

Используя getIntersectionList(), как показано в , этот очень похожий вопрос , вероятно, является самым чистым и наиболее эффективным решением.Однако он еще не поддерживается Firefox, поэтому я предложил решение, основанное на слегка адаптированной функции, взятой из этого ответа на другой вопрос .

Но осторожно: это, вероятно, оченьснижение производительности из-за комбинации события mousemove с двумя циклами forEach, повторяющимися по элементам DOM, в сочетании с повторным рендерингом, который может быть вызван скрытием / отображением элементов в течение минимального промежутка времени, в зависимости откак клиент будет обрабатывать и оптимизировать это.Так что это может привести к очень низкой производительности на слабых устройствах.Сказав это, похоже, он работает во всех основных браузерах (протестировано в 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...