Dodaj akcent do swojej witryny

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;
}

Wypróbuj

Obraz pokazujący różne kolory
stanów przycisku

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;
}

Wypróbuj

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;
}

Wypróbuj

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:

  1. Obsługa wszystkich przeglądarek
  2. 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.

Przykładowy GIF przedstawiający kliknięcie dokumentu

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.

Przykładowy GIF przedstawiający element dotykowy

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);
}

Wypróbuj

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ć:

  1. Dodaj wszystkie detektory TouchEvent i PointerEvent. W przypadku MouseEvents dodaj tylko zdarzenie startowe.
  2. 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.
  3. Obsługa zdarzeń przenoszenia.
  4. 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);

Wypróbuj

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);

Wypróbuj

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.

Ilustruje wiązanie zdarzeń dotknięcia z dokumentem w funkcji „touchstart”

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;
  }

Wypróbuj

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:

Parametry czynności dotknięcia
touch-action: none Przeglądarka nie będzie obsługiwać żadnych interakcji dotykowych.
touch-action: pinch-zoom Wyłącza wszystkie interakcje w przeglądarce, takie jak „touch-action: none” poza funkcją „pinch-zoom”, która jest nadal obsługiwana przez przeglądarkę.
touch-action: pan-y pinch-zoom Obsługa przewijania w poziomie w JavaScripcie bez wyłączania przewijania w pionie czy powiększania lub ściągania palcami (np. karuzele obrazów).
touch-action: manipulation Wyłącza gest dwukrotnego kliknięcia, który pozwala uniknąć opóźnień kliknięć wywołanych przez przeglądarkę. Pozostaw przewijanie i ściąganie palców, aby powiększyć widok przeglądarki.

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

Klasa Przykład Opis
:hover
Przycisk w stanie naciśnięcia
Wartość podawana po umieszczeniu kursora na elemencie. Zmiany w interfejsie wyświetlane po najechaniu kursorem myszy zachęcają użytkowników do interakcji z elementami.
:fokus
Przycisk ze stanem pełnej koncentracji
Wpisywane, gdy użytkownik przechodzi między elementami na stronie za pomocą klawisza Tab. Stan zaznaczenia informuje użytkownika, z jakim elementem wchodzi on w interakcję, a także pozwala łatwo poruszać się po interfejsie za pomocą klawiatury.
:aktywna
Przycisk w stanie naciśnięcia
Wpisywane podczas wybierania elementu, na przykład gdy użytkownik go klika lub dotyka.

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:

Zdarzenia dotknięcia, myszy, wskaźnika
touchstart, mousedown, pointerdown Dzieje się tak, gdy palec dotknie elementu po raz pierwszy lub gdy użytkownik kliknie przycisk myszy.
touchmove, mousemove, pointermove Nazywa się to, gdy użytkownik przesuwa palcem po ekranie lub przeciąga widok myszą.
touchend, mouseup, pointerup Dzieje się tak, gdy użytkownik uniesie palec z ekranu lub puszcza myszkę.
touchcancel pointercancel Jest ono wywoływane, gdy przeglądarka anuluje gesty dotykowe. Na przykład użytkownik klika aplikację internetową, a potem zmienia karty.

Dotknij list

Każde zdarzenie dotknięcia zawiera 3 atrybuty listy:

Atrybuty zdarzeń dotknięcia
touches Lista wszystkich bieżących dotknięć ekranu, niezależnie od dotykanych elementów.
targetTouches Lista dotknięć, które rozpoczęły się w elemencie będącym celem bieżącego zdarzenia. Jeśli na przykład utworzysz powiązanie z elementem <button>, będziesz otrzymywać tylko informacje o dotknięciach tego przycisku. Jeśli utworzysz powiązanie z dokumentem, zobaczysz wszystkie jego dotychczasowe kliknięcia.
changedTouches Lista dotknięć, które spowodowały uruchomienie zdarzenia:
  • W przypadku zdarzenia touchstart: lista punktów styczności z klientem, które stały się aktywne w związku z bieżącym zdarzeniem.
  • W przypadku zdarzenia touchmove: lista punktów styczności z klientem, które zmieniły się od ostatniego zdarzenia.
  • W przypadku zdarzeń touchend i touchcancel: lista punktów styczności z klientem, które zostały właśnie usunięte z platformy.

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);
    }
  }
};