Frontend Śródziemia

Przewodnik po tworzeniu treści na wiele urządzeń

W naszym pierwszym artykule o rozwoju eksperymentu Chrome Podróż przez Śródziemie skupiliśmy się na tworzeniu WebGL na urządzeniach mobilnych. W tym artykule omawiamy wyzwania, problemy i rozwiązania, które wystąpiły podczas tworzenia pozostałej części interfejsu HTML5.

Trzy wersje tej samej witryny

Zacznijmy od omówienia trochę informacji o zaadaptowaniu tego eksperymentu do działania zarówno na komputerach, jak i urządzeniach mobilnych z perspektywy wielkości ekranu i możliwości urządzeń.

Cały projekt został stworzony w stylu bardzo „filmowym”, w którym zależało nam na utrzymaniu scenerii w ustalonej ramce z orientacją poziomą, aby zachować magię filmu. Ponieważ duża część projektu składa się z interaktywnych mini„gier”, nie ma sensu pomijać ich w tekście.

Przykładem może być strona docelowa, która pokazuje, jak dostosowujemy projekt do różnych rozmiarów.

Orły właśnie wpłynęły na stronę docelową.
Orzechy właśnie rzuciły nas na stronę docelową.

Strona działa w 3 trybach: na komputery, na tablety i urządzenia mobilne. Nie tylko z uwagi na układ, ale także dlatego, że musimy obsługiwać zasoby wczytywane w czasie działania i dodawać różne optymalizacje skuteczności. W przypadku urządzeń o wyższej rozdzielczości niż komputery stacjonarne i laptopy, ale o gorszej wydajności niż telefony, sformułowanie ostatecznego zestawu reguł nie jest łatwe.

Używamy danych klienta użytkownika, aby wykrywać urządzenia mobilne i testować rozmiar widocznego obszaru, aby kierować reklamy na tablety (645 pikseli i wyższe). Każdy tryb może faktycznie renderować wszystkie rozdzielczości, ponieważ układ opiera się na zapytaniach o multimedia lub pozycjonowaniu względnym/procentowym w przypadku JavaScriptu.

Ponieważ projekty w tym przypadku nie są oparte na siatkach ani regułach, a w poszczególnych sekcjach różnią się od siebie, tak naprawdę zależy to od konkretnego elementu i scenariusza oraz użycia punktów przerwania i stylów. Wielokrotnie zdarzało się, że skonfigurowaliśmy idealny układ z ładnymi sass-mixinami i zapytaniami multimedialnymi. Potem musieliśmy dodać efekt zależny od pozycji myszy lub obiektów dynamicznych. W końcu cały proces został przepisany w JavaScript.

Dodajemy też w tagu <head> klasę z bieżącym trybem, abyśmy mogli użyć tych informacji w naszych stylach, jak w tym przykładzie (w SCSS):

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

Obsługujemy wszystkie rozmiary do około 360 x 320, co nie jest proste w rzeczywistości. Na komputerach obowiązuje minimalny rozmiar, po którym pojawiają się paski przewijania, ponieważ chcemy, by strona była wyświetlana na większym widocznym obszarze. Na urządzeniach mobilnych zdecydowaliśmy się umożliwić wyświetlanie zarówno filmów w orientacji poziomej, jak i pionowej, aż po obsługę interaktywną. Wynikało to z tego, że w orientacji pionowej nie jest ona tak imponująca jak w orientacji poziomej, ale skala witryny dobrze się skalowała, więc zachowaliśmy ją.

Trzeba pamiętać, że układu nie należy mieszać z wykrywaniem funkcji, takich jak typ wejścia, orientacja urządzenia, czujniki itp. Te funkcje mogą występować we wszystkich tych trybach i powinny być stosowane we wszystkich. Jednym z przykładów jest jednoczesne korzystanie z myszy i dotyku. Kompensacja siatkówki w oparciu o jakość, ale większość cech wydajności jest też inną, czasem gorsza jakość jest lepsza. Na przykład w przypadku rozwiązań obsługujących technologię WebGL na wyświetlaczach retina obszar roboczy ma połowę rozdzielczości, co w innym przypadku wymagałoby wyrenderowania 4-krotnie większej liczby pikseli.

Podczas programowania często używaliśmy emulatora w Narzędziach deweloperskich. Dotyczy to zwłaszcza Chrome Canary, które ma nowe, ulepszone funkcje i dużo gotowych ustawień. To dobry sposób na szybką weryfikację projektu. Nadal musieliśmy regularnie przeprowadzać testy na rzeczywistych urządzeniach. Jedną z nich było dostosowanie strony do trybu pełnoekranowego. Strony z przewijaniem w pionie w większości przypadków ukrywają interfejs przeglądarki podczas przewijania (obecnie natrafiają na to w Safari w iOS7), ale musieliśmy zmieścić wszystko niezależnie. Użyliśmy też gotowego ustawienia w emulatorze i zmieniliśmy ustawienia rozmiaru ekranu, aby symulować utratę wolnego miejsca. Testowanie na prawdziwych urządzeniach jest też ważne, jeśli chodzi o monitorowanie wykorzystania pamięci i wydajności.

Praca ze stanem

Po stronie docelowej trafiamy na mapę Śródziemia. Czy widzisz, że adres URL się zmienia? Witryna to aplikacja na jednej stronie, która używa interfejsu History API do routingu.

Każda sekcja witryny jest własnym obiektem i dziedziczy elementy DOM, takie jak przejścia, wczytywanie zasobów, usuwanie itp. Gdy przeglądasz różne części witryny, inicjowane są sekcje, elementy są dodawane do DOM i usuwane z elementów, a zasoby bieżącej sekcji są wczytywane.

Użytkownik może w każdej chwili kliknąć przycisk Wstecz w przeglądarce lub poruszać się po menu, więc wszystkie utworzone elementy muszą w pewnym momencie zostać zutylizowane. Przekroczenia czasu oczekiwania i animacje należy zatrzymać i odrzucić, w przeciwnym razie powodują niepożądane zachowanie, błędy i wycieki pamięci. Nie zawsze jest to łatwe, zwłaszcza gdy terminy zbliżają się, i trzeba jak najszybciej załatwić wszystkie sprawy.

Wyświetlanie lokalizacji

Aby pokazać piękne ustawienia i postaci ze Śródziemia, stworzyliśmy modułowy system komponentów z obrazami i tekstem, które możesz przeciągać i przesuwać w poziomie. Nie włączyliśmy tu paska przewijania, ponieważ chcemy mieć różne szybkości w różnych zakresach, np. w sekwencjach obrazów, w których zatrzymujesz ruch bokiem do chwili odtworzenia klipu.

Jaskinie Thranduila
Sala Thranduila oś czasu

Harmonogram

Na początku nie znaliśmy zawartości modułów w poszczególnych lokalizacjach. Wiedzieliśmy, że zależy nam na prezentacji różnych typów multimediów i informacji na osi czasu w formie poziomej, co da nam możliwość utworzenia 6 prezentacji lokalizacji bez konieczności sześciokrotnego ich tworzenia. W tym celu stworzyliśmy kontroler osi czasu, który obsługuje przesuwanie modułów na podstawie ustawień i zachowań modułów.

Moduły i komponenty zachowania

Obsługa różnych modułów to: sekwencja obrazów, nieruchomy obraz, scena z paralaksą, scena z przesunięciem ostrości i tekst.

Moduł sceny paralaksy ma nieprzezroczyste tło z niestandardową liczbą warstw, które nasłuchują postępu w widocznym obszarze i określają położenie.

Scena z przesunięciem ostrości jest wariantem zasobnika paralaksy, z tym że w każdej warstwie używamy 2 obrazów, które rozjaśniają i znikają, aby symulować zmianę ostrości. Próbowaliśmy zastosować filtr rozmycia, ale wciąż jest to drogie rozwiązanie, więc poczekamy na cieniowanie CSS.

Zawartość modułu tekstowego można przeciągać za pomocą wtyczki TweenMax Draggable. Możesz też przewijać w pionie za pomocą kółka przewijania lub 2 palcami. Zwróć uwagę na throw-props-plugin, która dodaje mechanizm rzucania po przesunięciu palcem i puszczaniu go.

Moduły mogą też działać w różny sposób jako zestaw komponentów. Każdy z nich ma własne selektory i ustawienia kierowania. Tłumacz, aby przesuwać element, skalować, by powiększyć, hotspoty w nakładce informacyjnej, dane debugowania do wizualnego testowania, nakładkę z tytułem początkowym, warstwa flary i nie tylko. Zostaną one dołączone do DOM lub będą sterować swoim elementem docelowym w module.

Dzięki temu możemy tworzyć różne lokalizacje za pomocą pliku konfiguracji, który określa, jakie zasoby mają zostać wczytane, i konfiguruje różne rodzaje modułów i komponentów.

Sekwencje obrazów

Największym wyzwaniem z modułów związanych z występem i rozmiarem pobierania jest sekwencja obrazów. Jest wiele artykułów do poczytania na ten temat. Na komórkach i tabletach zastępujemy ten obraz nieruchomym obrazem. To za dużo danych, aby je zdekodować i zapisać w pamięci, jeśli chcemy uzyskać dobrą jakość na komórce. Próbowaliśmy różnych rozwiązań. Najpierw korzystaliśmy z obrazu tła i arkusza sprite, ale powodowało to problemy z pamięcią i opóźnienie, gdy GPU wymagało zmiany arkuszy sprite. Potem próbowaliśmy zamienić elementy img, ale przebiegło to zbyt wolno. Najskuteczniejsze jest narysowanie ramki z arkusza sprite na płótno, więc zaczęliśmy ją optymalizować. Aby skrócić czas przetwarzania każdej klatki, dane obrazu zapisywane w obszarze roboczym są wstępnie przetwarzane w tymczasowym obszarze roboczym, a następnie zapisywane za pomocą metody putImageData() w tablicy, po zdekodowaniu i gotowe do użycia. Oryginalny arkusz sprite może zostać zebrany do kosza, a w pamięci podręcznej będzie przechowywana jedynie minimalna ilość danych, która ich wymaga. Może w rzeczywistości przechowywanie niezdekodowanych obrazów jest mniejsze, ale w ten sposób przewijanie sekwencji uzyskuje większą skuteczność. Ramki są dość małe, mają zaledwie 640 x 400 pikseli, ale będą widoczne tylko podczas przewijania. Gdy zatrzymasz odtwarzanie, obraz w wysokiej rozdzielczości wczytuje się i szybko się pojawia.

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

Arkusze sprite są generowane w narzędziu Imagemagick. Oto prosty przykład na GitHubie, który pokazuje, jak utworzyć arkusz sprite ze wszystkimi obrazami w folderze.

Animowanie modułów

Aby umieścić moduły na osi czasu, ukryta reprezentacja osi czasu wyświetlana poza ekranem uwzględnia suwak odtwarzania i szerokość osi czasu. Można to zrobić za pomocą samego kodu, ale podczas programowania i debugowania warto było przedstawić wizualną reprezentację. Podczas rzeczywistego działania narzędzie tylko aktualizuje się przy zmianie rozmiaru, aby określić wymiary. Niektóre moduły wypełniają widoczny obszar, a inne mają swoje własne proporcje, dlatego skalowanie i położenie wszystkich elementów we wszystkich rozdzielczościach było dość trudne, aby wszystko było widoczne, a nie przycięte. Każdy moduł ma dwa wskaźniki postępu: jeden za widoczne miejsce na ekranie, a drugi przez cały czas trwania modułu. Wykonując ruch paralaksy, często trudno jest obliczyć położenie początkowe i końcowe obiektów, aby zsynchronizować je z oczekiwaną pozycją, gdy znajduje się ona w polu widzenia. Warto wiedzieć, kiedy moduł ponownie trafia do widoku, odtwarza jego wewnętrzną oś czasu i kiedy ponownie znika z widoku.

Każdy moduł ma na górze subtelną czarną warstwę, która dopasowuje jego przezroczystość, dzięki czemu jest w pełni przezroczysta, gdy znajduje się w pozycji środkowej. Dzięki temu możesz skupić się na jednym module, co uatrakcyjnia cały proces.

Wydajność strony

Przejście z działającego prototypu na wersję pozbawioną zacięć oznacza przejście od zgadywania do wiedzy o tym, co dzieje się w przeglądarce. W takich sytuacjach Narzędzia deweloperskie w Chrome są Twoim najlepszym przyjacielem.

Poświęciliśmy sporo czasu na optymalizację witryny. Wymuszanie akceleracji sprzętowej jest oczywiście jednym z najważniejszych narzędzi zapewniających płynne animacje. W Narzędziach deweloperskich w Chrome możesz też szukać kolorowych kolumn i czerwonych prostokątów. Jest wiele dobrych artykułów na różne tematy, które warto przeczytać wszystkie. Nagroda za usunięcie pomijanych klatek jest natychmiastowa, ale i frustracja po powrocie użytkownika jest spodziewana. I tak zrobią. To ciągły proces, który wymaga iteracji.

Lubię używać TweenMax z Greensock do przekształcania właściwości, przekształceń i CSS. Myśl w kontenerach, wizualizuj strukturę i dodawaj nowe warstwy. Pamiętaj, że istniejące przekształcenia mogą zostać zastąpione nowymi. Wartość przesunięcie Z(0), która wymusza akcelerację sprzętową w klasie CSS, jest zastępowana macierz 2D tylko w przypadku starszych wartości 2D. Jeśli w takich przypadkach chcesz pozostawić warstwę w trybie akceleracji, użyj w przeszłości właściwości „force3D:true”, aby utworzyć macierz 3D zamiast matrycy 2D. Łatwo zapomnieć, że gdy zestawiasz style pośrednie CSS i JavaScript, przy tworzeniu stylów.

Nie wymuszaj akceleracji sprzętowej tam, gdzie nie jest potrzebna. Pamięć GPU może szybko się zapełnić i generować niepożądane rezultaty, gdy chcesz przyspieszyć sprzętowe działanie wielu kontenerów, zwłaszcza w systemie iOS, gdzie pamięć ma więcej ograniczeń. Znacznie ulepszyliśmy wczytywanie mniejszych zasobów i ich skalowanie za pomocą CSS oraz wyłączenie niektórych efektów w trybie mobilnym.

Wycieki pamięci to kolejna dziedzina, w której musieliśmy poprawić nasze umiejętności. Poruszając się między różnymi technologiami WebGL, tworzymy wiele obiektów, materiałów, tekstur i geometrii. Jeśli po zamknięciu sekcji nie są one gotowe do odśmiecania, prawdopodobnie spowoduje to po pewnym czasie awarię urządzenia, gdy zabraknie pamięci.

Zamykanie sekcji z błędną funkcją usuwania.
Zamknąć sekcję z błędną funkcją usuwania.
Dużo lepiej!
Znacznie lepiej!

Aby znaleźć wyciek danych, trzeba było wykonać proste czynności w Narzędziach deweloperskich: zarejestrować oś czasu i zrobić zrzuty stosu. Ułatwia to odfiltrowywanie konkretnych obiektów, takich jak geometria 3D lub konkretna biblioteka. W powyższym przykładzie okazało się, że scena 3D jest wciąż dostępna, a tablica z zapisanymi geometrią nie została wyczyszczona. Jeśli trudno jest znaleźć miejsce, w którym znajduje się obiekt, możesz skorzystać z przydatnej funkcji, która pozwala to zobaczyć, tzw. ścieżki przechowywania. W zrzucie stosu kliknij obiekt, który chcesz zbadać, a wyświetlą się one w panelu poniżej. Zastosowanie dobrej struktury z mniejszymi obiektami pomaga w zlokalizowaniu referencji.

Do sceny odwołuje się element EffectComposer.
Scena została dodana w narzędziu EffectComposer.

Ogólnie rzecz biorąc, dobrze jest zastanowić się dwa razy, zanim zaczniesz modyfikować DOM. Gdy już to zrobisz, pomyśl o wydajności. Jeśli możesz, nie manipuluj DOM w pętli gry. Przechowywanie odwołań w zmiennych do ponownego wykorzystania. Jeśli chcesz wyszukać element, użyj najkrótszej trasy, przechowując odwołania do strategicznych kontenerów i wyszukując w najbliższym elemencie nadrzędnym.

Opóźnij odczytywanie wymiarów nowo dodanych elementów lub usuwanie/dodawanie klas, jeśli występują błędy układu. Możesz też sprawdzić, czy Układ został aktywowany. Czasami grupa elementów przeglądarki zmienia style i nie aktualizuje się po kolejnym wywołaniu układu. Czasami może to być poważny problem, ale nie ma w tym powodu. Dowiedz się, jak działa ta funkcja, a przyniesie Ci to spore korzyści.

Pełny ekran

Jeśli jest taka możliwość, możesz przełączyć witrynę w tryb pełnoekranowy w menu za pomocą interfejsu pełnoekranowego interfejsu API. Jednak na urządzeniach również decyduje o tym, by włączyć tryb pełnoekranowy. W przeglądarce Safari w iOS haker przewidywał kontrolę, lecz obecnie nie jest to możliwe. Musisz więc przygotować swój projekt tak, by działał bez niego podczas tworzenia strony bez przewijania. Aktualizacje na ten temat prawdopodobnie można spodziewać się w kolejnych aktualizacjach, ponieważ zniszczyło to wiele aplikacji internetowych.

Komponenty

Animowane instrukcje dotyczące eksperymentów.
Animowane instrukcje dotyczące eksperymentów.

W witrynie mamy wiele różnych typów zasobów, w tym pliki graficzne (PNG i JPEG), SVG (w tekście i tło), arkusze sprite (PNG), niestandardowe czcionki ikon i animacje Adobe Edge. Używamy PNG w przypadku zasobów i animacji (arkuszy sprite), w przypadku których element nie może być w formacie wektorowym. W przeciwnym razie staramy się, aby w miarę możliwości korzystać z plików SVG.

Format wektorowy nie oznacza utraty jakości, nawet w przypadku skalowania. 1 plik na wszystkie urządzenia.

  • Mały rozmiar pliku.
  • Możemy animować każdą część oddzielnie (to idealne rozwiązanie w przypadku zaawansowanych animacji). Na przykład ukryjemy „podtytuł” logo Hobbita (pustkowie Smauga), gdy zostanie ono pomniejszone.
  • Można go umieścić na stronie jako tag HTML SVG lub jako obraz tła bez dodatkowego wczytywania (wczytuje się w tym samym czasie co strona HTML).

Kroje ikon mają te same zalety co w przypadku SVG pod względem skalowalności i są używane zamiast SVG w przypadku małych elementów, takich jak ikony, w przypadku których wystarczy zmienić kolor (po najechaniu kursorem, jako aktywny itp.). Ikony można również łatwo użyć ponownie – wystarczy ustawić dla elementu właściwość CSS „content”.

Animacje

W niektórych przypadkach animowanie elementów SVG za pomocą kodu może być bardzo czasochłonne, zwłaszcza gdy w procesie projektowania trzeba dużo zmienić w animacji. Aby usprawnić przepływ pracy między grafikami a deweloperami, w przypadku niektórych animacji używamy Adobe Edge (instrukcje dotyczące gier). Animacje są bardzo zbliżone do obsługi Flasha i pomagają zespołowi, ale mają pewne wady, zwłaszcza w przypadku integracji animacji Edge z procesem wczytywania zasobów, ponieważ ma własny mechanizm wczytywania i logikę implementacji.

Wydaje mi się, że jeszcze sporo brakuje, zanim stworzymy idealny przepływ pracy do obsługi zasobów i ręcznie tworzonych animacji w internecie. Nie możemy się doczekać, aż zobaczymy, jak narzędzia takie jak Edge będą się rozwijać. Jeśli chcesz, możesz podzielić się w komentarzach swoimi sugestiami na temat innych narzędzi do animacji i przepływów pracy.

Podsumowanie

Gdy wszystkie części projektu zostaną już opublikowane, a my patrzymy na efekt końcowy, muszę powiedzieć, że jesteśmy pod wrażeniem nowoczesnych przeglądarek mobilnych. Gdy zaczynaliśmy ten projekt, nie spodziewaliśmy się, że uda się nam go zrealizować, a także integracja i wydajność. Cały czas poświęcany na iterację i testowanie sprawił, że nasze szkolenia były bardzo przydatne. Dzięki temu możemy lepiej zrozumieć, jak działają nowoczesne przeglądarki. Właśnie dlatego chcemy skrócić czas produkcji takich projektów, od zgadywania po wiedzę.