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:
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 CSS – pointer-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>
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>
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
Ciekawy ten pointer-events. Orientujesz sie czy dziala pod IE 10?
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.
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ą.
pod opera niestety nie dziala..
Ciekawe.