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

Wesley Hales
Wesley Hales

Wstęp

Szybkie odświeżanie, nieregularne przejścia stron i okresowe opóźnienia zdarzeń kliknięcia to tylko niektóre z problemów współczesnych środowisk mobilnych. Deweloperzy starają się jak najbardziej zbliżyć się do natywnego interfejsu, ale często spotykają się z tym przeszkodami przez ataki hakerskie, resetowanie i sztywne platformy.

W tym artykule omawiamy absolutne minimalne wymagania umożliwiające stworzenie mobilnej aplikacji internetowej w formacie HTML5. Najważniejsze jest ukrycie ukrytych zawiłości, które kryją się w dzisiejszych środowiskach mobilnych. Zobaczysz minimalistyczne podejście (z wykorzystaniem podstawowych interfejsów API HTML5) i podstawowe podstawowe informacje, które pozwolą Ci napisać własną strukturę lub pomóc w pracy nad tym, z którego obecnie korzystasz.

Akceleracja sprzętowa

Zwykle procesory graficzne obsługują szczegółowe modele 3D i diagramy CAD, ale w tym przypadku chcemy, aby nasze podstawowe rysunki (elementy div, tła, tekst z cieniami, obrazy itp.) wyglądały płynnie i płynnie animowały za pomocą GPU. Niestety większość programistów interfejsów użytkownika udostępnia ten proces animacji do platformy zewnętrznej, nie przejmując się jej semantyką. Czy jednak te podstawowe funkcje CSS3 powinny być zamaskowane? Oto kilka powodów, dla których dbanie o to jest tak ważne:

  1. Alokacja pamięci i obciążenia obliczeniowe – jeśli komponujesz każdy element w DOM tylko dla akceleracji sprzętowej, kolejna osoba pracująca nad Twoim kodem może Cię ścigać i poważnie Cię pokonać.

  2. Pobór energii – oczywiście, gdy włącza się sprzęt, to samo dzieje się z baterią. Podczas tworzenia aplikacji na urządzenia mobilne deweloperzy muszą brać pod uwagę różne ograniczenia urządzeń. Stanie się tak w miarę uzyskiwania przez twórców przeglądarek dostępu do coraz większej liczby urządzeń.

  3. Konflikty – podczas próby zastosowania akceleracji sprzętowej do części strony, które były już przyspieszone, wystąpiły błędy. Dlatego informacja o pokrywaniu się przyspieszeń jest bardzo ważna.

Aby interakcja z użytkownikami była płynna i jak najbardziej zbliżona do natywnej, musimy zadbać o to, aby przeglądarka działała za nas. Najlepiej byłoby, gdyby procesor urządzenia mobilnego skonfigurował początkową animację, a to on odpowiadał tylko za komponowanie różnych warstw w trakcie animacji. Właśnie takie są zagadnienie Translate3d, scale3d i translateZ Aby dowiedzieć się więcej o przyspieszonym komponowaniu i o działaniu WebKit, Ariya Hidayat ma na swoim blogu wiele przydatnych informacji.

Przejścia między stronami

Przyjrzyjmy się 3 najpopularniejszym metodom interakcji użytkowników podczas tworzenia aplikacji internetowej: przesuwu, odwrócenia i obrotu.

Działanie kodu można zobaczyć tutaj: http://slidfast.appspot.com/slide-flip-rotate.html. (Uwaga: ta wersja demonstracyjna została stworzona z myślą o urządzeniu mobilnym, więc uruchom emulator, użyj telefonu lub tabletu albo zmniejsz rozmiar okna przeglądarki do maksymalnie 1024 pikseli).

Najpierw przypomnimy sobie przejścia slajdów, odwracania i obrotów oraz sposób ich przyspieszenia. Zwróć uwagę, że każda animacja zajmuje tylko 3 lub 4 wiersze kodu CSS i JavaScript.

Przesuwne

Najpopularniejszy z trzech sposobów przejścia na stronie przesuwane przejścia naśladują naturalny charakter aplikacji mobilnych. Przejście slajdu jest wywoływane w celu wyświetlenia nowego obszaru treści w widoku.

Aby uzyskać efekt slajdu, najpierw zadeklarujemy 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ę na koncepcję umiejscowienia stron w lewo lub w prawo. Zasadniczo może to być dowolny kierunek, ale ten występuje najczęściej.

Mamy teraz animację i akcelerację sprzętową za pomocą kilku wierszy kodu CSS. Animacje mają miejsce, gdy zamieniamy klasy w elementach div na stronie.

.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 tzw. najskuteczniejszy sposób.

Gdy użytkownik kliknie element nawigacji, wykonujemy poniższy kod JavaScript, aby zamienić klasy. Nie są używane żadne platformy innych firm, to zwykły 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 zmienia się w stage-center i wymusza na stronie przesuwanie się do środka widoku. Do wykonania najcięższych zadań w pełni polegamy na CSS3.

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

.stage-right {
  left: 480px;
}

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

Teraz przyjrzyjmy się CSS, który obsługuje wykrywanie i orientację urządzeń mobilnych. Mogliśmy zająć się wszystkimi urządzeniami i rozwiązaniami (zobacz rozwiązania zapytań o multimedia). W tej prezentacji wykorzystam tylko kilka prostych przykładów, aby uwzględnić większość widoków w orientacji pionowej i poziomej na urządzeniach mobilnych. Jest to też przydatne, gdy chcesz zastosować akcelerację sprzętową na urządzenie. Na przykład WebKit na komputerze przyspiesza wszystkie przekształcone elementy (niezależnie od tego, czy jest to 2D czy 3D), więc warto utworzyć zapytanie o media i wykluczyć akcelerację na tym poziomie. Pamiętaj, że sztuczki związane z akceleracją sprzętową nie gwarantują żadnej poprawy szybkości w systemie Android Froyo 2.2 lub nowszym. Wszystkie komponowanie 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 odwrócenie strony nazywa się przesunięciem strony poza ekran. Używamy tu prostego kodu JavaScript do obsługi tego zdarzenia na urządzeniach z iOS i Androidem (opartym na pakiecie WebKit).

Zobacz ją w praktyce: http://slidfast.appspot.com/slide-flip-rotate.html.

W przypadku zdarzeń dotknięcia i przejściach pierwszym krokiem jest ustalenie bieżącej pozycji 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;
}

Na potrzeby przewrócenia strony używamy przejścia z efektem wygładzania CSS3, więc standardowe ustawienie element.offsetLeft nie będzie działać.

Następnie musimy ustalić, w którym kierunku odwraca użytkownik, i ustalić próg wystąpienia zdarzenia (nawigacji 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 swipeTime w milisekundach. Dzięki temu zdarzenie nawigacji może być uruchamiane, gdy użytkownik szybko przesunie ekran, aby przewrócić stronę.

Aby określić położenie strony i sprawić, że animacje będą wyglądać natywne, gdy palec dotyka ekranu, po każdym wywołaniu zdarzenia 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óbowałam pobawić się kształtem sześciennego beziera, aby uzyskać najlepszy efekt naturalny przejścia, ale efekt końcowy okazał się słabszy.

Na koniec, aby ułatwić nawigację, musimy wywołać wcześniej zdefiniowane metody slideTo(), które były używane w ostatniej wersji demonstracyjnej.

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

Obracanie

Przyjrzyjmy się teraz animacji obrotu używaną w tej wersji demonstracyjnej. W każdej chwili możesz obrócić oglądaną stronę o 180 stopni, aby odsłonić jej tył, klikając opcję „Kontakt” w menu „Kontakt”. Aby przypisać klasę przejścia onclick, wystarczy kilka wierszy kodu CSS i JavaScript. UWAGA: przejście z rotacją nie jest prawidłowo renderowane w większości wersji Androida, ponieważ nie ma w nim funkcji przekształcania CSS 3D. Niestety, zamiast zignorować przerzucenie, Android sprawia, że strona się obraca, zamiast ją odwracać. Zalecamy oszczędne korzystanie z tego przejścia, dopóki obsługa klienta nie ulegnie poprawie.

Znaczniki (podstawowa koncepcja przód i tył):

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

Usługa porównywania cen:

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

Skoro mamy już omówione podstawowe przejścia, przyjrzyjmy się mechanizmom ich działania.

Aby przeprowadzić tę magiczną sesję debugowania, uruchom kilka przeglądarek i wybrany przez Ciebie IDE. Najpierw uruchom Safari z poziomu wiersza poleceń, aby skorzystać ze zmiennych środowiskowych 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 przeglądarki Safari z kilkoma pomocnikami debugowania. Element CA_COLOR_OPAQUE pokazuje, które elementy są rzeczywiście skomponowane lub przyspieszone. Element CA_LOG_MEMORY_USAGE pokazuje, ile pamięci używamy podczas wysyłania operacji rysowania do magazynu kopii zapasowych. Dowiesz się z niego dokładnie, jakie obciążenie jest obciążane urządzenie mobilne, a także uzyskasz wskazówki, jak użycie GPU może obciążać baterię urządzenia docelowego.

Teraz uruchom Chrome, aby zobaczyć informacje o wysokiej liczbie klatek na sekundę (FPS):

  1. Otwórz przeglądarkę Google Chrome.
  2. Na pasku adresu URL wpisz about:flags.
  3. Przewiń kilka elementów w dół i kliknij „Włącz” w sekcji Licznik FPS.

Jeśli wyświetlisz tę stronę w ulepszonej wersji Chrome, w lewym górnym rogu zobaczysz czerwony licznik klatek na sekundę.

FPS w Chrome

Dzięki temu wiemy, że akceleracja sprzętowa jest włączona. Daje nam to również pojęcie o tym, jak przebiega animacja i czy występują jakieś przecieki (ciągłe animacje, które należy zatrzymać).

Innym sposobem zwizualizowania przyspieszenia sprzętowego jest otwarcie tej samej strony w Safari (ze zmiennymi środowiskowymi, o których wspomnieliśmy powyżej). Każdy przyspieszony element DOM ma czerwony odcień. To pokazuje dokładnie, co jest komponowane w warstwie. Zwróć uwagę, że biała nawigacja nie jest czerwona, ponieważ nie jest przyspieszone.

Skomponowany kontakt

Podobne ustawienie dla Chrome jest też dostępne w sekcji about:flags „Obramowania skomponowanej warstwy renderowania”.

Innym świetnym sposobem na obejrzenie skomponowanych warstw jest obejrzenie prezentacji opadających liści w WebKit po zastosowaniu tego moda.

zebrane liście

A na koniec, aby w pełni zrozumieć wydajność sprzętową grafiki naszej aplikacji, zobaczmy, jak jest wykorzystywana pamięć. Widzimy, że przenosimy 1,38 MB instrukcji rysowania do buforów CoreAnimation w Mac OS. Bufory pamięci głównej animacji są współdzielone przez platformę OpenGL ES i GPU, aby tworzyć ostatnie piksele widoczne na ekranie.

Coreanimation 1

Gdy po prostu zmieniamy rozmiar okna przeglądarki lub je zmaksymalizujemy, powiększa się również pamięć.

Coreanimation 2

Pozwala to zorientować się, jak jest wykorzystywana pamięć urządzenia mobilnego tylko wtedy, gdy rozmiar przeglądarki zostanie zmieniony na odpowiednie wymiary. Jeśli debugujesz lub testujesz środowiska iPhone'a, zmień rozmiar na 480 na 320 pikseli. Wiemy już, jak działa akceleracja sprzętowa i co jest potrzebne do debugowania. Nie trzeba się niczym przejmować, ale patrzenie na działające bufory pamięci GPU to naprawdę świetna perspektywa.

Za kulisami: pobieranie i buforowanie

Czas przejść na wyższy poziom pamięci podręcznej stron i zasobów. Podobnie jak w przypadku JQuery Mobile i podobnych platform, będziemy pobierać z wyprzedzeniem nasze strony i zapisywać je w pamięci podręcznej z równoczesnymi wywołaniami AJAX.

Porozmawiajmy o kilku podstawowych problemach z internetem mobilnym i o tym, dlaczego jest to konieczne:

  • Pobieranie: pobieranie z wyprzedzeniem pozwala użytkownikom przejść do trybu offline w aplikacji i nie wymaga czekania między działaniami nawigacyjnymi. Oczywiście nie chcemy ograniczać przepustowości urządzenia, gdy jest ono online, dlatego musimy korzystać z tej funkcji z umiarem.
  • Zapisywanie w pamięci podręcznej – także na potrzeby pobierania i zapisywania stron w pamięci podręcznej w trybie równoczesnym lub asynchronicznym. Musimy też korzystać z metody localStorage (ponieważ jest dobrze obsługiwana przez różne urządzenia), co niestety nie działa asynchronicznie.
  • technologii AJAX i analizowania odpowiedzi: korzystanie z metody innerHTML() do wstawiania odpowiedzi AJAX do modelu DOM jest niebezpieczne (i niezawodne?). Do wstawiania odpowiedzi AJAX i obsługi równoczesnych wywołań używamy niezawodnego mechanizmu. Korzystamy też z nowych funkcji HTML5 do analizowania składni xhr.responseText.

Korzystając z kodu z prezentacji slajdów, odwracania i obracania, zaczynamy od dodania kilku dodatkowych stron i linków do nich. Następnie analizujemy linki i na bieżąco tworzymy przejścia.

Ekran główny iPhone&#39;a

Wyświetl prezentację pobierania i pamięci podręcznej.

Jak widać, używamy tutaj znaczników semantycznych. Tylko link do innej strony. Strona podrzędna ma taką samą strukturę węzłów/klas jak strona nadrzędna. Możemy pójść o krok dalej i użyć atrybutu data-* dla węzłów „strona” itp. A oto strona z informacjami (podrzędna) w osobnym pliku HTML (/demo2/home-detail.html), który będzie wczytywany, przechowywany w pamięci podręcznej i skonfigurowany pod kątem 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>

Przyjrzyjmy się teraz skryptowi JavaScript. Dla uproszczenia zostawiam w kodzie wszystkie narzędzia pomocnicze i optymalizacje. Teraz robimy to w pętli przez określoną tablicę węzłów DOM, aby wyszukać linki do pobrania i zapamiętywać w pamięci podręcznej. Uwaga: w tej wersji demonstracyjnej metoda fetchAndCache() jest wywoływana podczas wczytywania strony. W następnej sekcji przerabiamy to, gdy wykryjemy połączenie sieciowe i określimy, kiedy należy je 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();
      }
    }
  }
};

Korzystamy z obiektu „AJAX”, aby zapewnić prawidłowe asynchroniczne przetwarzanie końcowe. Opis używania parametru localStorage w wywołaniu AJAX oraz pracy poza siecią dzięki HTML5 offline znajdziesz w artykule Praca poza siecią z zasobami HTML5 offline. W tym przykładzie widać podstawowe użycie buforowania w przypadku każdego żądania i udostępnianie obiektów z pamięci podręcznej, gdy serwer zwraca cokolwiek poza udaną odpowiedzią (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;
      }
    }
  }
}

Ponieważ do kodowania znaków localStorage wykorzystuje UTF-16, każdy pojedynczy bajt jest przechowywany jako 2 bajty, co zwiększa limit miejsca z 5 MB do 2,6 MB łącznie. Cały powód pobierania tych stron i znaczników w pamięci podręcznej aplikacji poza zakresem pamięci podręcznej aplikacji podano w następnej sekcji.

Dzięki najnowszym rozwiązaniom elementu iframe w HTML5 wprowadziliśmy prosty i skuteczny sposób analizowania obiektu responseText uzyskiwanego z wywołania AJAX. Do wyboru jest mnóstwo parserów kodu JavaScript o długości 3000 wierszy i wyrażeń regularnych, które usuwają tagi skryptu itp. Ale dlaczego nie pozwolić przeglądarce zrobić tego, co robi najlepiej? W tym przykładzie zapiszemy parametr responseText w tymczasowym ukrytym elemencie iframe. Używamy atrybutu „piaskownica” HTML5, który wyłącza skrypty i zapewnia wiele funkcji zabezpieczeń...

Zgodnie ze specyfikacją: Atrybut piaskownicy, jeśli jest określony, włącza zestaw dodatkowych ograniczeń dotyczących dowolnej treści hostowanej przez element iframe. Jego wartością musi być nieuporządkowany zestaw unikalnych tokenów rozdzielonych spacjami. Wielkość liter w kodach ASCII (wielkość liter nie jest rozróżniana). Dozwolone wartości to allow-forms, allow-same-origin, allow-scripts oraz allow-top-navigation. Po ustawieniu atrybutu 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 odmawia domyślnie przeniesienia 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 adoptNode i wszystko jest w porządku.

Dlaczego więc element iframe? Dlaczego nie używa się po prostu innerHTML? Mimo że innerHTML jest teraz częścią specyfikacji HTML5, wstawianie odpowiedzi z serwera (złego lub dobrego) do odznaczonego obszaru jest niebezpieczne. Podczas pisania tego artykułu nie udało mi się znaleźć osoby używającej czegoś poza innerHTML. Wiem, że JQuery używa jej podstaw, dodając opcję zastępczą dołączenia tylko w wyjątkowych przypadkach. Jest on również używany w JQuery Mobile. Nie przeprowadziłam jednak intensywnych testów pod kątem problemu „przestaje działać losowo” w standardzie innerHTML, ale ciekawe informacje na temat wszystkich platform, na które ma to wpływ, byłyby bardzo interesujące. Interesuje mnie też, które podejście jest bardziej skuteczne... Z obu stron słyszałam też o tym.

Wykrywanie, obsługa i profilowanie typów sieci

Mamy możliwość buforowania (czyli przewidywania w pamięci podręcznej) naszej aplikacji internetowej. Teraz musimy wdrożyć odpowiednie funkcje wykrywania połączeń, które zwiększają inteligencję aplikacji. To właśnie tutaj podczas tworzenia aplikacji mobilnych jest szczególnie ważna kwestia szybkości połączenia i trybu online/offline oraz szybkości połączenia. Wpisz Network Information API. Za każdym razem, gdy pokazuję tę funkcję w prezentacji, ktoś z odbiorców podnosi rękę i pyta: „Do czego mogę tego użyć?”. Oto możliwy sposób skonfigurowania niezwykle inteligentnej aplikacji mobilnej.

Najpierw nudny scenariusz zdrowy rozsądku... Gdy korzystasz z internetu na urządzeniu mobilnym w szybkim pociągu, sieć może w pewnych momentach całkiem zniknąć, a różne obszary geograficzne obsługują różne szybkości przesyłania (np. Sieć HSPA lub 3G może być dostępna w niektórych obszarach miejskich, ale obszary oddalone mogą obsługiwać znacznie wolniejsze technologie 2G. Poniższy kod dotyczy większości scenariuszy związanych z połączeniami.

Ten kod zawiera:

  • Dostęp offline przez applicationCache.
  • Wykrywanie, czy użytkownik został dodany do zakładek i jest offline.
  • Wykrywanie przy przełączaniu z trybu offline na online i odwrotnie.
  • Wykrywa wolne połączenia i pobiera treści na podstawie typu sieci.

Wszystkie te funkcje również wymagają bardzo niewielkiej ilości kodu. Najpierw wykrywamy 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 obiekcie EventListeners powyżej musimy określić, czy nasz kod jest wywoływany ze zdarzenia, z rzeczywistego żądania strony lub odświeżania. Głównym powodem jest to, że zdarzenie onload nie będzie wywoływane przy przełączaniu między trybami online i offline.

Następnie mamy prosty sprawdzenie, czy wydarzenie ononline lub onload. Ten kod resetuje wyłączone linki podczas przełączania się z trybu offline na online. Jeśli ta aplikacja jest bardziej zaawansowana, można wstawić logikę, która wznowi pobieranie treści lub obsługuje UX w przypadku niestabilnych 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 witryny processOffline(). W tym miejscu musisz edytować aplikację w trybie offline i spróbować odzyskać wszystkie transakcje, które miały miejsce za kulisami. Poniższy kod pozwala wyodrębniać wszystkie linki zewnętrzne i wyłączać je. W tym czasie użytkownicy mogą na stałe zatrzymywać użytkowników w naszej aplikacji offline, prawda?

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

    }
  }
}

Dobrze, przejdźmy do rzeczy. Gdy aplikacja wie już, w jakim stanie jest połączenie, możemy też sprawdzić typ połączenia, gdy jest online, i odpowiednio je dostosować. W komentarzach dla każdego połączenia podałam listę typowych dostawców z Ameryki Północnej, którzy pobierają dane i czas oczekiwania.

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

Możemy wprowadzić wiele zmian w procesie fetchAndCache, ale zrobię tylko, aby pobierał zasoby asynchroniczne (true) lub synchroniczne (false) dla danego połączenia.

Oś czasu żądań Edge (synchroniczna)

Synchronizacja Edge

Oś czasu żądań sieci Wi-Fi (asynchroniczna)

Asynchroniczna sieć Wi-Fi

Pozwala to na zastosowanie przynajmniej określonej metody dostosowywania wrażeń użytkownika na podstawie wolnego lub szybkiego połączenia. W żadnym wypadku nie jest to rozwiązanie uniwersalne. Kolejną czynnością jest wyświetlenie okna wczytywania po kliknięciu linku (w przypadku wolniejszych połączeń), gdy aplikacja nadal może pobierać stronę tego linku w tle. Najważniejsze jest zmniejszenie opóźnień przy jednoczesnym wykorzystaniu wszystkich możliwości, jakie daje połączenie użytkownika z najnowszym i najlepszym rozwiązaniem HTML5. Zobacz pokaz wykrywania sieci

Podsumowanie

Przygoda w zakresie mobilnych aplikacji HTML5 dopiero się rozpoczyna. Teraz widać bardzo proste i podstawowe podstawy platformy mobilnej opartej wyłącznie na HTML5 i obsłudze technologii. Myślę, że deweloperzy powinni pracować nad tymi funkcjami i reagować na nie, a nie za pomocą kodu.