Ekrany dotykowe są dostępne na coraz większej liczbie urządzeń – od telefonów po ekrany komputerów. Aplikacja powinna reagować na dotyk w intuicyjny i estetyczny sposób.
Ekrany dotykowe są dostępne na coraz większej liczbie urządzeń, od telefonów po komputery. Gdy użytkownicy zdecydują się na interakcję z Twoim interfejsem, aplikacja powinna reagować na ich dotyk w intuicyjny sposób.
Reagowanie na stany elementów
Czy zdarzyło Ci się dotykać lub klikać elementów na stronie i zastanawiać się, czy witryna rzeczywiście go wykryła?
Prosta zmiana koloru elementu, gdy użytkownicy dotykają niektórych elementów interfejsu lub wchodzą z nimi w interakcje, dostarcza podstawowej pewności, że strona działa. Nie tylko niweluje to frustrację, ale też sprawia, że strona jest elastyczna i płynna.
Elementy DOM mogą dziedziczyć dowolny z tych stanów: domyślny, aktywny, po najechaniu kursorem i aktywny. Aby zmienić interfejs użytkownika dla każdego z tych stanów, musimy zastosować style do tych pseudoklas :hover
, :focus
i :active
, jak pokazano poniżej:
.btn {
background-color: #4285f4;
}
.btn:hover {
background-color: #296cdb;
}
.btn:focus {
background-color: #0f52c1;
/* The outline parameter suppresses the border
color / outline when focused */
outline: 0;
}
.btn:active {
background-color: #0039a8;
}
W większości przeglądarek mobilnych stan hover lub hover jest dodawany do elementu po jego kliknięciu.
Zastanów się dobrze, jakie style ustawisz i jak będą wyglądać dla użytkownika po zakończeniu edytowania.
Blokuję domyślne style przeglądarki
Po dodaniu stylów dla różnych stanów zauważysz, że większość przeglądarek implementuje własne style w odpowiedzi na kliknięcie użytkownika. Wynika to głównie z tego, że przy pierwszym wprowadzeniu urządzeń mobilnych w wielu witrynach brakowało stylu dla stanu :active
. W rezultacie wiele przeglądarek dodało
dodatkowy kolor lub styl zaznaczenia, aby zwrócić uwagę użytkowników.
Większość przeglądarek używa właściwości CSS outline
do wyświetlania pierścienia wokół elementu, gdy jest on zaznaczony. Możesz je ukryć za pomocą:
.btn:focus {
outline: 0;
/* Add replacement focus styling here (i.e. border) */
}
Safari i Chrome dodają kolor zaznaczenia po kliknięciu, któremu można zapobiec za pomocą właściwości CSS -webkit-tap-highlight-color
:
/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
-webkit-tap-highlight-color: transparent;
}
Internet Explorer w Windows Phone działa podobnie, ale jest pomijany przez metatag:
<meta name="msapplication-tap-highlight" content="no">
W przeglądarce Firefox dostępne są dwa efekty uboczne.
Pseudoklasa -moz-focus-inner
, która dodaje kontur do elementów dotykowych, możesz usunąć, ustawiając właściwość border: 0
.
Jeśli używasz elementu <button>
w Firefoksie, stosowany jest gradient, który możesz usunąć, ustawiając wartość background-image: none
.
/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
background-image: none;
}
.btn::-moz-focus-inner {
border: 0;
}
Wyłączanie funkcji wyboru przez użytkownika
Podczas tworzenia interfejsu może się zdarzyć, że chcesz, aby użytkownicy wchodzili w interakcję z elementami, ale chcesz ograniczyć domyślne zachowanie, które polega na zaznaczaniu tekstu przy długim naciśnięciu lub przeciągnięciu kursorem myszy po interfejsie.
Możesz to zrobić za pomocą właściwości CSS user-select
. Pamiętaj jednak, że taka zmiana może extremely irytować użytkowników, którzy chcą zaznaczyć tekst w elemencie.
Używaj jej więc ostrożnie i z umiarem.
/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
user-select: none;
}
Wdrażanie gestów niestandardowych
Jeśli masz pomysł na niestandardowe interakcje i gesty w witrynie, musisz pamiętać o 2 kwestiach:
- Obsługa wszystkich przeglądarek
- Jak utrzymać wysoką liczbę klatek
W tym artykule przyjrzymy się dokładnie tym zagadnieniom, które obejmują interfejsy API potrzebne do działania we wszystkich przeglądarkach, oraz dowiesz się, jak efektywnie korzystać z tych zdarzeń.
W zależności od tego, co chcesz robić, może Ci zależeć na interakcji użytkownika z jednym elementem naraz lub umożliwienie mu interakcji z wieloma elementami naraz.
W tym artykule przyjrzymy się 2 przykładom, które pokazują obsługę wszystkich przeglądarek i sposoby utrzymania dużej liczby klatek.
Pierwszy przykład pozwala użytkownikowi na interakcję z jednym elementem. W takim przypadku możesz chcieć, aby wszystkie zdarzenia dotknięcia były przypisywane do danego elementu, o ile gest rozpoczął się na samym elemencie. Na przykład przesunięcie palcem poza element przesuwany może nadal sterować elementem.
Jest to przydatne, ponieważ zapewnia użytkownikowi dużą elastyczność, ale nakłada ograniczenie na sposób, w jaki może on korzystać z interfejsu.
Jeśli jednak oczekujesz, że użytkownicy będą wchodzić w interakcje z wieloma elementami jednocześnie (za pomocą funkcji multi-touch), musisz ograniczyć kontakt do konkretnego elementu.
Jest to bardziej elastyczne dla użytkowników, ale komplikuje logikę przy manipulowaniu interfejsem i jest mniej odporne na błędy użytkowników.
Dodaj detektory zdarzeń
W Chrome (w wersji 55 i nowszych) oraz Internet Explorerze i Edge należy używać PointerEvents
do implementowania gestów niestandardowych.
W innych przeglądarkach TouchEvents
i MouseEvents
są prawidłowe.
Ogromną zaletą funkcji PointerEvents
jest to, że scala ona wiele typów danych wejściowych, w tym zdarzenia myszy, dotknięcia i rysika, w jeden zestaw wywołań zwrotnych. Nasłuchiwane zdarzenia to pointerdown
, pointermove
, pointerup
i pointercancel
.
Odpowiedniki w innych przeglądarkach to touchstart
, touchmove
, touchend
i touchcancel
w przypadku zdarzeń dotyku. Jeśli chcesz zaimplementować ten sam gest w przypadku wprowadzania danych myszą, musisz zaimplementować zdarzenia mousedown
, mousemove
i mouseup
.
Jeśli masz pytania, których zdarzeń używać, zapoznaj się z tabelą zdarzeń dotknięcia, myszy i wskaźnika.
Korzystanie z tych zdarzeń wymaga wywołania metody addEventListener()
w elemencie DOM wraz z nazwą zdarzenia, funkcją wywołania zwrotnego i wartością logiczną.
Wartość logiczna określa, czy należy przechwycić zdarzenie przed, czy po tym, czy inne elementy miały możliwość wychwycenia i interpretowania zdarzeń. (true
oznacza, że chcesz, by zdarzenie było widoczne przed innymi elementami).
Oto przykład słuchania na początku interakcji.
// Check if pointer events are supported.
if (window.PointerEvent) {
// Add Pointer Event Listener
swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
// Add Touch Listener
swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);
// Add Mouse Listener
swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}
Obsługa interakcji jednoelementowej
W powyższym krótkim fragmencie kodu dodaliśmy tylko początkowy detektor zdarzeń myszy. Dzieje się tak, ponieważ zdarzenia myszy są wywoływane tylko po najechaniu kursorem na element, do którego jest dodany detektor.
TouchEvents
będzie śledzić gest po jego rozpoczęciu niezależnie od tego, gdzie nastąpił dotknięcie, a PointerEvents
będzie śledzić zdarzenia niezależnie od tego, gdzie nastąpi dotknięcie po wywołaniu metody setPointerCapture
w elemencie DOM.
W przypadku zdarzeń dotyczących ruchu i zakończenia działania myszy dodajemy detektory zdarzeń w metodzie uruchamiania gestu i dodajesz detektory do dokumentu. Oznacza to, że mogą one śledzić kursor do momentu zakończenia gestu.
Aby to zrobić:
- Dodaj wszystkie detektory TouchEvent i PointerEvent. W przypadku MouseEvents dodaj tylko zdarzenie startowe.
- W wywołaniu zwrotnym gestu rozpoczęcia przypisz do dokumentu zdarzenia dotyczące ruchu i zakończenia myszy. Dzięki temu będą odbierane wszystkie zdarzenia myszy niezależnie od tego, czy zachodzą w pierwotnym elemencie. Aby odbierać wszystkie kolejne zdarzenia, w przypadku PointerEvents musimy w pierwotnym elemencie wywołać metodę
setPointerCapture()
. Następnie obsługujej początek gestu. - Obsługa zdarzeń przenoszenia.
- Po zdarzeniu końcowym usuń z dokumentu ruchy myszy i detektory końcowe, a następnie zakończ gest.
Poniżej znajdziesz fragment metody handleGestureStart()
, która dodaje do dokumentu zdarzenia przeniesienia i zakończenia:
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if(evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
Końcowe wywołanie zwrotne, które dodajemy, to handleGestureEnd()
. Usuwa z dokumentu detektory zdarzeń przenoszenia i zdarzenia końcowego oraz zwalnia przechwytywanie wskaźnika po zakończeniu gestu:
// Handle end gestures
this.handleGestureEnd = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 0) {
return;
}
rafPending = false;
// Remove Event Listeners
if (window.PointerEvent) {
evt.target.releasePointerCapture(evt.pointerId);
} else {
// Remove Mouse Listeners
document.removeEventListener('mousemove', this.handleGestureMove, true);
document.removeEventListener('mouseup', this.handleGestureEnd, true);
}
updateSwipeRestPosition();
initialTouchPos = null;
}.bind(this);
Gdy użytkownik zacznie korzystać z elementu i przesunie go poza ten element, będziemy obserwować ruchy myszy niezależnie od tego, w którym miejscu strony się znajdują, ponieważ zdarzenia są odbierane z dokumentu.
Ten diagram pokazuje działanie zdarzeń dotknięcia podczas dodawania zdarzeń ruchu i zakończenia do dokumentu po rozpoczęciu gestu.
Efektywne reagowanie na dotyk
Mając już informacje na temat zdarzeń początkowych i końcowych, możemy reagować na zdarzenia dotknięcia.
W przypadku każdego zdarzenia rozpoczęcia i przeniesienia możesz łatwo wyodrębnić zdarzenia x
i y
ze zdarzenia.
Ten przykład pozwala sprawdzić, czy zdarzenie pochodzi z komponentu TouchEvent
, sprawdzając, czy istnieje parametr targetTouches
. Jeśli tak, zostanie wyodrębniony clientX
i clientY
już przy pierwszym dotknięciu.
Jeśli zdarzenie to PointerEvent
lub MouseEvent
, wyodrębnia zdarzenia clientX
i clientY
bezpośrednio z samego zdarzenia.
function getGesturePointFromEvent(evt) {
var point = {};
if (evt.targetTouches) {
// Prefer Touch Events
point.x = evt.targetTouches[0].clientX;
point.y = evt.targetTouches[0].clientY;
} else {
// Either Mouse event or Pointer Event
point.x = evt.clientX;
point.y = evt.clientY;
}
return point;
}
Element TouchEvent
zawiera 3 listy zawierające dane o dotknięciach:
touches
: lista wszystkich bieżących dotknięć ekranu, niezależnie od elementu DOM, w którym się on znajduje.targetTouches
: lista dotknięć elementu DOM, z którymi jest powiązane zdarzenie.changedTouches
: lista dotknięć, których zmiana spowodowała uruchomienie zdarzenia.
W większości przypadków targetTouches
oferuje wszystko, czego potrzebujesz. Więcej informacji o tych listach znajdziesz w artykule Listy dotykowe.
Używanie metody requestAnimationFrame
Wywołania zwrotne zdarzeń są uruchamiane w wątku głównym, więc chcemy je wykorzystywać jak najmniejszy kod w wywołaniach zwrotnych zdarzeń, aby utrzymać wysoką liczbę klatek i zapobiegać zacięciu.
Używając requestAnimationFrame()
, możemy zaktualizować interfejs tuż przed tym, zanim przeglądarka będzie chciała narysować ramkę. Pomoże nam to wyeliminować trochę czasu z wywołań zwrotnych zdarzeń.
Jeśli nie znasz funkcji requestAnimationFrame()
, tutaj znajdziesz więcej informacji.
Typową implementacją jest zapisywanie współrzędnych x
i y
ze zdarzenia rozpoczęcia i przeniesienia oraz żądanie ramki animacji w wywołaniu zwrotnym zdarzenia przenoszenia.
W naszej wersji demonstracyjnej zapisujemy początkową pozycję dotyku w handleGestureStart()
(szukaj symbolu initialTouchPos
):
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
Metoda handleGestureMove()
zapisuje pozycję zdarzenia przed żądaniem klatki animacji, przekazując w razie potrzeby funkcję onAnimFrame()
jako wywołanie zwrotne:
this.handleGestureMove = function (evt) {
evt.preventDefault();
if (!initialTouchPos) {
return;
}
lastTouchPos = getGesturePointFromEvent(evt);
if (rafPending) {
return;
}
rafPending = true;
window.requestAnimFrame(onAnimFrame);
}.bind(this);
Wartość onAnimFrame
to funkcja, która po wywołaniu zmienia interfejs użytkownika, aby ją przenieść. Przekazując tę funkcję do requestAnimationFrame()
, informujemy przeglądarkę, że ma ją wywołać tuż przed zaktualizowaniem strony (czyli wyrenderować wszelkie zmiany strony).
W wywołaniu zwrotnym handleGestureMove()
wstępnie sprawdzamy, czy wartość rafPending
ma wartość fałsz, co wskazuje, że usługa onAnimFrame()
została wywołana przez requestAnimationFrame()
od ostatniego zdarzenia ruchu. Oznacza to, że w danym momencie oczekuje na uruchomienie tylko 1 zasobu requestAnimationFrame()
.
Podczas wykonywania wywołania zwrotnego onAnimFrame()
ustawiamy przekształcenie wszystkich elementów, które chcesz przenieść, a następnie aktualizujemy element rafPending
do wartości false
, dzięki czemu następne zdarzenie dotknięcia może zażądać nowej klatki animacji.
function onAnimFrame() {
if (!rafPending) {
return;
}
var differenceInX = initialTouchPos.x - lastTouchPos.x;
var newXTransform = (currentXPosition - differenceInX)+'px';
var transformStyle = 'translateX('+newXTransform+')';
swipeFrontElement.style.webkitTransform = transformStyle;
swipeFrontElement.style.MozTransform = transformStyle;
swipeFrontElement.style.msTransform = transformStyle;
swipeFrontElement.style.transform = transformStyle;
rafPending = false;
}
Steruj gestami za pomocą działań dotykowych
Właściwość CSS touch-action
pozwala kontrolować domyślne zachowanie elementu dotykowego. W naszych przykładach używamy parametru touch-action: none
, aby uniemożliwiać przeglądarce wykonywanie jakichkolwiek działań związanych z dotykiem użytkownika, co pozwala nam przechwytywać wszystkie zdarzenia dotknięcia.
/* Pass all touches to javascript: */
button.custom-touch-logic {
touch-action: none;
}
Użycie elementu touch-action: none
jest w pewnym stopniu prostą opcją, ponieważ uniemożliwia wszystkim domyślne działanie przeglądarki. W wielu przypadkach lepszym
rozwiązaniem jest jedna z poniższych opcji.
touch-action
pozwala wyłączyć gesty zaimplementowane przez przeglądarkę.
Na przykład IE10+ obsługuje gest powiększenia dwukrotnym dotknięciem. Ustawienie wartości touch-action
na manipulation
zapobiega domyślnemu zachowaniu dwukrotnego kliknięcia.
Dzięki temu możesz samodzielnie wykonać gest dwukrotnego dotknięcia.
Oto lista często używanych wartości parametru touch-action
:
Obsługa starszych wersji IE
Jeśli chcesz obsługiwać IE10, musisz obsługiwać wersje PointerEvents
z prefiksem dostawcy.
Aby sprawdzić obsługę protokołu PointerEvents
, którego zwykle używasz window.PointerEvent
, ale w IE10 – window.navigator.msPointerEnabled
.
Nazwy zdarzeń z prefiksami dostawców to: 'MSPointerDown'
, 'MSPointerUp'
i 'MSPointerMove'
.
Przykład poniżej pokazuje, jak znaleźć obsługę i zmienić nazwy zdarzeń.
var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';
if (window.navigator.msPointerEnabled) {
pointerDownName = 'MSPointerDown';
pointerUpName = 'MSPointerUp';
pointerMoveName = 'MSPointerMove';
}
// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
window.PointerEventsSupport = true;
}
Więcej informacji znajdziesz w tym artykule o aktualizacjach firmy Microsoft.
Dokumentacja
Pseudoklasy dla stanów dotyku
Szczegółowe informacje o zdarzeniach dotknięcia znajdziesz tutaj: Zdarzenia dotyku W3C.
Zdarzenia dotknięcia, myszy i wskaźnika
Te zdarzenia to elementy składowe umożliwiające dodawanie nowych gestów do aplikacji:
Dotknij list
Każde zdarzenie dotknięcia zawiera 3 atrybuty listy:
Włączanie obsługi stanu aktywności w iOS
Safari w iOS domyślnie nie stosuje stanu aktywnego. Aby wszystko działało, musisz dodać odbiornik zdarzeń touchstart
do treści dokumentu lub do każdego elementu.
Należy to zrobić za testem klienta użytkownika, aby blokował się tylko na urządzeniach z iOS.
Dodanie trybu rozpoczęcia kliknięcia do treści ma tę zaletę, że jest stosowane do wszystkich elementów DOM, ale może to powodować problemy z wydajnością podczas przewijania strony.
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
document.body.addEventListener('touchstart', function() {}, false);
}
};
Alternatywnym sposobem jest dodanie detektorów uruchamiania dotykowego do wszystkich elementów na stronie, co zmniejsza ryzyko problemów z działaniem.
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
var elements = document.querySelectorAll('button');
var emptyFunction = function() {};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('touchstart', emptyFunction, false);
}
}
};