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;
}
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;
}
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;
}
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:
- Jak obsługiwać wszystkie przeglądarki
- 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.
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.
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 PointerEvents
Internet Explorer i Edge.
W innych przeglądarkach prawidłowe są wartości TouchEvents
i MouseEvents
.
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
, pointerup
i pointercancel
.
Odpowiedniki w innych przeglądarkach to touchstart
, touchmove
,
touchend
i touchcancel
w przypadku zdarzeń dotykowych. Jeśli chcesz zaimplementować ten sam gest dla danych wejściowych myszy, musisz zaimplementować mousedown
,
mousemove
i mouseup
.
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);
}
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:
- Dodaj wszystkie detektory zdarzeń TouchEvent i PointerEvent. W przypadku zdarzeń MouseEvents dodaj tylko zdarzenie start.
- 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. - Zarządzaj zdarzeniami dotyczącymi przenoszenia.
- 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);
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);
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.
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 x
i y
.
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 clientX
i clientY
.
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;
}
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 x
i y
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
:
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'
i '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
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:
Lista dotykowa
Każde zdarzenie dotknięcia zawiera 3 atrybuty listy:
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);
}
}
};