Techniki HTML5 do optymalizacji skuteczności na urządzeniach mobilnych

Wprowadzenie

W dzisiejszych środowiskach mobilnych strony internetowe mają wiele problemów, takich jak wczytywanie z obrótką animacją, niepłynne przejścia między stronami czy okresowe opóźnienia w wydarzeniach związanych z kliknięciem. Programiści starają się jak najbardziej zbliżyć się do kodu natywnego, ale często zniechęcają ich do tego różnego rodzaju obejścia, resety i sztywny framework.

W tym artykule omówimy minimalne wymagania dotyczące tworzenia mobilnych aplikacji internetowych w HTML5. Głównym celem jest ujawnienie ukrytych złożoności, które próbują ukryć obecne frameworki mobilne. Poznasz minimalistyczne podejście (wykorzystujące podstawowe interfejsy API HTML5) oraz podstawy, które pozwolą Ci napisać własny framework lub rozszerzyć ten, którego obecnie używasz.

akceleracja sprzętowa,

Zwykle procesory graficzne obsługują szczegółowe modelowanie 3D lub diagramy CAD, ale w tym przypadku chcemy, aby proste rysunki (divy, tła, tekst z cieniowaniem obrystowym, obrazy itp.) były płynne i płynnie animowane przez procesor graficzny. Problem w tym, że większość programistów front-endu oddaje ten proces animacji do zewnętrznej platformy bez zwracania uwagi na semantykę. Czy jednak te podstawowe funkcje CSS3 powinny być maskowane? Oto kilka powodów, dla których warto przejmować się tymi sprawami:

  1. Przydzielanie pamięci i obciążenie obliczeniowe – jeśli skompilujesz każdy element w DOM tylko ze względu na przyspieszenie sprzętowe, następna osoba, która będzie pracowała nad Twoim kodem, może Cię dogonić i mocno pokonać.

  2. Zużycie energii – oczywiście, gdy sprzęt działa, działa też bateria. Podczas tworzenia aplikacji mobilnych deweloperzy muszą uwzględniać wiele ograniczeń urządzeń podczas pisania aplikacji mobilnych. Będzie to jeszcze bardziej powszechne, gdy producenci przeglądarek zaczną udostępniać dostęp do coraz większej liczby urządzeń.

  3. Konflikty – podczas stosowania akceleracji sprzętowej do części strony, które zostały już przyspieszone, wystąpiły problemy z działaniem. Dlatego bardzo ważne jest, aby wiedzieć, czy masz nakładające się przyspieszenia.

Aby interakcja użytkownika była płynna i zbliżona do natywnych, musimy dostosować przeglądarkę do naszych potrzeb. W idealnej sytuacji procesor na urządzeniu mobilnym powinien zająć się konfiguracją początkowej animacji, a procesor graficzny tylko składaniem różnych warstw podczas procesu animacji. Do tego służą funkcje translate3d, scale3d i translateZ – nadają one animowanym elementom własną warstwę, dzięki czemu urządzenie może płynnie renderować wszystkie elementy. Aby dowiedzieć się więcej o przyspieszonym składaniu i działaniu WebKit, przeczytaj wiele przydatnych informacjiblogu Ariya Hidayata.

Przejścia między stronami

Przyjrzyjmy się 3 najczęstszym podejściom do interakcji z użytkownikiem podczas tworzenia aplikacji mobilnej w witrynie: efektom przesuwania, przewracania i obrotu.

Aby zobaczyć, jak działa ten kod, wejdź na http://slidfast.appspot.com/slide-flip-rotate.html. Uwaga: ta wersja demonstracyjna powstała z myślą o urządzeniach mobilnych, dlatego uruchom emulator, użyj telefonu lub tabletu albo zmniejsz okno przeglądarki do maksymalnie 1024 pikseli.

Najpierw przyjrzymy się przejściom między slajdami, przekształceniom i obrotom oraz temu, jak można je przyspieszyć. Zwróć uwagę, że każda animacja zajmuje tylko 3–4 wiersze kodu CSS i JavaScriptu.

Przesuwanie

Jest to najczęstszy z 3 sposobów przejścia na strony, czyli przesuwane przejścia stron, które naśladują natywny wygląd aplikacji mobilnych. Przejście slajdu jest wywoływane, aby wprowadzić nowy obszar treści do okna widoku.

W przypadku efektu slajdu najpierw deklarujemy nasze znaczniki:

<div id="home-page" class="page">
  <h1>Home Page</h1>
</div>

<div id="products-page" class="page stage-right">
  <h1>Products Page</h1>
</div>

<div id="about-page" class="page stage-left">
  <h1>About Page</h1>
</div>

Zwróć uwagę, że mamy tutaj koncepcję stron w wersji testowej po lewej lub po prawej stronie. Może to być dowolny kierunek, ale najczęściej.

Do wyboru są teraz animacje i akceleracja sprzętowa z wykorzystaniem zaledwie kilku wierszy kodu CSS. Dopiero gdy zamienimy klasy elementów div na stronie, nastąpi faktyczna animacja.

.page {
  position: absolute;
  width: 100%;
  height: 100%;
  /*activate the GPU for compositing each page */
  -webkit-transform: translate3d(0, 0, 0);
}

translate3d(0,0,0) to tak zwana „magiczna kula”.

Gdy użytkownik kliknie element nawigacji, wykonujemy kod JavaScript, który zamienia klasy. Nie używamy żadnych platform zewnętrznych – to czysty JavaScript. .

function getElement(id) {
  return document.getElementById(id);
}

function slideTo(id) {
  //1.) the page we are bringing into focus dictates how
  // the current page will exit. So let's see what classes
  // our incoming page is using. We know it will have stage[right|left|etc...]
  var classes = getElement(id).className.split(' ');

  //2.) decide if the incoming page is assigned to right or left
  // (-1 if no match)
  var stageType = classes.indexOf('stage-left');

  //3.) on initial page load focusPage is null, so we need
  // to set the default page which we're currently seeing.
  if (FOCUS_PAGE == null) {
    // use home page
    FOCUS_PAGE = getElement('home-page');
  }

  //4.) decide how this focused page should exit.
  if (stageType > 0) {
    FOCUS_PAGE.className = 'page transition stage-right';
  } else {
    FOCUS_PAGE.className = 'page transition stage-left';
  }

  //5. refresh/set the global variable
  FOCUS_PAGE = getElement(id);

  //6. Bring in the new page.
  FOCUS_PAGE.className = 'page transition stage-center';
}

stage-left lub stage-right staje się stage-center i wymusza przesunięcie strony do widocznego obszaru pośrodku. W tym przypadku całkowicie polegamy na CSS3.

.stage-left {
  left: -480px;
}

.stage-right {
  left: 480px;
}

.stage-center {
  top: 0;
  left: 0;
}

Teraz przyjrzymy się usłudze porównywania cen, która obsługuje wykrywanie urządzeń mobilnych i ich orientację. Możemy uwzględnić każde urządzenie i każdą rozdzielczość (patrz media query resolution). W tym pokazie użyłem tylko kilku prostych przykładów, aby objąć większość widoków w orientacji poziomej i pionowej na urządzeniach mobilnych. Jest to też przydatne do stosowania akceleracji sprzętowej na poszczególnych urządzeniach. Na przykład, ponieważ wersja WebKit na komputery stacjonarne przyspiesza wszystkie przekształcone elementy (niezależnie od tego, czy są to elementy 2D czy 3D), warto utworzyć zapytanie o multimedia i wykluczyć przyspieszanie na tym poziomie. Pamiętaj, że sztuczki związane z akceleracją sprzętową w Androidzie Froyo 2.2 lub nowszym nie poprawiają szybkości. Całość kompozycji odbywa się w oprogramowaniu.

/* iOS/android phone landscape screen width*/
@media screen and (max-device-width: 480px) and (orientation:landscape) {
  .stage-left {
    left: -480px;
  }

  .stage-right {
    left: 480px;
  }

  .page {
    width: 480px;
  }
}

Odwracanie

Na urządzeniach mobilnych przewracanie oznacza przesuwanie palcem po ekranie. W tym celu używamy prostego kodu JavaScript do obsługi tego zdarzenia na urządzeniach z iOS i Androidem (opartych na silniku WebKit).

Zobacz, jak to działa: http://slidfast.appspot.com/slide-flip-rotate.html.

Gdy zajmujesz się zdarzeniami dotykowymi i przechodami, najpierw musisz określić bieżącą pozycję elementu. Więcej informacji o WebKitCSSMatrix znajdziesz w tym dokumencie.

function pageMove(event) {
  // get position after transform
  var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
  var pagePosition = curTransform.m41;
}

Ponieważ do przewracania stron używamy przejścia wygaszania CSS3, zwykłe element.offsetLeft nie będzie działać.

Następnie chcemy ustalić, w jaką stronę użytkownik przewija stronę, i ustawić wartość progową dla zdarzenia (przechodzenie do innej strony).

if (pagePosition >= 0) {
 //moving current page to the right
 //so means we're flipping backwards
   if ((pagePosition > pageFlipThreshold) || (swipeTime < swipeThreshold)) {
     //user wants to go backward
     slideDirection = 'right';
   } else {
     slideDirection = null;
   }
} else {
  //current page is sliding to the left
  if ((swipeTime < swipeThreshold) || (pagePosition < pageFlipThreshold)) {
    //user wants to go forward
    slideDirection = 'left';
  } else {
    slideDirection = null;
  }
}

Zauważysz też, że mierzymy także swipeTime w milisekundach. Dzięki temu zdarzenie nawigacji może zostać wywołane, gdy użytkownik szybko przesunie palcem po ekranie, aby przewinąć stronę.

Aby ustawić pozycję strony i uzyskać naturalny wygląd animacji podczas dotykania ekranu palcem, po każdym zdarzeniu używamy przejść CSS3.

function positionPage(end) {
  page.style.webkitTransform = 'translate3d('+ currentPos + 'px, 0, 0)';
  if (end) {
    page.style.WebkitTransition = 'all .4s ease-out';
    //page.style.WebkitTransition = 'all .4s cubic-bezier(0,.58,.58,1)'
  } else {
    page.style.WebkitTransition = 'all .2s ease-out';
  }
  page.style.WebkitUserSelect = 'none';
}

Próbowaliśmy użyć funkcji cubic-bezier, aby przejścia wyglądały jak w natywności, ale ostatecznie udało się to osiągnąć za pomocą funkcji ease-out.

Aby nawigacja działała, musimy wywołać zdefiniowane wcześniej metody slideTo(), które zostały użyte w poprzednim pokazie.

track.ontouchend = function(event) {
  pageMove(event);
  if (slideDirection == 'left') {
    slideTo('products-page');
  } else if (slideDirection == 'right') {
    slideTo('home-page');
  }
}

Obracanie

Teraz przyjrzyjmy się animacji obrotu używanej w tym pokazie. W każdej chwili możesz obrócić wyświetlaną stronę o 180 stopni, aby zobaczyć jej odwrotną stronę. Aby to zrobić, kliknij opcję „Kontakt” w menu. Przypisanie klasy przejścia onclick wymaga tylko kilku linii kodu CSS i niewielkiej ilości kodu JavaScript. UWAGA: w większości wersji Androida przejście z obrótem nie jest renderowane prawidłowo, ponieważ brakuje mu możliwości przekształcenia 3D w CSS. Zamiast ignorować przerzucanie, Android powoduje, że strona „przewraca się” przez obrócenie. Zalecamy nieznaczne korzystanie z tej zmiany, dopóki poziom obsługi nie poprawi się.

Znaczniki (podstawowe pojęcie dotyczące przedniej i tylnej strony):

<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
    <div id="contact-page" class="page">
        <h1>Contact Page</h1>
    </div>
</div>

Kod JavaScript:

function flip(id) {
  // get a handle on the flippable region
  var front = getElement('front');
  var back = getElement('back');

  // again, just a simple way to see what the state is
  var classes = front.className.split(' ');
  var flipped = classes.indexOf('flipped');

  if (flipped >= 0) {
    // already flipped, so return to original
    front.className = 'normal';
    back.className = 'flipped';
    FLIPPED = false;
  } else {
    // do the flip
    front.className = 'flipped';
    back.className = 'normal';
    FLIPPED = true;
  }
}

Kod CSS:

/*----------------------------flip transition */
#back,
#front {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  -webkit-transition-duration: .5s;
  -webkit-transform-style: preserve-3d;
}

.normal {
  -webkit-transform: rotateY(0deg);
}

.flipped {
  -webkit-user-select: element;
  -webkit-transform: rotateY(180deg);
}

Debugowanie akceleracji sprzętowej

Teraz, gdy omówiliśmy podstawowe przejścia, przyjrzyjmy się ich mechanizmom działania i składaniu.

Aby rozpocząć tę magiczną sesję debugowania, uruchom kilka przeglądarek i wybrany przez siebie IDE. Najpierw uruchom Safari z poziomu wiersza poleceń, aby skorzystać z niektórych zmiennych środowiskowych do debugowania. Używam Maca, więc polecenia mogą się różnić w zależności od systemu operacyjnego. Otwórz terminal i wpisz:

  • $> eksportuj: CA_color_OPAQUE=1
  • $> eksportuj CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

Spowoduje to uruchomienie Safari z kilkoma narzędziami do debugowania. Pole CA_color_OPAQUE pokazuje, które elementy są rzeczywiście komponowane lub przyspieszane. CA_LOG_MEMORY_USAGE pokazuje, ile pamięci wykorzystujemy podczas wysyłania operacji rysowania do magazynu bazowego. Dzięki temu dowiesz się, jak bardzo obciążasz urządzenie mobilne i co może być przyczyną wyczerpywania się baterii urządzenia docelowego.

Uruchom teraz Chrome, aby zobaczyć informacje o dobrej liczbie klatek na sekundę:

  1. Otwórz przeglądarkę Google Chrome.
  2. W pasku adresu wpisz about:flags.
  3. Przewiń kilka elementów w dół i kliknij „Włącz”, aby uzyskać licznik FPS.

Jeśli otworzysz tę stronę w ulepszonej wersji Chrome, w lewym górnym rogu zobaczysz czerwony licznik FPS.

Liczba klatek na sekundę w Chrome

W ten sposób wiemy, że akceleracja sprzętowa jest włączona. Pozwala nam to też określić, jak działa animacja i czy występują jakieś wycieki (ciągle działające animacje, które powinny zostać zatrzymane).

Innym sposobem wizualizacji akceleracji sprzętowej jest otwarcie tej samej strony w Safari (z uwzględnieniem zmiennych środowiskowych, o których wspomniałem powyżej). Każdy przyspieszony element DOM ma czerwony odcień. Dzięki temu widzimy dokładnie, co jest łączone na poszczególnych warstwach. Zwróć uwagę, że biała nawigacja nie jest czerwona, ponieważ nie jest przyspieszona.

Składany kontakt

W Chrome podobne ustawienie jest dostępne w sekcji about:flags „Składane krawędzie warstwy renderowania”.

Innym świetnym sposobem na poznanie skomponowanych warstw jest wyświetlenie wersji demonstracyjnej funkcji spadających liści WebKit podczas stosowania tego moda.

omposed Leaves

Aby dokładnie poznać wydajność sprzętu graficznego naszej aplikacji, przyjrzyjmy się zużyciu pamięci. Tutaj widzimy, że przesyłamy 1,38 MB instrukcji rysowania do buforów CoreAnimation w systemie Mac OS. Pamięci buforowe animacji w Core są współdzielone przez OpenGL ES i GPU, aby tworzyć ostateczne piksele widoczne na ekranie.

Coreanimation 1

Gdy po prostu zmienimy rozmiar okna przeglądarki lub je zmaksymalizujemy, zobaczymy, że pamięć również się zwiększy.

Coreanimation 2

Dzięki temu możesz sprawdzić, jak pamięć jest wykorzystywana na urządzeniu mobilnym, tylko wtedy, gdy zmienisz rozmiar przeglądarki na odpowiedni. Jeśli debugujesz lub testujesz środowisko na iPhone’a, zmień rozmiar na 480 x 320 pikseli. Wiemy już, jak działa akceleracja sprzętowa i co jest potrzebne do debugowania. Nie ma o nim wiele, ale patrzenie, jak działają bufory pamięci GPU, pozwala spojrzeć na całość z różnych perspektyw.

Sceny zza kulis: pobieranie i przechowywanie w pamięci podręcznej

Teraz czas na zwiększenie wydajności pamięci podręcznej stron i zasobów. Podobnie jak w przypadku JQuery Mobile i podobnych frameworków, będziemy pobierać strony w tle i przechowywać je w pamięci podręcznej za pomocą równoczesnych wywołań AJAX.

Przyjrzyjmy się kilku głównym problemom związanym z internetem mobilnym i powodom, dla których musimy to robić:

  • Pobieranie: pobieranie stron z wyprzedzeniem umożliwia użytkownikom korzystanie z aplikacji w trybie offline i eliminuje oczekiwanie między działaniami związanymi z przemieszczaniem się po aplikacji. Oczywiście nie chcemy ograniczać przepustowości urządzenia, gdy jest ono online, więc musimy oszczędnie korzystać z tej funkcji.
  • Buforowanie: chcemy stosować podejście równoległe lub asynchroniczne podczas pobierania i przechowywania w buforze tych stron. Musimy też używać localStorage (ponieważ jest dobrze obsługiwany na różnych urządzeniach), który niestety nie jest asynchroniczny.
  • AJAX i analiza odpowiedzi: użycie funkcji innerHTML() do wstawienia odpowiedzi AJAX do DOM jest niebezpieczne (i niezawodne). Zamiast tego używamy niezawodnego mechanizmu do wstawiania odpowiedzi AJAX i obsługi równoczesnych wywołań. Do parsowania xhr.responseText wykorzystujemy też niektóre nowe funkcje HTML5.

Opierając się na kodzie z prezentacji dotyczącej slajdów, przerzucania i obracania, zaczynamy od dodania kilku dodatkowych stron i umieszczenia do nich linków. Następnie przeanalizujemy linki i na bieżąco utworzymy przejścia.

Ekran główny iPhone&#39;a

Tutaj możesz obejrzeć demonstrację funkcji pobierania i przechowywania w pamięci podręcznej

Jak widzisz, wykorzystujemy tutaj znaczniki semantyczne. To tylko link do innej strony. Strona podrzędna ma taką samą strukturę węzła lub klasy jak strona nadrzędna. Możemy pójść o krok dalej i użyć atrybutu data-* dla węzłów „page” itp. A tutaj jest strona szczegółowa (podrzędna) znajdująca się w oddzielnym pliku HTML (/demo2/home-detail.html), który zostanie załadowany, zapisany w pamięci podręcznej i skonfigurowany do przejścia podczas wczytywania aplikacji.

<div id="home-page" class="page">
  <h1>Home Page</h1>
  <a href="demo2/home-detail.html" class="fetch">Find out more about the home page!</a>
</div>

Teraz przyjrzyjmy się kodom JavaScript. Ze względu na prostotę nie uwzględniam w kodzie żadnych pomocników ani optymalizacji. Wszystko, co tutaj robimy, to przechodzenie w pętli przez określony tablica węzłów DOM, aby wydobyć linki do pobrania i zapisania w pamięci podręcznej. Uwaga: w tym pokazie metoda fetchAndCache() jest wywoływana po załadowaniu strony. W następnej sekcji zmienimy ten kod, gdy wykryjemy połączenie z siecią i ustalimy, kiedy należy go wywołać.

var fetchAndCache = function() {
  // iterate through all nodes in this DOM to find all mobile pages we care about
  var pages = document.getElementsByClassName('page');

  for (var i = 0; i < pages.length; i++) {
    // find all links
    var pageLinks = pages[i].getElementsByTagName('a');

    for (var j = 0; j < pageLinks.length; j++) {
      var link = pageLinks[j];

      if (link.hasAttribute('href') &amp;&amp;
      //'#' in the href tells us that this page is already loaded in the DOM - and
      // that it links to a mobile transition/page
         !(/[\#]/g).test(link.href) &amp;&amp;
        //check for an explicit class name setting to fetch this link
        (link.className.indexOf('fetch') >= 0))  {
         //fetch each url concurrently
         var ai = new ajax(link,function(text,url){
              //insert the new mobile page into the DOM
             insertPages(text,url);
         });
         ai.doGet();
      }
    }
  }
};

Zapewnienie prawidłowego asynchronicznego przetwarzania wstecznego za pomocą obiektu „AJAX”. Bardziej zaawansowane wyjaśnienie korzystania z localStorage w wywołaniu AJAX znajdziesz w artykule Praca offline z HTML5 offline. W tym przykładzie pokazano podstawowe wykorzystanie buforowania w przypadku każdego żądania i udostępnianie obiektów z pamięci podręcznej, gdy serwer zwraca odpowiedź inną niż pomyślna (200).

function processRequest () {
  if (req.readyState == 4) {
    if (req.status == 200) {
      if (supports_local_storage()) {
        localStorage[url] = req.responseText;
      }
      if (callback) callback(req.responseText,url);
    } else {
      // There is an error of some kind, use our cached copy (if available).
      if (!!localStorage[url]) {
        // We have some data cached, return that to the callback.
        callback(localStorage[url],url);
        return;
      }
    }
  }
}

Niestety localStorage używa kodowania znaków UTF-16, więc każdy bajt jest przechowywany jako 2 bajty, co zwiększa limit miejsca z 5 MB do 2,6 MB. Cały powód pobierania tych stron/znaczników do pamięci podręcznej poza zakresem pamięci podręcznej aplikacji został przedstawiony w następnej sekcji.

Dzięki ostatnim ulepszeniom elementu iframe w HTML5 możemy w prosty i skuteczny sposób przeanalizować responseText, które otrzymujemy z wywołania AJAX. Istnieje wiele parserów JavaScript liczących 3000 wierszy i wyrażeń regularnych, które usuwają tagi skryptu itd. Ale dlaczego nie pozwolić przeglądarce na robienie tego, co potrafi najlepiej? W tym przykładzie zapiszemy element responseText w tymczasowym ukrytym iframe. Używamy atrybutu „sandbox” języka HTML5, który wyłącza skrypty i oferuje wiele funkcji zabezpieczeń...

Z specyfikacji: Jeśli atrybut piaskownicy jest określony, umożliwia on ustawienie dodatkowych ograniczeń dla treści hostowanych przez iframe. Jego wartość musi być niezmiennym zbiorem unikalnych tokenów rozdzielanych spacjami, które są nieczułe na wielkość liter w kodzie ASCII. Dozwolone wartości to allow-forms, allow-same-origin, allow-scripts i allow-top-navigation. Gdy atrybut jest skonfigurowany, treść jest traktowana jako pochodząca z unikalnego źródła, formularze i skrypty są wyłączone, linki nie mogą być kierowane na inne konteksty przeglądania, a wtyczki są wyłączone.

var insertPages = function(text, originalLink) {
  var frame = getFrame();
  //write the ajax response text to the frame and let
  //the browser do the work
  frame.write(text);

  //now we have a DOM to work with
  var incomingPages = frame.getElementsByClassName('page');

  var pageCount = incomingPages.length;
  for (var i = 0; i < pageCount; i++) {
    //the new page will always be at index 0 because
    //the last one just got popped off the stack with appendChild (below)
    var newPage = incomingPages[0];

    //stage the new pages to the left by default
    newPage.className = 'page stage-left';

    //find out where to insert
    var location = newPage.parentNode.id == 'back' ? 'back' : 'front';

    try {
      // mobile safari will not allow nodes to be transferred from one DOM to another so
      // we must use adoptNode()
      document.getElementById(location).appendChild(document.adoptNode(newPage));
    } catch(e) {
      // todo graceful degradation?
    }
  }
};

Safari prawidłowo odrzuca niejawne przeniesienie węzła z jednego dokumentu do innego. Jeśli nowy węzeł podrzędny został utworzony w innym dokumencie, wystąpi błąd. Używamy więc nazwy adoptNode i wszystko jest w porządku.

Dlaczego iframe? Dlaczego nie korzystamy tylko z innerHTML? Mimo że innerHTML jest teraz częścią specyfikacji HTML5, wstawianie odpowiedzi serwera (złej lub dobrej) w niesprawdzonym obszarze jest niebezpieczną praktyką. Podczas pisania tego artykułu nie udało mi się znaleźć nikt używający czegokolwiek innego niż innerHTML. Wiem, że JQuery używa tego w swojej podstawie z dodatkowym dołączaniem tylko w wyjątkowych przypadkach. Jest też używana przez JQuery Mobile. Nie przeprowadziłem jednak żadnych szczegółowych testów pod kątem innerHTML „przestaje działać losowo”, ale bardzo ciekawie byłoby zobaczyć, jak wpłynie to na wszystkie platformy. Ciekawi mnie też, które podejście jest skuteczniejsze. Słyszałem różne opinie na ten temat.

Wykrywanie, obsługa i profilowanie typu sieci

Teraz, gdy mamy możliwość buforowania (lub przewidującego buforowania) naszej aplikacji internetowej, musimy udostępnić odpowiednie funkcje wykrywania połączeń, które sprawią, że nasza aplikacja będzie mądrzejsza. W tym przypadku tworzenie aplikacji mobilnych staje się bardzo wrażliwe na tryby online/offline i szybkość połączenia. Wpisz Network Information API. Za każdym razem, gdy pokazuję tę funkcję w prezentacji, ktoś z publiczności podnosi rękę i pyta: „Do czego mogę jej używać?”. Oto więc sposób na skonfigurowanie bardzo inteligentnej internetowej aplikacji mobilnej.

Najpierw nudny scenariusz oparty na zdrowym rozsądku. Podczas korzystania z Internetu na urządzeniu mobilnym w szybkim pociągu sieć może się w różnych momentach wyłączyć, a różne regiony mogą obsługiwać różne prędkości transmisji (np. HSPA lub 3G mogą być dostępne w niektórych obszarach miejskich, ale obszary odległe mogą obsługiwać znacznie wolniejsze technologie 2G). Poniższy kod rozwiązuje większość scenariuszy dotyczących połączeń.

Ten kod zapewnia:

  • Dostęp offline za pomocą applicationCache.
  • Wykrywanie, czy strona jest dodana do zakładek i czy jest offline.
  • Wykrywa, kiedy następuje przejście z trybu offline do trybu online i odwrotnie.
  • Wykrywa wolne połączenia i pobiera treści na podstawie typu sieci.

Wszystkie te funkcje wymagają bardzo małej ilości kodu. Najpierw wykrywają zdarzenia i scenariusze wczytywania:

window.addEventListener('load', function(e) {
 if (navigator.onLine) {
  // new page load
  processOnline();
 } else {
   // the app is probably already cached and (maybe) bookmarked...
   processOffline();
 }
}, false);

window.addEventListener("offline", function(e) {
  // we just lost our connection and entered offline mode, disable eternal link
  processOffline(e.type);
}, false);

window.addEventListener("online", function(e) {
  // just came back online, enable links
  processOnline(e.type);
}, false);

W powyższych zdarzeniach EventListener musimy określić, czy kod jest wywoływany z wydarzenia czy z rzeczywistego żądania lub odświeżenia strony. Głównym powodem jest to, że zdarzenie body onload nie zostanie wywołane podczas przełączania się między trybami online i offline.

Następnie wykonujemy prostą kontrolę zdarzenia ononline lub onload. Ten kod resetuje wyłączone linki po przejściu z trybu offline do trybu online, ale jeśli aplikacja jest bardziej zaawansowana, możesz wstawić logikę, która wznowi pobieranie treści lub obsłuży UX w przypadku przerywanych połączeń.

function processOnline(eventType) {

  setupApp();
  checkAppCache();

  // reset our once disabled offline links
  if (eventType) {
    for (var i = 0; i < disabledLinks.length; i++) {
      disabledLinks[i].onclick = null;
    }
  }
}

To samo dotyczy processOffline(). W tym przypadku trzeba skonfigurować aplikację, aby działała w trybie offline, i spróbować odzyskać wszystkie transakcje realizowane za kulisami. Ten kod wykrywa wszystkie linki zewnętrzne i je wyłącza, co spowoduje, że użytkownicy będą na zawsze uwięzieni w naszej aplikacji offline. Muhahaha!

function processOffline() {
  setupApp();

  // disable external links until we come back - setting the bounds of app
  disabledLinks = getUnconvertedLinks(document);

  // helper for onlcick below
  var onclickHelper = function(e) {
    return function(f) {
      alert('This app is currently offline and cannot access the hotness');return false;
    }
  };

  for (var i = 0; i < disabledLinks.length; i++) {
    if (disabledLinks[i].onclick == null) {
      //alert user we're not online
      disabledLinks[i].onclick = onclickHelper(disabledLinks[i].href);

    }
  }
}

OK, przejdźmy do rzeczy. Teraz, gdy nasza aplikacja wie, w jakim jest stanie, może też sprawdzić typ połączenia, gdy jest online, i odpowiednio je dostosować. W komentarzach do każdego połączenia znajdziesz listę typowych dostawców z Ameryki Północnej w celach związanych z pobieraniem i opóźnieniami.

function setupApp(){
  // create a custom object if navigator.connection isn't available
  var connection = navigator.connection || {'type':'0'};
  if (connection.type == 2 || connection.type == 1) {
      //wifi/ethernet
      //Coffee Wifi latency: ~75ms-200ms
      //Home Wifi latency: ~25-35ms
      //Coffee Wifi DL speed: ~550kbps-650kbps
      //Home Wifi DL speed: ~1000kbps-2000kbps
      fetchAndCache(true);
  } else if (connection.type == 3) {
  //edge
      //ATT Edge latency: ~400-600ms
      //ATT Edge DL speed: ~2-10kbps
      fetchAndCache(false);
  } else if (connection.type == 2) {
      //3g
      //ATT 3G latency: ~400ms
      //Verizon 3G latency: ~150-250ms
      //ATT 3G DL speed: ~60-100kbps
      //Verizon 3G DL speed: ~20-70kbps
      fetchAndCache(false);
  } else {
  //unknown
      fetchAndCache(true);
  }
}

W procesie fetchAndCache można wprowadzić wiele zmian, ale w tym przypadku po prostu poprosiłem o pobieranie zasobów asynchronicznie (true) lub synchronicznie (false) w przypadku danego połączenia.

Oś czasu żądania opartego na Edge (synchronicznym)

Edge Sync

Czas odpowiedzi na żądanie WIFI (asynchroniczne)

Asynchroniczne Wi-Fi

Umożliwi to co najmniej pewną metodę dostosowywania wygody użytkowników na podstawie powolnych lub szybkich połączeń. Nie jest to w żaden sposób rozwiązanie uniwersalne. Kolejnym zadaniem jest wyświetlenie modalnego okna wczytywania po kliknięciu linku (przy wolnym połączeniu), gdy aplikacja nadal może pobierać stronę z linkiem w tle. Najważniejsze jest tutaj skrócenie opóźnień przy jednoczesnym wykorzystaniu pełnych możliwości połączenia użytkownika dzięki najnowszym i najlepszym funkcjom HTML5. Tutaj możesz obejrzeć prezentację wykrywania sieci

Podsumowanie

To dopiero początek drogi do rozwoju mobilnych aplikacji HTML5. Teraz widzisz bardzo proste i podstawowe podstawy „ramy” mobilnej stworzonej wyłącznie w języku HTML5 i z wykorzystaniem technologii pomocniczych. Uważam, że deweloperzy powinni pracować nad tymi funkcjami i rozwiązywać je w sposób u podstaw, a nie za pomocą kodu.