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 ekrany komputerów. Gdy użytkownicy chcą wchodzić w interakcje z interfejsem, aplikacja powinna reagować na ich dotyk w intuicyjny sposób.

reagować na stany elementów,

Czy zdarzyło Ci się dotknąć lub kliknąć element na stronie internetowej i zastanawiać się, czy strona go wykryła?

Wystarczy zmienić kolor elementu, gdy użytkownicy dotykają lub klikają elementy interfejsu, aby zapewnić im podstawową pewność, że witryna działa. Pozwala to nie tylko zmniejszyć frustrację, ale też sprawić, że aplikacja będzie działać szybko i płynnie.

Elementy DOM mogą dziedziczyć dowolny z tych stanów: domyślny, fokus, najechanie kursorem i aktywny. Aby zmienić interfejs użytkownika w przypadku każdego z tych stanów, trzeba 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

Ilustracja pokazująca różne kolory przycisków w różnych stanach

Większość przeglądarek mobilnych stosuje stan najechania kursorem lub naświetlenia do elementu po jego kliknięciu.

Uważnie zastanów się nad tym, jakie style ustawisz i jak będą wyglądać dla użytkownika po zakończeniu modyfikacji.

Blokowanie domyślnych stylów 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 dotyk użytkownika. Wynika to głównie z tego, że gdy urządzenia mobilne pojawiły się na rynku, wiele witryn nie miało stylizacji dla stanu :active. W rezultacie wiele przeglądarek dodało dodatkowy kolor lub styl wyróżnienia, aby przekazać użytkownikowi informacje zwrotne.

Większość przeglądarek używa właściwości CSS outline, aby wyświetlać pierścień wokół elementu, gdy jest on aktywny. Możesz go pominąć, korzystając z tych opcji:

.btn:focus {
    outline: 0;

    /* Add replacement focus styling here (i.e. border) */
}

Safari i Chrome dodają kolor podświetlenia przy dotknięciu, którego można uniknąć za pomocą właściwości -webkit-tap-highlight-color CSS:

/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
  -webkit-tap-highlight-color: transparent;
}

Wypróbuj

Internet Explorer na Windows Phone działa podobnie, ale jest blokowany za pomocą metatagu:

<meta name="msapplication-tap-highlight" content="no">

Firefox ma 2 efekty uboczne.

Pseudoklasa -moz-focus-inner, która dodaje obrys do elementów dotykowych, może zostać usunięta przez ustawienie border: 0.

Jeśli używasz elementu <button> w Firefox, zostanie zastosowany gradient, który możesz usunąć, ustawiając 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 opcji wyboru użytkownika

Podczas tworzenia interfejsu mogą wystąpić sytuacje, w których chcesz, aby użytkownicy mogli wchodzić w interakcje z elementami, ale jednocześnie chcesz zablokować domyślne zachowanie polegające na zaznaczaniu tekstu po przytrzymaniu lub przeciąganiu myszy po interfejsie.

Możesz to zrobić za pomocą właściwości CSS user-select, ale pamiętaj, że bardzo irytuje to użytkowników, jeśli chcą zaznaczyć tekst w elemencie. Używaj go ostrożnie i oszczędnie.

/* 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 swojej witrynie, pamiętaj o tych 2 kwestiach:

  1. Jak obsługiwać wszystkie przeglądarki
  2. Jak utrzymać wysoką liczbę klatek.

W tym artykule przyjrzymy się tym tematom dotyczącym interfejsów API, które muszą być obsługiwane, by docierać do wszystkich przeglądarek, a następnie jak skutecznie korzystać z tych zdarzeń.

W zależności od tego, co ma zrobić gest, prawdopodobnie zależy Ci na interakcji użytkownika z jednym elementem naraz lub z kilkoma elementami naraz.

W tym artykule omówimy 2 przykłady, które pokazują obsługę wszystkich przeglądarek i sposób na utrzymanie wysokiej liczby klatek.

Przykładowy GIF dotykowy dokumentu

Pierwszy przykład pozwoli użytkownikowi na interakcję z jednym elementem. W takim przypadku możesz chcieć, aby wszystkie zdarzenia dotyku były przekazywane do tego jednego elementu, o ile gest został początkowo rozpoczęty na samym elemencie. Na przykład przesunięcie palca poza elementem, który można przesuwać, może nadal sterować tym elementem.

Jest to przydatne, ponieważ zapewnia dużą elastyczność użytkownikowi, ale narzuca ograniczenia dotyczące sposobu interakcji z interfejsem.

Przykładowy GIF dotykowy elementu

Jeśli jednak chcesz, aby użytkownicy mogli jednocześnie wchodzić w interakcję z kilkoma elementami (za pomocą wielodotyku), ogranicz dotyk do konkretnego elementu.

Jest to bardziej elastyczne dla użytkowników, ale komplikuje logikę manipulowania interfejsem i jest mniej odporne na błędy użytkowników.

Dodawanie detektorów zdarzeń

W Chrome (w wersji 55 i nowszych) implementowanie gestów niestandardowych to PointerEventsInternet Explorer i Edge.

W innych przeglądarkach prawidłowe są wartości TouchEventsMouseEvents.

Wspaniałą cechą funkcji PointerEvents jest to, że łączy ona różne typy danych wejściowych, w tym zdarzenia związane z myszą, dotykiem i piórkiem, w jeden zestaw wywołań zwrotnych. Do odebrania są zdarzenia pointerdown, pointermove, pointeruppointercancel.

Odpowiedniki w innych przeglądarkach to touchstart, touchmove, touchendtouchcancel w przypadku zdarzeń dotykowych. Jeśli chcesz zaimplementować ten sam gest dla danych wejściowych myszy, musisz zaimplementować mousedown, mousemovemouseup.

Jeśli masz pytania dotyczące tego, których zdarzeń używać, zapoznaj się z tabelą z zdarzeniami dotykowymi, myszy i wskaźnika.

Korzystanie z tych zdarzeń wymaga wywołania metody addEventListener() elementu DOM wraz z nazwą zdarzenia, funkcją wywołania zwrotnego i wartością logiczną. Wartość logiczna określa, czy zdarzenie powinno być przechwycone przed czy po tym, jak inne elementy miały możliwość przechwycenia i zinterpretowania zdarzeń. (true oznacza, że zdarzenie ma być wyświetlane przed innymi elementami).

Oto przykład słuchania w celu wykrycia 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 z pojedynczym elementem

W tym krótkim fragmencie kodu dodaliśmy tylko detektory zdarzeń dla zdarzeń myszy. Dzieje się tak, ponieważ zdarzenia myszy są wywoływane tylko wtedy, gdy kursor znajduje się nad elementem, do którego dodano detektor zdarzeń.

TouchEvents będzie śledzić gest po jego rozpoczęciu niezależnie od tego, gdzie nastąpiło dotknięcie, a PointerEvents będzie śledzić zdarzenia niezależnie od tego, gdzie nastąpiło dotknięcie po wywołaniu funkcji setPointerCapture w elemencie DOM.

W przypadku zdarzeń przesunięcia i zakończenia myszy dodajemy detektory zdarzeń w metodzie rozpoczęcia gestów i dodajemy je do dokumentu, dzięki czemu mogą śledzić kursor do momentu zakończenia gestu.

Aby wdrożyć to ustawienie:

  1. Dodaj wszystkie detektory zdarzeń TouchEvent i PointerEvent. W przypadku zdarzeń MouseEvents dodaj tylko zdarzenie start.
  2. W zwracanej funkcji rozpoczęcia gestów powiązaj zdarzenia przesunięcia kursora i zakończenia z dokumentem. W ten sposób są odbierane wszystkie zdarzenia myszy, niezależnie od tego, czy zdarzenie ma miejsce na pierwotnym elemencie. W przypadku zdarzeń PointerEvents musimy wywołać metodę setPointerCapture() w pierwotnym elemencie, aby otrzymywać wszystkie kolejne zdarzenia. Następnie obsłuż początek gestu.
  3. Zarządzaj zdarzeniami dotyczącymi przenoszenia.
  4. W przypadku zdarzenia zakończenia usuń z dokumentu słuchaczy ruchu kursora i zakończenia oraz zakończ gest.

Oto fragment metody handleGestureStart(), która dodaje do dokumentu zdarzenia move i end:

// 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

Dodajemy wywołanie zwrotne zakończenia handleGestureEnd(), które usuwa z dokumentu detektory zdarzeń przesunięcia i zakończenia 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

Jeśli w dokumentzie zastosujesz ten wzór dodawania do niego zdarzenia przesunięcia, a użytkownik zacznie wchodzić w interakcję z elementem i przeniesie gest poza ten element, będziemy nadal otrzymywać ruchy kursora niezależnie od tego, gdzie są one na stronie, ponieważ zdarzenia są odbierane z dokumentu.

Ten diagram pokazuje działanie zdarzeń dotknięcia w wyniku dodania zdarzeń przenoszenia i zakończenia do dokumentu po rozpoczęciu gestu.

Ilustracja wiązania zdarzeń dotyku z dokumentem w funkcji `touchstart`

Skuteczne reagowanie na dotyk

Teraz, gdy mamy już zdarzenia rozpoczęcia i zakończenia, możemy reagować na zdarzenia dotykowe.

W przypadku dowolnego zdarzenia rozpoczęcia i ruchu możesz łatwo wyodrębnić z niego wartości xy.

Poniższy przykład pokazuje, czy zdarzenie pochodzi z elementu TouchEvent. W tym celu sprawdź, czy zdarzenie targetTouches istnieje. Jeśli tak, wyodrębnia z pierwszego dotknięcia wartości clientXclientY. Jeśli zdarzenie to PointerEvent lub MouseEvent, wyodrębnia clientX i clientY bezpośrednio ze 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

TouchEvent ma 3 listy z danymi o kliknięciach:

  • touches: lista wszystkich bieżących dotknięć ekranu, niezależnie od elementu DOM, do którego należą.
  • targetTouches: lista dotknięć bieżąco zarejestrowanych w elemencie DOM, do którego jest powiązany event.
  • changedTouches: lista interakcji, które uległy zmianie i spowodowały wywołanie zdarzenia.

W większości przypadków targetTouches zapewnia wszystko, czego potrzebujesz i czego oczekujesz. (więcej informacji o tych listach znajdziesz w artykule Listy dotykowe).

Używanie metody requestAnimationFrame

Wywołania zwrotne zdarzeń są wywoływane w głównym wątku, dlatego chcemy, aby w wywołaniach zwrotnych zdarzeń był wykonywany jak najmniej kodu, co pozwoli nam utrzymać wysoką częstotliwość generowania klatek i uniknąć zacięcia.

Dzięki requestAnimationFrame() możemy zaktualizować interfejs użytkownika tuż przed tym, jak przeglądarka zamierza narysować ramkę. Pomoże nam to przenieść część pracy z funkcji zwracających wywołania zwrotne zdarzeń.

Jeśli nie znasz usługi requestAnimationFrame(), tutaj znajdziesz więcej informacji.

Typowe rozwiązanie polega na zapisaniu współrzędnych xy z zdarzeń start i move oraz na żądaniu ramki animacji w ramce wywołania zwrotnego zdarzenia move.

W naszej wersji demonstracyjnej początkowe położenie dotykowe zapisujemy w języku handleGestureStart() (język 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() przechowuje pozycję swojego zdarzenia przed żądaniem ramki animacji, jeśli jest to konieczne, przekazując funkcję onAnimFrame() jako funkcję wywołania zwrotnego:

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 można było go przesuwać. Przekazując tę funkcję do requestAnimationFrame(), każemy przeglądarce wywołać ją tuż przed zaktualizowaniem strony (czyli naniesieniem na nią zmian).

W zgłoszeniu handleGestureMove() najpierw sprawdzamy, czy rafPending ma wartość false, co oznacza, że funkcja onAnimFrame() została wywołana przez funkcję requestAnimationFrame() od ostatniego zdarzenia przeniesienia. Oznacza to, że w danym momencie czeka na uruchomienie tylko 1 requestAnimationFrame().

Gdy wywołanie zwrotne onAnimFrame() zostanie wykonane, ustawiamy transformację dla wszystkich elementów, które chcemy przesunąć, zanim zaktualizujemy rafPending na false, co pozwala następnemu zdarzeniu dotykowemu poprosić o nowy kadr 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;
}

Sterowanie gestami za pomocą działań dotykowych

Właściwość CSS touch-action umożliwia kontrolowanie domyślnego działania dotykowego elementu. W naszych przykładach używamy elementu touch-action: none, aby uniemożliwić przeglądarce wykonywanie jakichkolwiek czynności na podstawie dotyku użytkownika, co pozwala nam przechwytywać wszystkie zdarzenia dotyku.

/* Pass all touches to javascript: */
button.custom-touch-logic {
  touch-action: none;
}

Użycie opcji touch-action: none jest jakąś formą ostateczności, ponieważ uniemożliwia wszystkie domyślne działania przeglądarki. W wielu przypadkach lepszym rozwiązaniem jest jedna z poniższych opcji.

touch-action umożliwia wyłączenie gestów implementowanych przez przeglądarkę. Na przykład IE 10 i nowsze wersje obsługują gest powiększania przez dwukrotne kliknięcie. Ustawienie wartości touch-action manipulation spowoduje wyłączenie domyślnego działania po dwukrotnym dotknięciu.

Dzięki temu możesz samodzielnie zaimplementować gest dwukrotnego dotknięcia.

Oto lista często używanych wartości touch-action:

Parametry dotknięcia
touch-action: none Przeglądarka nie obsługuje żadnych interakcji dotykiem.
touch-action: pinch-zoom Wyłącza wszystkie interakcje w przeglądarce, takie jak „dotyk: brak”, z wyjątkiem powiększenia, które jest nadal obsługiwane przez przeglądarkę.
touch-action: pan-y pinch-zoom Obsługa przewijania poziomego w JavaScript bez wyłączania przewijania pionowego ani powiększania za pomocą dwóch palców (np. w przypadku karuzeli zdjęć).
touch-action: manipulation Wyłącza gest dwukrotnego dotknięcia, co zapobiega opóźnieniom kliknięć w przeglądarce. Przewijanie i powiększanie za pomocą gestów jest obsługiwane przez przeglądarkę.

Obsługa starszych wersji Internet Explorera

Jeśli chcesz obsługiwać IE10, musisz obsługiwać wersje PointerEvents z prefiksem dostawcy.

Aby sprawdzić, czy PointerEvents jest obsługiwane, zazwyczaj szukasz opcji window.PointerEvent, ale w IE10 szukasz opcji window.navigator.msPointerEnabled.

Nazwy zdarzeń z prefiksami dostawców to: 'MSPointerDown', 'MSPointerUp''MSPointerMove'.

Przykład poniżej pokazuje, jak sprawdzić, czy obsługa jest dostępna, 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 firmy Microsoft.

Dokumentacja

Pseudoklasy stanów dotyku

Klasa Przykład Opis
:hover
Przycisk w stanie wciśniętym
Wpisuje się, gdy kursor znajduje się nad elementem. Zmiany w interfejsie podczas najechania kursorem na element pomagają zachęcić użytkowników do interakcji z elementami.
:focus
Przycisk w stanie narysowania
Wywoływane, gdy użytkownik przełącza się między elementami na stronie. Stan fokusa informuje użytkownika, z jakim elementem obecnie wchodzi w interakcję. Pozwala też użytkownikom łatwo poruszać się po interfejsie za pomocą klawiatury.
:active
Przycisk w stanie wciśniętym
Wprowadzane, gdy element jest wybierany, na przykład gdy użytkownik go klika lub klika.

Pełną listę zdarzeń dotykowych znajdziesz tutaj: Zdarzenia dotykowe W3C.

Zdarzenia dotyku, myszy i wskaźnika

Te zdarzenia są elementami składowymi dodawania nowych gestów do aplikacji:

Zdarzenia dotyku, myszy i wskaźnika
touchstart, mousedown, pointerdown Jest wywoływany, gdy palec dotyka elementu lub użytkownik klika mysz.
touchmove, mousemove, pointermove Jest wywoływany, gdy użytkownik przesuwa palcem po ekranie lub przeciąga element myszką.
touchend, mouseup, pointerup Jest wywoływany, gdy użytkownik odrywa palec od ekranu lub puści mysz.
touchcancel pointercancel Ta metoda jest wywoływana, gdy przeglądarka anuluje gesty dotykowe. Na przykład użytkownik dotyka aplikacji internetowej, a potem zmienia karty.

Lista dotykowa

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

Atrybuty zdarzenia dotknięcia
touches Lista wszystkich bieżących dotknięć ekranu, niezależnie od dotykanych elementów.
targetTouches Lista dotknięć, które rozpoczęły się na elemencie będącym celem bieżącego zdarzenia. Jeśli na przykład zbindujesz <button>, będziesz otrzymywać tylko dotknięcia tego przycisku. Jeśli zwiążesz dokument, zobaczysz wszystkie zmiany, które zostały w nim wprowadzone.
changedTouches Lista punktów styczności, które uległy zmianie i spowodowały wywołanie zdarzenia:
  • W przypadku zdarzenia touchstart: lista punktów styczności z klientem, które właśnie stały się aktywne w ramach bieżącego zdarzenia.
  • 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 właśnie zostały usunięte z platformy.

Włączanie obsługi stanu aktywnego na iOS

Niestety Safari na iOS domyślnie nie stosuje stanu active, więc aby go użyć, musisz dodać do touchstart elementu touchstart do body dokumentu lub do każdego elementu.

Trzeba to zrobić za pomocą testu klienta użytkownika, aby uruchamiał się tylko na urządzeniach z iOS.

Dodanie do ciała elementu dotknij, aby rozpocząć ma tę zaletę, że działa na wszystkie elementy w DOM, ale może 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);
  }
};

Alternatywą jest dodanie dotykowych elementów inicjujących do wszystkich elementów interaktywnych na stronie, co pozwoli ograniczyć niektóre problemy ze skutecznością.

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