Wskazywanie drogi naprzód

Sérgio Gomes

Wskazywanie rzeczy w internecie było kiedyś proste. Mieliśmy mysz, którą można było przesuwać, czasami naciskać przyciski, i to wszystko. Wszystko, co nie było myszką, było emulowane jako mysz, a deweloperzy wiedzieli dokładnie, na co mogą liczyć.

Prostota nie musi jednak oznaczać jakości. Z czasem coraz większą rolę zaczęły odgrywać urządzenia inne niż mysz: pióra reagujące na nacisk i przechylanie, które zapewniały niesamowitą swobodę twórczą; urządzenia, które można obsługiwać palcami, więc do pracy wystarczyło urządzenie i ręka; a dodatkowo, czemu nie używać więcej niż jednego palca?

Od jakiegoś czasu mamy zdarzenia dotykowe, które pomagają nam w tym zakresie, ale są to zupełnie osobne interfejsy API, przeznaczone do obsługi dotyku, co zmusza Cię do tworzenia 2 oddzielnych modeli zdarzeń, jeśli chcesz obsługiwać zarówno mysz, jak i dotykowe ekrany. Chrome 55 zawiera nowszy standard, który łączy oba modele: zdarzenia wskaźnika.

Model pojedynczego zdarzenia

Zdarzenia związane z wskaźnikiem unifikują model danych wejściowych związanego ze wskaźnikiem w przeglądarce, łącząc w jeden zestaw zdarzeń dane z dotyku, piór i myszy. Na przykład:

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

Oto lista wszystkich dostępnych zdarzeń, która powinna wyglądać znajomo, jeśli znasz zdarzenia myszy:

pointerover Wskaźnik wszedł w pole ograniczające elementu. Dzieje się tak natychmiast na urządzeniach obsługujących najechanie kursorem, a przed zdarzeniempointerdown na urządzeniach, które tej funkcji nie mają.
pointerenter Podobnie jak reguła pointerover, ale nie przenosi wartości do góry i różnie od niej obsługuje potomków. Szczegóły specyfikacji
pointerdown Wskaźnik wszedł w stan aktywnego przycisku, w którym albo został naciśnięty przycisk, albo nawiązano kontakt, w zależności od semantyki urządzenia wejściowego.
pointermove Wskaźnik zmienił pozycję.
pointerup Wskaźnik opuścił stan aktywnego przycisku.
pointercancel Coś się stało, co oznacza, że wskaźnik prawdopodobnie nie wyemituje już żadnych zdarzeń. Oznacza to, że należy anulować wszystkie trwające działania i wrócić do neutralnego stanu.
pointerout wskaźnik opuścił ramkę elementu lub ekranu; Również po pointerup, jeśli urządzenie nie obsługuje najechania kursorem.
pointerleave Podobnie jak reguła pointerout, ale nie przenosi wartości do góry i różnie od niej obsługuje potomków. Szczegóły specyfikacji
gotpointercapture Element otrzymał przechwytywanie kursora.
lostpointercapture Wskaźnik, który był przechwycony, został zwolniony.

Różne typy danych wejściowych

Ogólnie zdarzenia związane z wskaźnikiem umożliwiają pisanie kodu w sposób niezależny od sposobu wprowadzania danych, bez konieczności rejestrowania oddzielnych metod obsługi zdarzeń dla różnych urządzeń wejściowych. Nadal musisz jednak pamiętać o różnicach między typami danych wejściowych, np. o tym, czy ma zastosowanie koncepcja najechania kursorem. Jeśli chcesz odróżnić różne typy urządzeń wejściowych (np. aby zapewnić osobny kod lub funkcję dla różnych urządzeń), możesz to zrobić w ramach tych samych metod obsługi zdarzeń, korzystając z właściwości pointerType interfejsu PointerEvent. Jeśli np. kodujesz boczny panel nawigacyjny, możesz użyć tej logiki w przypadku zdarzenia pointermove:

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

Działania domyślne

W przeglądarkach z ekranem dotykowym do przewijania, powiększania i odświeżania strony służą określone gesty. W przypadku zdarzeń dotykowych nadal będziesz otrzymywać zdarzenia, gdy będą wykonywane te domyślne działania – np. zdarzenie touchmove będzie nadal wywoływane, gdy użytkownik będzie przewijać.

W przypadku zdarzeń wskaźnika, gdy zostanie wywołane domyślne działanie, takie jak przewijanie lub powiększanie, otrzymasz zdarzenie pointercancel, aby poinformować, że przeglądarka przejęła kontrolę nad kursorem. Na przykład:

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

Wbudowana szybkość: ten model zapewnia domyślnie lepszą wydajność w porównaniu ze zdarzeniami dotykowymi, w których przypadku do osiągnięcia takiego samego poziomu szybkości reakcji musisz używać biernych odbiorników zdarzeń.

Możesz uniemożliwić przeglądarce przejęcie kontroli za pomocą właściwości touch-action w kodzie CSS. Ustawienie tej opcji na none w przypadku elementu spowoduje wyłączenie wszystkich akcji zdefiniowanych przez przeglądarkę, które zostały uruchomione na tym elemencie. Dostępne są jednak inne wartości, które umożliwiają bardziej szczegółowe sterowanie, np. pan-x, która pozwala przeglądarce reagować na ruch wzdłuż osi x, ale nie wzdłuż osi y. Chrome 55 obsługuje te wartości:

auto Domyślna. Przeglądarka może wykonać dowolne domyślne działanie.
none Przeglądarka nie może wykonywać żadnych domyślnych działań.
pan-x Przeglądarka może wykonać tylko domyślne działanie przewijania w poziomie.
pan-y Przeglądarka może wykonywać tylko domyślną czynność przewijania w pionie.
pan-left Przeglądarka może wykonywać tylko domyślne działanie przewijania poziomego i tylko przesuwać stronę w lewo.
pan-right Przeglądarka może wykonywać tylko domyślne działanie przewijania w poziomie i tylko przesuwać stronę w prawo.
pan-up Przeglądarka może wykonywać tylko domyślne działanie przewijania w poziomie i tylko przesuwać stronę w górę.
pan-down Przeglądarka może wykonywać tylko domyślne działanie przewijania w poziomie i tylko przesuwać stronę w dół.
manipulation Przeglądarka może tylko przewijać i powiększać.

Przechwytywanie wskaźnika

Czy zdarzyło Ci się spędzić godzinę na debugowaniu nieprawidłowego zdarzenia mouseup, zanim zdasz sobie sprawę, że to dlatego, że użytkownik puścił przycisk poza obszarem docelowym kliknięcia? Nie? OK, może to tylko ja.

Do tej pory nie było jednak dobrego sposobu na rozwiązanie tego problemu. Oczywiście. Możesz skonfigurować w dokumentach mouseup i zapisz w aplikacji stan, aby móc śledzić zmiany. Nie jest to jednak najczystsze rozwiązanie, zwłaszcza jeśli tworzysz komponent internetowy i starasz się zachować wszystko w izolacji.

Zdarzenia związane z wskaźnikiem to znacznie lepsze rozwiązanie: możesz rejestrować ruch kursora, aby mieć pewność, że pointerup zdarzenie (lub inne z jego ulotnych przyjaciół).

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

Obsługa przeglądarek

W momencie pisania tego artykułu zdarzenia wskaźnika są obsługiwane w Internet Explorerze 11, Microsoft Edge, Chrome i Operze, a w Firefoksie są częściowo obsługiwane. Aktualną listę znajdziesz na stronie caniuse.com.

Do wypełnienia luk możesz użyć wypełnienia wskaźnika zdarzeń. Sprawdzanie obsługi przeglądarki w czasie działania jest proste:

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

Zdarzenia związane z wskaźnikiem są świetnym kandydatem do stopniowego ulepszania: wystarczy zmodyfikować metody inicjalizacji, aby wykonać powyższe sprawdzenie, dodać moduły obsługi zdarzeń związanych ze wskaźnikiem w bloku if i przesunąć moduły obsługi zdarzeń związanych z myszą lub dotykiem do bloku else.

Wypróbuj je i daj nam znać, co o nich myślisz.