Twórz z Chrome

Klocki LEGO® na stronach internetowych na wiele urządzeń

Build with Chrome to zabawny eksperyment dla użytkowników Chrome na komputerach stacjonarnych. Został po raz pierwszy udostępniony w Australii, a w 2014 r. został ponownie wydany na całym świecie. Wprowadzono w nim elementy związane z filmem THE LEGO® MOVIE™ oraz nową obsługę urządzeń mobilnych. W tym artykule omówimy wnioski z tego projektu, zwłaszcza dotyczące przejścia z wersji przeznaczonej tylko na komputery na rozwiązanie wieloekranowe, które obsługuje zarówno mysz, jak i sterowanie dotykowe.

Historia kompilacji w Chrome

Pierwsza wersja aplikacji Build with Chrome została wprowadzona w Australii w 2012 r. Chcieliśmy pokazać potęgę internetu w zupełnie nowy sposób i zaprezentować Chrome zupełnie nowej grupie odbiorców.

Strona składała się z 2 głównych części: trybu „Budowanie”, w którym użytkownicy mogą budować konstrukcje z klocków LEGO, oraz trybu „Odkrywanie”, w którym mogą przeglądać konstrukcje w wersji LEGO Map Google.

Interaktywne modele 3D były niezbędne, aby zapewnić użytkownikom najlepsze wrażenia podczas budowania z klocków LEGO. W 2012 r. WebGL był publicznie dostępny tylko w przeglądarkach na komputery, więc Build był przeznaczony tylko na komputery. W trybie Explore do wyświetlania kreacji używane są Mapy Google, ale po zbliżeniu na odpowiednią odległość włącza się implementacja mapy w technologii WebGL, która pokazuje kreacje w 3D, ale nadal wykorzystuje Mapy Google jako teksturę podstawy. Chcieliśmy stworzyć środowisko, w którym pasjonaci LEGO w każdym wieku mogliby łatwo i intuicyjnie wyrażać swoją kreatywność oraz poznawać projekty innych użytkowników.

W 2013 roku postanowiliśmy rozszerzyć Build with Chrome o nowe technologie internetowe. Wśród tych technologii znalazł się WebGL w Chrome na Androida, który pozwala na rozwijanie funkcji „Tworzenie w Chrome” na urządzeniach mobilnych. Na początek opracowaliśmy prototypy dotykowe, a potem przetestowaliśmy sprzęt w „narzędzie do tworzenia”, aby zrozumieć zachowanie gestów i reakcję dotykowa, z którą możemy się spotkać w przeglądarce w porównaniu z aplikacją mobilną.

Elastyczny interfejs

Musieliśmy zapewnić obsługę urządzeń z klawiaturą dotykowego i myszką. Jednak używanie tego samego interfejsu na małych ekranach dotykowych okazało się nieoptymalnym rozwiązaniem ze względu na ograniczoną ilość miejsca.

W budowaniu jest wiele interakcji: powiększanie i pomniejszanie, zmiana kolorów klocków i oczywiście wybieranie, obracanie i umieszczanie klocków. Jest to narzędzie, z którego użytkownicy często korzystają, dlatego ważne jest, aby mieli szybki dostęp do wszystkich często używanych funkcji i by mogli wygodnie z nich korzystać.

Podczas projektowania bardzo interaktywnej aplikacji dotykowej szybko zauważysz, że ekran wydaje się za mały, a palce użytkownika zajmują sporą jego część. Stało się to dla nas oczywiste, gdy pracowaliśmy z Kreatorem. Podczas projektowania należy wziąć pod uwagę fizyczny rozmiar ekranu, a nie piksele w grafice. Ważne jest, aby zminimalizować liczbę przycisków i elementów sterujących, aby jak najwięcej miejsca na ekranie przeznaczyć na rzeczywiste treści.

Naszym celem było uczynienie Build naturalnym na urządzeniach dotykowych. Nie tylko dodaliśmy obsługę dotykiem do pierwotnej implementacji na komputery, ale sprawiliśmy, że wygląda ona tak, jakby była przeznaczona do urządzeń dotykowych. W efekcie powstały 2 warianty interfejsu: jeden dla komputerów i tabletów z dużymi ekranami oraz drugi dla urządzeń mobilnych z mniejszymi ekranami. W miarę możliwości zalecamy użycie jednej implementacji i płynne przechodzenie między trybami. W naszym przypadku stwierdziliśmy, że różnica w doświadczeniu między tymi dwoma trybami była tak znacząca, że zdecydowaliśmy się polegać na określonym punkcie przełamania. Obie wersje mają wiele wspólnych funkcji i staraliśmy się, aby większość z nich działała na podstawie jednego kodu, ale niektóre aspekty interfejsu różnią się w zależności od wersji.

Korzystamy z danych user-agenta, aby wykrywać urządzenia mobilne, a potem sprawdzamy rozmiar widoku, aby zdecydować, czy należy użyć interfejsu na mały ekran. Trudno jest wybrać punkt przecięcia dla „dużego ekranu”, ponieważ trudno jest uzyskać wiarygodną wartość fizycznego rozmiaru ekranu. W naszym przypadku nie ma większego znaczenia, czy wyświetlamy interfejs na małym ekranie czy na urządzeniu dotykowym z dużym ekranem, ponieważ narzędzie będzie działać prawidłowo. Niektóre przyciski mogą być jednak nieco za duże. Ostatecznie ustawiliśmy punkt przecięcia na 1000 pikseli. Jeśli wczytasz witrynę z poziomu okna szerszego niż 1000 pikseli (w trybie poziomym), zobaczysz wersję na duży ekran.

Porozmawiajmy teraz o 2 rozmiarach ekranu i doświadczeniach:

duży ekran z obsługą myszy i dotyku;

Wersja na duży ekran jest wyświetlana na wszystkich komputerach z myszką i na urządzeniach dotykowych z dużymi ekranami (np. Google Nexus 10). Ta wersja jest zbliżona do oryginalnego rozwiązania na komputery pod względem dostępnych elementów sterujących nawigacją, ale dodaliśmy obsługę dotyku i niektóre gesty. Dostosowujemy interfejs użytkownika do rozmiaru okna, więc gdy użytkownik zmieni rozmiar okna, może to spowodować usunięcie lub zmianę rozmiaru części interfejsu. Wykorzystujemy do tego zapytania o multimedia w CSS.

Przykład: gdy dostępna wysokość jest mniejsza niż 730 pikseli, suwak powiększenia w trybie Eksplorowanie jest ukryty:

@media only screen and (max-height: 730px) {
    .zoom-slider {
        display: none;
    }
}

Mały ekran, obsługa tylko dotykiem

Ta wersja jest wyświetlana na urządzeniach mobilnych i małych tabletach (urządzenia docelowe: Nexus 4 i Nexus 7). Ta wersja wymaga obsługi wielodotykowej.

Na urządzeniach z małym ekranem musimy zapewnić treściom jak najwięcej miejsca na ekranie, więc wprowadziliśmy kilka zmian, aby zmaksymalizować przestrzeń, głównie poprzez ukrycie elementów używanych rzadziej:

  • Podczas budowania selektor klocków w budowaniu jest minimalizowany do selektora kolorów.
  • Elementy sterujące powiększeniem i orientacją zostały zastąpione gestami wielodotykowymi.
  • Funkcja pełnego ekranu w Chrome również pomaga w wypełnieniu większej części ekranu.
Tworzenie na dużym ekranie
Twórz reklamy na dużym ekranie. Wybór klocki jest zawsze widoczny, a po prawej stronie znajduje się kilka elementów sterujących.
Tworzenie na małym ekranie
Twórz na małym ekranie. Wybór klocki jest zminimalizowany, a niektóre przyciski zostały usunięte.

Wydajność i obsługa WebGL

Nowoczesne urządzenia dotykowe mają dość wydajne procesory graficzne, ale wciąż nie są tak wydajne jak ich odpowiedniki na komputerach. Wiedzieliśmy więc, że będziemy mieć problemy z wydajnością, zwłaszcza w trybie Odkrywaj w 3D, w którym musimy renderować wiele kreacji jednocześnie.

Chcieliśmy też dodać kilka nowych rodzajów klocków o skomplikowanych kształtach i przezroczystości – funkcje, które zwykle bardzo obciążają procesor graficzny. Musieliśmy jednak zachować zgodność wsteczną i nadal obsługiwać kreacje z pierwszej wersji, więc nie mogliśmy nałożyć żadnych nowych ograniczeń, takich jak znaczne zmniejszenie łącznej liczby klocków w kreacji.

W pierwszej wersji aplikacji Build obowiązywał limit maksymalnej liczby klocków, które można było użyć w jednym projekcie. Było tam „pomiarów cegieł”, które wskazywały, ile cegieł zostało. W ramach nowej implementacji niektóre nowe cegły miały wpływać na licznik cegieł bardziej niż cegły standardowe, co spowodowało nieznaczne zmniejszenie łącznej maksymalnej liczby cegieł. Był to jeden ze sposobów na dodanie nowych klocków przy zachowaniu przyzwoitej wydajności.

W trybie eksploracji 3D wiele rzeczy dzieje się jednocześnie: wczytywanie tekstur podstawy, wczytywanie kreacji, animowanie i renderowanie kreacji itd. Wymaga to dużej mocy zarówno procesora graficznego, jak i procesora. Dlatego w Chrome DevTools przeprowadziliśmy wiele profilowania klatek, aby zoptymalizować te elementy w jak największym stopniu. Na urządzeniach mobilnych postanowiliśmy przybliżyć nieco kreacje, aby nie trzeba było renderować tak wielu kreacji jednocześnie.

W przypadku niektórych urządzeń musieliśmy ponownie przejrzeć i uprościć niektóre shadery WebGL, ale zawsze znajdowaliśmy sposób na rozwiązanie problemu i dalsze działanie.

Obsługa urządzeń bez obsługi WebGL

Chcieliśmy, aby witryna była w jakimś stopniu użyteczna nawet wtedy, gdy urządzenie użytkownika nie obsługuje WebGL. Czasami można uprościć wyświetlanie obiektów 3D, korzystając z rozwiązania canvas lub funkcji CSS3D. Niestety nie znaleźliśmy wystarczająco dobrego rozwiązania, które pozwoliłoby odtworzyć funkcje tworzenia i eksplorowania modeli 3D bez użycia WebGL.

Ze względu na spójność styl wizualny kreacji musi być taki sam na wszystkich platformach. Mogliśmy spróbować użyć rozwiązania 2,5D, ale wtedy niektóre kreacje wyglądałyby inaczej. Musieliśmy też zastanowić się, jak zapewnić, aby projekty utworzone za pomocą pierwszej wersji aplikacji Build with Chrome wyglądały tak samo i działały tak samo płynnie w nowej wersji witryny jak w pierwszej.

Tryb 2D jest nadal dostępny na urządzeniach, które nie obsługują WebGL, ale nie można tworzyć nowych kreacji ani eksplorować świata w 3D. Użytkownicy mogą jednak zobaczyć, jak rozbudowany jest projekt i co mogą stworzyć za pomocą tego narzędzia, gdyby mieli urządzenie z obsługą WebGL. Witryna może nie być tak wartościowa dla użytkowników bez obsługi WebGL, ale powinna przynajmniej pełnić funkcję teasera i zachęcać do wypróbowania.

Przechowywanie wersji zastępczych dla rozwiązań WebGL jest czasami po prostu niemożliwe. Jest wiele możliwych przyczyn: wydajność, styl wizualny, koszty rozwoju i utrzymania itp. Jeśli jednak zdecydujesz się nie stosować rozwiązania zastępczego, musisz przynajmniej zadbać o użytkowników, którzy nie mają dostępu do WebGL. Wyjaśnij im, dlaczego nie mogą w pełni korzystać z witryny, i podaj instrukcje, jak rozwiązać ten problem za pomocą przeglądarki obsługującej WebGL.

Zarządzanie komponentami

W 2013 r. wprowadziliśmy nową wersję Map Google, w której wprowadzono najważniejsze zmiany w interfejsie od czasu premiery. Dlatego postanowiliśmy zmienić wygląd aplikacji Build with Chrome, aby pasował do nowego interfejsu Map Google. Podczas tej zmiany wzięliśmy pod uwagę inne czynniki. Nowy design jest stosunkowo płaski, z czystymi, jednolitymi kolorami i prostymi kształtami. Dzięki temu mogliśmy użyć czystego CSS w wielu elementach interfejsu, minimalizując użycie obrazów.

W sekcji Odkrywaj musimy wczytać wiele obrazów: miniatury kreacji, tekstury map dla podstaw i wreszcie same kreacje 3D. Dokładamy wszelkich starań, aby nie dochodziło do wycieku pamięci podczas ciągłego wczytywania nowych obrazów.

Elementy 3D są przechowywane w niestandardowym formacie pliku zapakowanym jako obraz PNG. Przechowywanie danych dotyczących kreacji 3D w postaci obrazu umożliwiło nam przekazanie tych danych bezpośrednio do shaderów renderujących kreacje.

Dzięki temu projektowi mogliśmy używać tych samych rozmiarów obrazów na wszystkich platformach, co pozwoliło nam zminimalizować wykorzystanie miejsca na dane i przepustowości.

Zarządzanie orientacją ekranu

Łatwo zapomnieć, jak bardzo zmienia się format obrazu podczas przełączania się z orientacji pionowej na poziomą i odwrotnie. Należy wziąć to pod uwagę już na etapie dostosowywania aplikacji do urządzeń mobilnych.

W tradycyjnej witrynie z włączonym przewijaniem możesz zastosować reguły CSS, aby uzyskać witrynę responsywną, która ponownie układa treści i menu. Jeśli możesz korzystać z funkcji przewijania, nie powinno to stanowić problemu.

Użyliśmy tej metody również w przypadku Build, ale możliwości dostosowania układu były nieco ograniczone, ponieważ treści musiały być widoczne przez cały czas, a jednocześnie trzeba było zachować szybki dostęp do wielu elementów sterujących i przycisków. W przypadku stron z treściami, np. witryn z wiadomościami, płynny układ ma sens, ale w przypadku aplikacji z grą, takiej jak nasza, było to trudne. Wyzwaniem było znalezienie układu, który działałby zarówno w orientacji poziomej, jak i pionowej, a jednocześnie zapewniał dobry przegląd treści i wygodny sposób interakcji. Ostatecznie zdecydowaliśmy się zachować tryb poziomy tylko w przypadku tworzenia, a użytkownikowi prosimy o obrócenie urządzenia.

Zadania w trybie eksploracji były znacznie łatwiejsze do rozwiązania w obu orientacjach. Musieliśmy tylko dostosować poziom powiększenia obrazu 3D w zależności od orientacji, aby zapewnić spójność.

Większość układu treści jest kontrolowana przez CSS, ale niektóre elementy związane z orientacją trzeba było zaimplementować w JavaScript. Okazało się, że nie ma dobrego rozwiązania umożliwiającego identyfikację orientacji na różnych urządzeniach za pomocą window.orientation, więc ostatecznie po prostu porównywaliśmy window.innerWidth i window.innerHeight, aby określić orientację urządzenia.

if( window.innerWidth > window.innerHeight ){
  //landscape
} else {
  //portrait
}

Dodawanie obsługi dotyku

Dodanie obsługi dotykowej do treści internetowych jest dość proste. Podstawowa interaktywność, np. zdarzenie kliknięcia, działa tak samo na komputerach i urządzeniach z ekranem dotykowym, ale w przypadku bardziej zaawansowanych interakcji musisz też obsługiwać zdarzenia dotykowe: touchstart, touchmove i touchend. Z tego artykułu dowiesz się, jak korzystać z tych zdarzeń. Internet Explorer nie obsługuje zdarzeń dotyku, ale zamiast tego używa zdarzeń wskaźnika (pointerdown, pointermove, pointerup). Zdarzenia wskaźnika zostały przesłane do W3C w celu ujednoliwienia, ale na razie są stosowane tylko w Internet Explorerze.

W trybie Odkrywaj w 3D chcieliśmy zachować tę samą nawigację co w standardowej wersji Map Google: przesuwanie po mapie jednym palcem i powiększanie za pomocą 2 palców. Ponieważ kreacje są w 3D, dodaliśmy też gest obracania dwoma palcami. Zwykle wymaga to użycia zdarzeń dotykowych.

Dobrą praktyką jest unikanie intensywnych obliczeń, takich jak aktualizowanie lub renderowanie modelu 3D w obsługach zdarzeń. Zamiast tego przechowuj dane dotykowe w zmiennej i reaguj na nie w pętli renderowania requestAnimationFrame. Dzięki temu łatwiej jest też zaimplementować mysz, ponieważ odpowiednie wartości myszy są przechowywane w tych samych zmiennych.

Zacznij od zainicjowania obiektu do przechowywania danych wejściowych i dodaj detektor zdarzenia dotknięcie. W każdym obiekcie event handler wywołujemy funkcję event.preventDefault(). Ma to zapobiec dalszemu przetwarzaniu zdarzenia dotykowego przez przeglądarkę, co mogłoby spowodować nieoczekiwane działanie, np. przewijanie lub skalowanie całej strony.

var input = {dragStartX:0, dragStartY:0, dragX:0, dragY:0, dragDX:0, dragDY:0, dragging:false};
plateContainer.addEventListener('touchstart', onTouchStart);

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
    //start listening to all needed touchevents to implement the dragging
    document.addEventListener('touchmove', onTouchMove);
    document.addEventListener('touchend', onTouchEnd);
    document.addEventListener('touchcancel', onTouchEnd);
  }
}

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }
}

function onTouchEnd(event) {
  event.preventDefault();
  if( event.touches.length === 0){
    handleDragStop();
    //remove all eventlisteners but touchstart to minimize number of eventlisteners
    document.removeEventListener('touchmove', onTouchMove);
    document.removeEventListener('touchend', onTouchEnd);
    //also listen to touchcancel event to avoid unexpected behavior when switching tabs and some other situations
    document.removeEventListener('touchcancel', onTouchEnd);
  }
}

Nie przechowujemy danych wejściowych w obsługach zdarzeń, ale w osobnych: handleDragStart, handleDragging i handleDragStop. Chcemy, aby można było wywoływać te metody również z modułów obsługi zdarzeń myszy. Pamiętaj, że chociaż jest to mało prawdopodobne, użytkownik może jednocześnie używać ekranu dotykowego i myszy. Zamiast zajmować się bezpośrednio tą sprawą, sprawdzamy, czy nic się nie zepsuje.

function handleDragStart(x ,y ){
  input.dragging = true;
  input.dragStartX = input.dragX = x;
  input.dragStartY = input.dragY = y;
}

function handleDragging(x ,y ){
  if(input.dragging) {
    input.dragDX = x - input.dragX;
    input.dragDY = y - input.dragY;
    input.dragX = x;
    input.dragY = y;
  }
}

function handleDragStop(){
  if(input.dragging) {
    input.dragging = false;
    input.dragDX = 0;
    input.dragDY = 0;
  }
}

Podczas tworzenia animacji opartych na zdarzeniu touchmove często warto przechowywać także wartość delta od ostatniego zdarzenia. Użyliśmy go na przykład jako parametru prędkości kamery podczas przemieszczania się po wszystkich płytach bazowych w sekcji Odkrywaj, ponieważ nie przeciągasz płyt, tylko poruszasz kamerą.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );

  //execute animation based on input.dragDX, input.dragDY, input.dragX or input.dragY
 /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

Przykład w ramce: przeciąganie obiektu za pomocą zdarzeń dotyku. Implementacja podobna do przeciągania mapy 3D w Build with Chrome: http://cdpn.io/qDxvo

Gesty wielodotykowe

Istnieje kilka frameworków i bibliotek, takich jak Hammer czy QuoJS, które mogą uprościć zarządzanie gestami wielodotykowymi, ale jeśli chcesz połączyć kilka gestów i uzyskać pełną kontrolę, czasami lepiej jest zacząć od zera.

Aby zarządzać gestami ściskania i obracania, przechowujemy odległość i kąt między 2 palcami, gdy drugi palec dotyka ekranu:

//variables representing the actual scale/rotation of the object we are affecting
var currentScale = 1;
var currentRotation = 0;

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGestureStart(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGestureStart(x1, y1, x2, y2){
  input.isGesture = true;
  //calculate distance and angle between fingers
  var dx = x2 - x1;
  var dy = y2 - y1;
  input.touchStartDistance=Math.sqrt(dx*dx+dy*dy);
  input.touchStartAngle=Math.atan2(dy,dx);
  //we also store the current scale and rotation of the actual object we are affecting. This is needed to support incremental rotation/scaling. We can't assume that an object is always the same scale when gesture starts.
  input.startScale=currentScale;
  input.startAngle=currentRotation;
}

W przypadku zdarzenia dotknij przesuń stale mierzymy odległość i kąt między tymi dwoma palcami. Różnica między początkową a bieżącą odległością służy do ustawienia skali, a różnica między początkowym a bieżącym kątem służy do ustawienia kąta.

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length  === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGesture(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGesture(x1, y1, x2, y2){
  if(input.isGesture){
    //calculate distance and angle between fingers
    var dx = x2 - x1;
    var dy = y2 - y1;
    var touchDistance = Math.sqrt(dx*dx+dy*dy);
    var touchAngle = Math.atan2(dy,dx);
    //calculate the difference between current touch values and the start values
    var scalePixelChange = touchDistance - input.touchStartDistance;
    var angleChange = touchAngle - input.touchStartAngle;
    //calculate how much this should affect the actual object
    currentScale = input.startScale + scalePixelChange*0.01;
    currentRotation = input.startAngle+(angleChange*180/Math.PI);
    //upper and lower limit of scaling
    if(currentScale<0.5) currentScale = 0.5;
    if(currentScale>3) currentScale = 3;
  }
}

Zmianę odległości między każdym zdarzeniem touchmove można potencjalnie wykorzystać w sposób podobny do przykładu z przeciąganiem, ale to podejście jest często przydatniejsze, gdy chcesz uzyskać ciągły ruch.

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //execute transform based on currentScale and currentRotation
  /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

Jeśli chcesz, możesz też umożliwić przeciąganie obiektu podczas wykonywania gestów ściskania i obracania. W takim przypadku jako punkt wejścia do uchwytu przeciągania należy użyć punktu środkowego między 2 palcami.

Przykład: obracanie i zmienianie skali obiektu w 2D. Podobnie jak mapa na karcie Odkrywaj: http://cdpn.io/izloq

Obsługa myszy i dotyku na tym samym sprzęcie

Obecnie istnieje kilka laptopów, takich jak Chromebook Pixel, które obsługują zarówno mysz, jak i sterowanie dotykowe. Jeśli nie będziesz uważać, może to spowodować nieoczekiwane działanie.

Pamiętaj, że nie wystarczy wykryć obsługę dotykowego sterowania i ignorować sygnałów myszy. Musisz obsługiwać oba te elementy jednocześnie.

Jeśli w obsługach zdarzeń dotykowych nie używasz event.preventDefault(), będą też wywoływane niektóre emulowane zdarzenia myszy, aby umożliwić działanie większości witryn nieoptymalizowanych pod kątem urządzeń dotykowych. Na przykład w przypadku pojedynczego kliknięcia na ekranie te zdarzenia mogą być wywoływane w szybkiej kolejności w takiej kolejności:

  1. touchstart
  2. touchmove
  3. touchend
  4. ruch kursora myszy|najechanie kursorem myszy (na obiekt)
  5. mousemove
  6. mousedown
  7. mouseup
  8. click

Jeśli masz nieco bardziej złożone interakcje, te zdarzenia myszy mogą powodować nieoczekiwane działanie i zakłócać implementację. Często najlepiej jest używać event.preventDefault() w obsługach zdarzeń dotykowych, a zarządzanie danymi wprowadzanymi przez mysz pozostawić osobnym obsługiwanym zdarzeniom. Pamiętaj, że używanie w obsługach zdarzeń dotykowych wartości event.preventDefault() uniemożliwi też niektóre domyślne zachowania, takie jak przewijanie i zdarzenie kliknięcia.

„W Build with Chrome nie chcieliśmy, aby funkcja powiększania była aktywowana po dwukrotnym kliknięciu w witrynie, mimo że jest to standardowa funkcja w większości przeglądarek. Dlatego używamy metatagu viewport, aby powiedzieć przeglądarce, że nie ma powiększać obrazu, gdy użytkownik dwukrotnie kliknie. Pozwala to też wyeliminować opóźnienie kliknięcia o 300 ms, co poprawia szybkość działania witryny. (opóźnienie kliknięcia służy do rozróżnienia pojedynczego i podwójnego kliknięcia, gdy włączone jest powiększanie po dwukrotnym kliknięciu).

<meta name="viewport" content="width=device-width,user-scalable=no">

Pamiętaj, że korzystając z tej funkcji, musisz zadbać o czytelność witryny na wszystkich rozmiarach ekranu, ponieważ użytkownik nie będzie mógł powiększyć obrazu.

Mysz, dotyk i klawiatura

W trybie eksploracji w 3D chcieliśmy zapewnić 3 sposoby nawigacji po mapie: za pomocą myszy (przeciąganie), dotykiem (przeciąganie, zbliżanie i obracanie za pomocą gestów) oraz za pomocą klawiatury (nawigacja za pomocą klawiszy strzałek). Wszystkie te metody nawigacji działają nieco inaczej, ale w przypadku każdej z nich zastosowaliśmy to samo podejście: zmienne ustawiamy w obsługach zdarzeń i działamy na nich w pętli requestAnimationFrame. Pętla requestAnimationFrame nie musi wiedzieć, która metoda jest używana do nawigacji.

Na przykład możemy ustawić ruch mapy (dragDX i dragDY) za pomocą wszystkich 3 metod wprowadzania danych. Oto implementacja klawiatury:

document.addEventListener('keydown', onKeyDown );
document.addEventListener('keyup', onKeyUp );

function onKeyDown( event ) {
  input.keyCodes[ "k" + event.keyCode ] = true;
  input.shiftKey = event.shiftKey;
}

function onKeyUp( event ) {
  input.keyCodes[ "k" + event.keyCode ] = false;
  input.shiftKey = event.shiftKey;
}

//this needs to be called every frame before animation is executed
function handleKeyInput(){
  if(input.keyCodes.k37){
    input.dragDX = -5; //37 arrow left
  } else if(input.keyCodes.k39){
    input.dragDX = 5; //39 arrow right
  }
  if(input.keyCodes.k38){
    input.dragDY = -5; //38 arrow up
  } else if(input.keyCodes.k40){
    input.dragDY = 5; //40 arrow down
  }
}

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //because keydown events are not fired every frame we need to process the keyboard state first
  handleKeyInput();
  //implement animations based on what is stored in input
   /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX = 0;
  input.dragDY = 0;
}

Przykład wbudowany: Przemieszczanie się za pomocą myszy, ekranu dotykowego i klawiatury: http://cdpn.io/catlf

Podsumowanie

Dostosowanie Buildu do Chrome, aby obsługiwał urządzenia dotykowe o różnych rozmiarach ekranu, było dla nas cenną lekcją. Nasz zespół nie miał zbyt dużego doświadczenia w korzystaniu z tego poziomu interakcji na urządzeniach dotykowych, ale dużo się nauczyliśmy.

Największym wyzwaniem okazało się znalezienie odpowiedniego rozwiązania w zakresie wrażeń użytkownika i projektu. Wyzwania techniczne dotyczyły zarządzania wieloma rozmiarami ekranów, zdarzeniami dotykowymi i problemami z wydajnością.

Chociaż shadery WebGL na urządzeniach dotykowych sprawiały pewne problemy, w ogóle wszystko działało lepiej niż oczekiwano. Urządzenia stają się coraz potężniejsze, a implementacje WebGL poprawiają się błyskawicznie. Uważamy, że w najbliższej przyszłości będziemy częściej korzystać z WebGL na urządzeniach.

Jeśli jeszcze tego nie zrobisz, utwórz coś niesamowitego.