Возможно ли сделать выбор элемента на странице при помощи мыши, если поверх неё есть div обёртка?

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

введите сюда описание изображения

Изучив информацию в сети, я пришёл к следующему варианту реализации. После нажатия на иконку расширения, будет происходить включение функционала, а перемещая курсор по странице подсвечиваться активный элемент.

Получается есть базовая структура любой страницы и контейнер с блоком подсветки, который я буду добавлять через расширение.

HTML

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <header class="header">...</header>
    <main class="content">...</main>
    <footer class="footer">...</footer>

    <div class="extension_container"> // будет добавлено через скрипт
      <div class="extension_item"></div>
    </div>
  </body>
</html>

Browser extension - content.css

.extension_container {
  position: absolute;
  z-index: 1000000;
}

.extension_item {
  background-color: green;
  opacity: .5;
}

Browser extension - content.js

const container = document.createElement("div");
const item = document.createElement("div");

container.classList.add("extension_container");
item.classList.add("extension_item");

container.append(item);
document.body.append(container);

document.addEventListener("mouseover", e => {
  selectElement(e.target); // вычисляем размер и позицию элемента, после чего обновляем стили extension_item
});

document.addEventListener("mouseout", e => {
  unselectElement(e.target); // скрываем extension_item (сбрасываем размер и позицию)
});

Тут я сталкиваюсь с проблемой того, что контейнер перекрывает страницу и не позволяет получать элементы в событии mouseover.

введите сюда описание изображения

Поискав решения в сети, я нашёл два варианта:

  1. Использовать CSS свойство pointer-events: none для контейнера и его содержимого.
  2. Заменить событие mouseover на mousemove и в обработчике скрывать контейнер через свойство visability: hidden, потом получать элемент по текущим координатам и снова возвращать контейнеру изначальное свойство visability.

К сожалению, эти два варианта мне не подходят. Первый по причине того, что все события и css эффекты на странице останутся доступными. Например при наведении на какой-то элемент у нас может изменится цвет или открыться tooltip, чего хотелось бы избежать.

введите сюда описание изображения

Второй вариант продемонстрировал плохую производительность из-за большого количества срабатываний события mousemove. Отслеживание элемента под обёрткой и обновление extension_item вызывает задержки в случае быстрого перемещения курсора.

Я хотел бы узнать, возможно ли сделать подобный функционал без использования свойства pointer-events: none? Либо, если так нельзя, то оставить pointer-events: none, но предотвратить сторонние события и css эффекты. Буду рад любым предложениям и советам.


Ответы (1 шт):

Автор решения: stylok

Начнём с того, что у вас слегка недореализованы события и попытаемся одновременно их дополнить и исправить подход к решению задачи.

  1. Сразу и сходу, чтобы сократить страдания (мы вам не буддисты.) предлагаю во избежание всяких "ошибок" и условных исправлений с ними связанных воспользоваться кастомными тегами Custom Elements и назвать его (popup) как-нибудь не по "человечески", например — <den-popup>. Поверьте, но это не только сократит ваш код на избыточные проверки, но и позволит в будущем, если захотите делать куда больше. Но не будем пока упрощать? Тем более что это ничего не меняет.
  2. куда более важное и главное, что вы упускаете, так это сам ваш "popup", его идентификацию и события с ним связанные. a) вы не учитываете событие mouseleave, когда курсор уходит с экрана на панели и дальше. b) в связи с чем нам не хватает идентификатора для вашего popup, чтобы не пересоздавать его безо всякого смысла и отслеживать это безобразие. Я бы предложил chrome.runtime.id который доступен и в контенте и в сервис-воркере. Но, опять же не будем мудрить, но на этот раз для демонстрации рабочего кода и назначим id="abracadabra", но если что, то в дальнейшем, в событиях обращаться по, например — chrome.runtime.id.classList.add('visible')
  3. Конкретно для этого примера я жёстко выставлю позицию popup-а на нули к левому верхнему углу, но вам, насколько я понял их понадобится каждый раз менять относительно объекта. Разберётесь.) Поехали?

let container = document.createElement('div'); // 'den-popup'
container.id = 'abracadabra'; // = chrome.runtime.id
document.body.append(container);

document.addEventListener('mouseover', e => {
  abracadabra.style.top = 0;
  abracadabra.style.left = 0;
  abracadabra.textContent = e.target.clientWidth + 'x' + e.target.clientHeight;
  abracadabra.classList.add('visible');
});

document.addEventListener('mouseout', e => {
  abracadabra.classList.remove('visible');
  abracadabra.removeAttribute('style');
});

document.addEventListener('mouseleave', e => {
  abracadabra.classList.remove('visible');
  abracadabra.removeAttribute('style');
});
#abracadabra {
  display: none;
}
#abracadabra.visible {
  display: block;
  position: absolute;
  border: 1px solid red;
  background-color: gold;
  z-index: 1000000;
}

/* немного "красоты" для наглядности */
* {
  margin: 0;
}

header {
  background-color: #dfd8b1;
}
nav {
  display: flex;
  gap: 4px;
}
a:first-child {
  background-color: #ffabab;
}
a:last-child {
  background-color: #98ff7c;
}
main {
  background-color: #efefef;
}
h1 {
  width: 70vw;
  background-color: #bce1ff;
}
p {
   background-color: #dfc3ff;
   width: 50%;
   height: 100px;
}
<header>
  <nav>
      <a href="#a">ПОСТОРОННИМ</a>
      <a href="#b">В.</a>
  </nav>
</header>
<main>
  <h1>Заголовок</h1>
  <p>Текст</p>
</main>

P.S. Может я где-то что-нибудь и недосмотрел, но в целом это более правильный подход к решению задачи. И, даже если вдруг ваш popup окажется в центре событий, что по идее не должно произойти при правильном подходе, то вы всегда можете добавить проверку if (e.target.id !== chrome.runtime.id) {...} или что-нибудь подобное. В текущем представлении кода — if (e.target.id !== abracadabra.id) {...}

[нельзя учесть всего того, что вы не договорили. Например? Этот содержимое должно быть доступно для выделения текста и копирования]

Однако в данном конкретном моём примере вы увидите 0x0 и уже знаете на что провериться, чтобы избежать накладки: оберните все действия на события в условие исключающее ваш "popup", хотя и там есть небольшой нюанс, но с ним куда легче и понятнее.

→ Ссылка