Возможно ли сделать выбор элемента на странице при помощи мыши, если поверх неё есть 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.
Поискав решения в сети, я нашёл два варианта:
- Использовать CSS свойство pointer-events: none для контейнера и его содержимого.
- Заменить событие mouseover на mousemove и в обработчике скрывать контейнер через свойство visability: hidden, потом получать элемент по текущим координатам и снова возвращать контейнеру изначальное свойство visability.
К сожалению, эти два варианта мне не подходят. Первый по причине того, что все события и css эффекты на странице останутся доступными. Например при наведении на какой-то элемент у нас может изменится цвет или открыться tooltip, чего хотелось бы избежать.
Второй вариант продемонстрировал плохую производительность из-за большого количества срабатываний события mousemove. Отслеживание элемента под обёрткой и обновление extension_item вызывает задержки в случае быстрого перемещения курсора.
Я хотел бы узнать, возможно ли сделать подобный функционал без использования свойства pointer-events: none? Либо, если так нельзя, то оставить pointer-events: none, но предотвратить сторонние события и css эффекты. Буду рад любым предложениям и советам.
Ответы (1 шт):
Начнём с того, что у вас слегка недореализованы события и попытаемся одновременно их дополнить и исправить подход к решению задачи.
- Сразу и сходу, чтобы сократить страдания (мы вам не буддисты.) предлагаю во избежание всяких "ошибок" и условных исправлений с ними связанных воспользоваться кастомными тегами Custom Elements и назвать его (popup) как-нибудь не по "человечески", например —
<den-popup>
. Поверьте, но это не только сократит ваш код на избыточные проверки, но и позволит в будущем, если захотите делать куда больше. Но не будем пока упрощать? Тем более что это ничего не меняет. - куда более важное и главное, что вы упускаете, так это сам ваш "popup", его идентификацию и события с ним связанные.
a) вы не учитываете событие
mouseleave
, когда курсор уходит с экрана на панели и дальше. b) в связи с чем нам не хватает идентификатора для вашего popup, чтобы не пересоздавать его безо всякого смысла и отслеживать это безобразие. Я бы предложилchrome.runtime.id
который доступен и в контенте и в сервис-воркере. Но, опять же не будем мудрить, но на этот раз для демонстрации рабочего кода и назначим id="abracadabra", но если что, то в дальнейшем, в событиях обращаться по, например —chrome.runtime.id.classList.add('visible')
- Конкретно для этого примера я жёстко выставлю позицию 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", хотя и там есть небольшой нюанс, но с ним куда легче и понятнее.