Niechciane zdarzenia myszki a CSS pointer-events

Bardzo często zdarza się – szczególnie podczas pracy nad wszelakiej maści drag and dropami w JavaScript – że warstwa przykrywająca jakiś element blokuje wykonywanie się na nim eventów myszki. Np. chcesz odpalić mousedown na divie, który jest „przykryty” przez inny, pozycjonowany absolutnie. Jak poradzić sobie z tym problemem?

Po pierwsze, rozważmy o czym mówimy. Powiedzmy, że mamy dwa elementy <div> nałożone na siebie.

<!DOCTYPE html>
<html lang="pl">
	<head>
		<title>Pointer events</title>
		<style>
			.layer1,
			.layer2 {
				height: 100px;
				width: 100px;
				position: absolute;
			}
			.layer1 {
				background: blue;
				top: 10px;
				left: 20px;
			}
			.layer2 {
				opacity: 0.7;
				background: yellow;
				left: 40px;
				top: 30px;
			}
		</style>
	</head>
	<body>
		<div class="layer1"></div>
		<div class="layer2"></div>
	</body>
</html>

Wygląda to tak:

Chcemy, aby zdarzenie mousedown, przypisane do niebieskiego diva, było odpalane również po naciśnięciu przycisku myszki na żółtym divie. Naturalnie, najpierw powinniśmy dodać jakiekolwiek tego typu zdarzenie poprzez JavaScript. Do dzieła:

<body>
	<div class="layer1"></div>
	<div class="layer2"></div>
	<script>
		document.querySelectorAll('.layer1')[0].addEventListener('mousedown', function() {
			alert("onmousedown!");
		}, false);
	</script>
</body>

Sprawdźmy, czy to działa:

mousedown
mousedown

Wszystko jest ok – to znaczy naciśnięcie przycisku myszki na żółtym elemencie div nie odpala funkcji z alert("onmousedown!");. Jak zrobić, by również kliknięcie w obszarze żóltego diva odpaliło zdarzenie przypisane dla .layer1? Na szczęście z pomocą przychodzi nam właściwość w CSSpointer-events!

Pomijając SVG, pointer-events może przyjąć wartości auto oraz none. Domyślnie ustawione jest oczywiście auto. Zobaczmy, co się stanie, kiedy użyję wartości none na elemencie .layer2:

<style>
	.layer2 {
		pointer-events: none;
	}
</style>
mousedown
mousedown

Wszystko znakomicie działa! Okazuje się, że ustawienie pointer-events na none sprawia, że element staje się w „niewidoczny” dla zdarzeń myszki, automatycznie przekazując dany event do elementu „pod” nim.

Idąc tym tropem, można również ustawić linki na nieklikalne!

<style>
	a {
		pointer-events: none;
	}
</style>
<a href="http://ferrante.pl" style="pointer-events: none;">strona główna</a>

strona główna

Podany wyżej link nie powoduje przejścia do strony głównej tego bloga.

Warto dodać, że pointer-events nie działają pod IE oraz Operą.

Obejściem może być podobna funkcja w JS (poniższy skrypt wymaga zaimplementowania funkcji addEvent, która dodawałaby zdarzenia):

var noPointerEvents = function(element, events) {
	for (var i = 0, ilen = events.length; i < ilen; i++) {
		(function(eventName) {
			addEvent(element, eventName, function (e) {
				e = e || window.event;
				var el = e.target || e.srcElement;
				el.style.display = 'none';
				var under = document.elementFromPoint(e.clientX, e.clientY);
				el.style.display = '';
				if (e.stopPropagation) {
					e.stopPropagation();
				} else {
					e.cancelBubble = true;
				}
				if (e.preventDefault) {
					e.preventDefault();
				} else {
					e.returnValue = false;
				}
				if (document.createEventObject) {
					// IE
					return under.fireEvent('on' + eventName, document.createEventObject());
				}
				else {
					// dobre przeglądarki
					var htmlEvents = document.createEvent("HTMLEvents");
					htmlEvents.initEvent(eventName, true, true); // event type, bubbling, cancelable
					return !under.dispatchEvent(htmlEvents);
				}
			});
		})(events[i]);
	}
};
noPointerEvents(document.querySelectorAll('.layer2')[0], ['mousedown']);

noPointerEvents dodaje podane w drugim argumencie eventy (u nas tylko mousedown, można rozszerzyć to dalej o reszte eventów myszki) do elementu z pierwszego argumentu. Następnie dzięki document.elementFromPoint pobieramy najwyższy, jeśli chodzi o widoczność, element o danych koordynatach. Dzięki temu możemy wywołać na nim zdarzenie, które niechybnie zostało wywołane na elemencie niepożądanym, "zakrywającym" właściwy. Warto dodać, że starsze wersje Opery i Safari wymagały podania e.pageX i e.pageY.

Miłego użycia pointers-events!

Komentarze

1

Ciekawy ten pointer-events. Orientujesz sie czy dziala pod IE 10?

2

Wojtek – http://caniuse.com/#search=pointer-events – nie wiadomo. Niby do IE9 nie, ale różne bajery pozapowiadali do 10, więc nie zdziwiłbym się, gdyby i to się pojawiło.

3

Ciekawe. Nigdy nic o tej właściwości nie słyszałem.
Może być przydatna kiedy chcemy zrobić delegację mouseenter i mouseleave. Obydwa eventy się nie bubblują, więc o delegacji mowy być nie może, ale wykorzystując tę właściwość można by czasami podpiąć zwykły mouseover i mouseout i zablokować inne eventy wewnątrz bloków, które nas interesują.

4

pod opera niestety nie dziala..

michal
5

Ciekawe.

wrb
6

Dzięki !

Dodaj komentarz

Dozwolone tagi: <blockquote>, <code>, <strong>