Obsługa myszki

Do zdarzeń, które wywołuje kursor myszki zaliczamy:

  • mousedown - przycisk myszki został naciśnięty
  • mouseup - przycisk myszki został puszczony
  • click - przycisk myszki został naciśnięty i puszczony (czyli normalne kliknięcie)
  • dblclick - podwójne kliknięcie
  • mousemove - kursor porusza się po...
  • mouseover - kursor znalazł się na elemencie
  • mouseout - kursor opuścił dany element

Zdarzenia mousedown, mouseup, click

Zdarzenia te są związane z kliknięciem przyciskiem myszki na dany obiekt.

Istnieje pewna różnica miedzy click i mousedown. Przypuśćmy, że użytkownik nacisnął guzik na buttonie, następnie przesunął kursor poza ten button i puścił przycisk myszki. Button zarejestrował tylko zdarzenie mousedown. Podobnie - jeżeli użytkownik naciśnie guzik myszki poza buttonem, przesunie kursor na button i wtedy puści przycisk myszki, to button zarejestruje tylko zdarzenie mouseup.

W Windows dla większości przycisków stosowane jest zdarzenie click. Sprawdź sam - kliknij na którymś z guzików w prawym górnym rogu dowolnego okna (np. na zamykającym [X]), a następnie nie puszczając przycisku zjedź kursorem z tego guzika. Okno się nie zamknie, bo nie zaistniało zdarzenia click, tylko zdarzenie mousedown.

Ogólna składnia przypisania tych zdarzeń do obiektu jest następująca:


function showMe() {
    console.log(this);
}

document.addEventListener("DOMContentLoaded", function() {
    element.addEventListener('mousedown', showMe);
    element.addEventListener('mouseup', showMe);
    element.addEventListener('click', showMe);
});

Poniżej możesz sprawdzć opisane wyżej przypadki.

Zdarzenia mouseover, mousemove, mouseout

Zdarzenia mouseover, mousemove i mouseout służą do sprawdzenia, czy użytkownik najechał kursorem na element, czy się po nim porusza lub czy z kursorem opuścił dany element.


//włączamy mouseover
element.addEventListener('mouseover', showMe);

//wyłączamy
element.removeEventListener('mouseover', showMe);

//włączamy mousemove
element.addEventListener('mousemove', showMe);

//wyłączamy
element.removeEventListener('mousemove', showMe);


//włączamy mouseout
element.addEventListener('mouseout', showMe);

//wyłączamy
element.removeEventListener('mouseout', showMe);

Propagacja zdarzeń

Przypuśćmy, że mamy blok:

Kliknij

Wszystko standardowo. Po kliknięciu myszką na blok pokazuje się okienko alert().

Co jednak się stanie, gdy nasz blok będzie miał w sobie dwa inne bloki, a ty nie klikniesz bezpośrednio w blok 1 a 3?

Kliknij

<div style="..." id="blok">
    1
    <div style="...">
        2
        <div style="...">
            3
        </div>
    </div>
</div>

document.querySelector('#blok').addEventListener('click', function() {
    alert('Kliknąłeś mnie!')
});

Okazuje się, że zdarzenie przypisane do 1-go bloku odpalane jest też, gdy klikniemy myszką na blok nr 2 i 3 (mimo tego, że bloki te nie mają przypisanego zdarzenia).

Czemu tak się dzieje?

Wszystko za sprawą propagacji zdarzeń, które jest stosowane dla wszystkich zdarzeń (click, mouseover, mousemove itd.).

Zdarzenia zachowują się tak samo jak bąbelki, które wypływają z ust ryby i lecą ku górze. W naszym przypadku zdarzenie leci od naszego elementu w górę dokumentu aż dojdzie do najwyższego elementu czyli window.

Po kliknięciu na blok nr 3, zdarzenie click będzie wędrować w stronę document, i tym samym spotka na swej drodze blok nr 2 a zaraz potem blok nr 1, który ma przypisane zdarzenie click. Tak samo po kliknięciu na blok nr 2, zdarzenie click wędruje do obiektów położonych wyżej (z 2 na 1, następnie do body, i do document). Blok 1 ma przypisane zdarzenie więc je odpala.

Gdyby każdy z bloków miał przypisane zdarzenie click, wówczas po kliknięciu na blok 3, odpalane były by wszystkie zdarzenia!


function stopBubble(e) {
    e.cancelBubble = true;
    e.stopPropagation();
}

Należy zauważyć, że wyłączenie bąbelków nie może odbywać się ogólnie (czyli np. w funkcji load dokumentu). Możemy je wyłączyć tylko lokalnie dla danego zdarzenia:


document.querySelector('#blok31').addEventListener('click', function() {
    alert('Kliknąłeś mnie!')
});

document.querySelector('#blok32').addEventListener('click', function(e) {
    stopBubble(e);
});

document.querySelector('#blok33').addEventListener('click', function(e) {
    stopBubble(e);
});

właściwość target

Javascript udostępnia nam dla zdarzenia właściwość target, która wskazuje nam obiekt, który odpalił to zdarzenie.

Jestem divem
e.target.innerText:

Co możemy zrobić z taką informacją? Na przykład wykorzystać ją do pozyskania cennych informacji na temat danego obiektu - chociażby jaki to znacznik, jaka jest jego szerokość itp:


e.taget.nodeName; //wskaże nam typ znacznika
e.taget.style.width; //szerokość ustawiona w stylach
Ale uwaga! Nie każde style możemy pobrać za pomocą właściwości style. Tutaj mamy dostęp tylko do styli, które są wstawione za pomocą JS, lub są wpisane inline jako atrybut style danego elementu. Żeby pobierać style bezpośrednio z css, musimy skorzystać z getComputedStyle:

    const elem1 = document.querySelector("#elemId");
    getComputedStyle(elem1).getPropertyValue("margin-top");
    
Odpalający:

Wykrywanie skąd przybył kursor

No dobrze, ale skąd mamy wiedzieć, z którego bloku wjechał kursor na blok nr 2? Czy z bloku nr 1 czy z 3?

Dla zdarzeń mouseover i mouseout istnieje właściwość relatedTarget, która mówi nam z jakiego elementu myszka przybyła (dla zdarzenia mouseover) i na jaki element zdąża (dla zdarzenia mouseout).

Jeżeli chcielibyśmy odczytać obiekt z którego kursor przybył (dla zdarzenia mouseover) skorzystamy z funkcji:


function targetFrom(e) {
    if (e.relatedTarget) {
        return e.relatedTarget;
    } else if (e.fromElement) {
        return e.fromElement;
    }
}

Jeżeli chcielibyśmy odczytać obiekt, na który kursor myszki podąża (dla zdarzenia mouseout) skorzysytamy z poniższej funkcji:


function targetTo(e) {
    if (e.relatedTarget) {
        return e.relatedTarget;
    } else if (e.toElement) {
        return e.toElement;
    }
}
zjechałeś kursorem z:
najechałeś kursorem na:

Powyższy przykład odpala te funkcje tylko dla czerwonego diva

Pozycja kursora

Aby pobrać pozycję kursora na stronie możemy skorzystać z 2 par właściwości:

e.pageX Zwraca pozycję kursora od lewej krawędzi strony (wraz z przewinięciem)
e.pageY Zwraca pozycję kursora od górnej krawędzi strony (wraz z przewinięciem)
e.clientX
Zwraca pozycję kursora od górnej krawędzi widocznego obszaru strony (bez uwzględnienia pozycji przewinięcia strony)
e.clientY Zwraca pozycję kursora od lewej krawędzi widocznego obszaru strony (bez uwzględnienia pozycji przewinięcia strony)

Właściwości e.pageX i e.pageY zwracają realną pozycję kursora od początku strony, dlatego idealnie się nadają do "przyklejania" elementów do kursora - np jakiś dymków, menusów itp.

Właściwości e.clientX i e.clientY zwracają pozycję kursora od krawędzi widocznego obszaru strony (tego który aktualnie widać w oknie przeglądarki), dlatego używając ich powinniśmy do nich doliczać właściwości document.body.scrollLeft i document.body.scrollTop, ponieważ strona może być przewinięta.

Pozycja myszki

element.addEventListener('mousemove', function(e) {
    console.log('Pozycja myszki:');
    console.log('x: ', e.pageX);
    console.log('y: ', e.pageY);
});

//inny przykład

element.addEventListener('click', function() {
    console.log('Pozycja myszki:');
    console.log('x: ', e.clientX + document.body.scrollLeft);
    console.log('y: ', e.clientY + document.body.scrollTop);
});
Najedź by pokazać pozycję kursora

Który przycisk myszki

Aby odczytać, który przycisk został naciśnięty skorzystamy z właściwości button.

Guziki myszki zostały obdarowane odpowiednimi wartościami:

  • lewy guzik - 0
  • środkowy guzik - 1
  • prawy guzik - 2
Kliknij RMB by pokazać który klawisz został naciśnięty

function buttonMouse(e) {
    e.preventDefault();

    alert('Numer kliknietego przycisku: '+ e.button);
}

const block = document.querySelector('#blockBtn');
block.addEventListener('mousedown', buttonMouse);

Menu kontekstowe

Zbierzmy kilka powyższych informacji, i zróbmy własne kontekstowe menu.

Kliknij prawym by pokazać menu kontekstowe

Nasze menu to zwykła lista UL pozycjonowana absolutnie. Domyślnie wysuwamy ją totalnie za ekran ustawiając top i left na -9999:


<ul class="menu-context">
    <li><a href="">Element menu 1</a></li>
    <li><a href="">Element menu 2</a></li>
    <li><a href="">Element menu 3</a></li>
    <li><a href="">Element menu 4</a></li>
</ul>

.menu-context {
    position: absolute;
    left:-9999px;
    top:-9999px;
    background: #222;
    color:#fff;
    list-style:none;
    padding:0;
    margin:0;
    width:300px;
    padding:10px;
    border-radius: 4px;
    box-shadow:0 2px 4px rgba(0,0,0,0.3);
    font-size:13px;
}
.menu-context li {
    border-bottom:1px solid #444;
    cursor: pointer;
}
.menu-context li a {
    color:#fff;
    text-decoration: none;
    padding:5px 10px;
    display: block;
    transition:0.5s all;
}
.menu-context li a:hover {
    background: #F15C5C;
    color:#fff;
}
.menu-context li:last-child {
    border:0;
}

Dodajemy pokazywanie naszego menu po kliknięciu prawym przyciskiem na div:


const menu = document.querySelector('.menu-context');

function rightButton(e) {
    e.preventDefault();

    if (e.button === 2) {
        e.stopPropagation();
        menu.style.left = e.pageX + 10 + 'px';
        menu.style.top = e.pageY + 10 + 'px';
    }
}

document.querySelector('#blockRightBtn').addEventListener('mousedown', rightButton);

Jeżeli teraz klikniemy prawym przyciskiem myszy na div, faktycznie nasze menu się pojawi, ale też pojawi się domyślne menu.
Żeby domyślne menu się nie pokazywało, powinniśmy użyć pewnie e.preventDefault().
I faktycznie - tyle tylko, że musimy zatrzymać event contextmenu:


document.querySelector('#blockRightBtn').addEventListener('contextmenu', function(e) {
    e.preventDefault();
});

Ostatnią rzeczą jaką musimy zrobić to możliwość schowania menu poprzez kliknięcie lewym przyciskiem myszy w dowolne miejsce na stronie i podczas scrollu (bo tak - przynajmniej u mnie zachowuje się to klasyczne menu):


const menu = document.querySelector('.menu-context');

function removeMenu() {
    menu.style.left = '-9999px';
    menu.style.top = '-9999px';
}

function rightButton(e) {
    ...
}

const block = document.querySelector('#blockRightBtn');
block.addEventListener('mousedown', rightButton);
block.addEventListener('contextmenu', function(e) {
    e.preventDefault();
});
document.addEventListener('mousedown', removeMenu);
document.addEventListener('scroll', removeMenu);

Powyższe rozwiązanie jest prostym przykładem. W większych projektach prawdopodobnie o wiele lepiej sprawdzi się gotowy plugin np. https://github.com/electerious/basicContext