Wprowadzenie
Odświeżanie z animacją obracania, przerywane przejścia między stronami i okresowe opóźnienia w zdarzeniach kliknięcia to tylko niektóre z problemów, które występują w obecnych środowiskach internetowych na urządzeniach mobilnych. Programiści starają się jak najbardziej zbliżyć do natywnego środowiska, ale często przeszkadzają im w tym włamania, resetowania i sztywne struktury.
W tym artykule omówimy minimum, które jest potrzebne do utworzenia mobilnej aplikacji internetowej HTML5. Chodzi nam przede wszystkim o ujawnienie ukrytych złożoności, które dzisiejsze platformy mobilne starają się ukryć. Zobaczysz minimalistyczne podejście (z użyciem podstawowych interfejsów API HTML5) i podstawowe zasady, które pozwolą Ci napisać własny framework lub współtworzyć 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 nasze proste rysunki (divy, tła, tekst z cieniami, obrazy itp.) wyglądały gładko i były płynnie animowane przez procesor graficzny. Niestety większość programistów front-endu przekazuje ten proces animacji do zewnętrznej platformy, nie przejmując się semantyką. Czy jednak te podstawowe funkcje CSS3 powinny być maskowane? Podam Ci kilka powodów, dla których warto się tym przejmować:
Przydzielanie pamięci i obciążenie obliczeniowe – jeśli będziesz łączyć wszystkie elementy w DOM tylko po to, aby przyspieszyć działanie sprzętu, następna osoba, która będzie pracować nad Twoim kodem, może Cię dopaść i dotkliwie pobić.
Zużycie energii – gdy włącza się sprzęt, zaczyna działać bateria. Podczas tworzenia aplikacji mobilnych deweloperzy muszą uwzględniać wiele ograniczeń urządzeń. Będzie to jeszcze bardziej powszechne, gdy producenci przeglądarek zaczną umożliwiać dostęp do coraz większej liczby komponentów sprzętowych urządzenia.
Konflikty – podczas stosowania akceleracji sprzętowej do części strony, które już były przyspieszone, wystąpiły problemy. Dlatego wiedza o tym, czy masz nakładające się przyspieszenie, jest bardzo ważna.
Aby interakcja użytkownika była płynna i jak najbardziej zbliżona do natywnej, musimy wykorzystać możliwości przeglądarki. Najlepiej, aby procesor urządzenia mobilnego skonfigurował animację początkową, a procesor graficzny odpowiadał tylko za łączenie różnych warstw podczas procesu animacji. Tak działają funkcje translate3d, scale3d i translateZ – nadają animowanym elementom własną warstwę, dzięki czemu urządzenie może płynnie renderować wszystko razem. Więcej informacji o przyspieszonym komponowaniu i działaniu WebKit znajdziesz w tym artykule na blogu Ariyi Hidayata.
Przejścia między stronami
Przyjrzyjmy się 3 najczęstszym sposobom interakcji użytkownika podczas tworzenia mobilnej aplikacji internetowej: efektom przesuwania, przerzucania i obracania.
Możesz zobaczyć ten kod w działaniu tutaj: http://slidfast.appspot.com/slide-flip-rotate.html (uwaga: ta wersja demonstracyjna jest przeznaczona na urządzenia mobilne, więc uruchom emulator, użyj telefonu lub tabletu albo zmniejsz rozmiar okna przeglądarki do około 1024 pikseli lub mniej).
Najpierw omówimy przejścia slajdów, obracania i przewracania oraz sposób ich przyspieszania. Zwróć uwagę, że każda animacja wymaga tylko 3–4 wierszy kodu CSS i JavaScript.
Przesuwanie
Najpopularniejsza z tych 3 metod to przejścia stron z efektem przesuwania, które przypominają natywne aplikacje mobilne. Przejście slajdu jest wywoływane, aby wyświetlić nowy obszar treści w obszarze widoku.
W przypadku efektu przesuwania najpierw deklarujemy znacznik:
<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 koncepcję umieszczania stron po lewej lub prawej stronie. Może to być w zasadzie dowolny kierunek, ale ten jest najczęstszy.
Teraz możemy uzyskać animację z akceleracją sprzętową za pomocą zaledwie kilku wierszy kodu CSS. Animacja jest wykonywana, 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) jest znane jako podejście „cudownego rozwiązania”.
Gdy użytkownik kliknie element nawigacyjny, wykonujemy ten kod JavaScript, aby zamienić klasy. Nie używamy żadnych platform innych firm, tylko 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 zmienia się w stage-center i powoduje przesunięcie strony do środka widocznego obszaru. W dużej mierze polegamy na CSS3.
.stage-left {
left: -480px;
}
.stage-right {
left: 480px;
}
.stage-center {
top: 0;
left: 0;
}
Teraz przyjrzyjmy się arkuszowi CSS, który obsługuje wykrywanie urządzeń mobilnych i ich orientację. Możemy uwzględnić każde urządzenie i każdą rozdzielczość (patrz rozdzielczość zapytania o media). W tej wersji demonstracyjnej użyłem tylko kilku prostych przykładów, aby uwzględnić większość widoków pionowych i poziomych na urządzeniach mobilnych. Jest to również przydatne w przypadku stosowania akceleracji sprzętowej na poszczególnych urządzeniach. Na przykład, ponieważ wersja WebKit na komputery przyspiesza wszystkie przekształcone elementy (niezależnie od tego, czy są 2D czy 3D), warto utworzyć zapytanie o media i wykluczyć przyspieszenie na tym poziomie. Pamiętaj, że sztuczki związane z akceleracją sprzętową nie zapewniają żadnego wzrostu szybkości w systemie Android Froyo 2.2 i nowszym. Cała kompozycja jest wykonywana 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 stron polega na przesunięciu strony. Używamy tu prostego kodu JavaScript, aby obsługiwać to zdarzenie na urządzeniach z iOS i Androidem (opartych na WebKit).
Zobacz, jak to działa: http://slidfast.appspot.com/slide-flip-rotate.html.
W przypadku zdarzeń dotykowych i przejść najpierw musisz poznać bieżące położenie 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;
}
Do przewracania strony używamy przejścia CSS3, więc zwykły kod element.offsetLeft nie będzie działać.
Następnie chcemy określić, w jakim kierunku użytkownik przewraca urządzenie, i ustawić próg, po przekroczeniu którego nastąpi zdarzenie (nawigacja po stronie).
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 swipeTime mierzymy również w milisekundach. Dzięki temu zdarzenie nawigacji zostanie wywołane, jeśli użytkownik szybko przesunie palcem po ekranie, aby przewrócić stronę.
Aby ustawić stronę i sprawić, że animacje będą wyglądać naturalnie, 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łem użyć funkcji cubic-bezier, aby przejścia wyglądały jak najbardziej naturalnie, ale funkcja ease-out okazała się wystarczająca.
Aby nawigacja działała, musimy wywołać zdefiniowane wcześniej metody slideTo(), których użyliśmy w ostatnim 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 obracania użytej w tym przykładzie. W każdej chwili możesz obrócić wyświetlaną stronę o 180 stopni, aby zobaczyć jej drugą stronę. Wystarczy, że klikniesz opcję menu „Kontakt”. Ponownie wystarczy kilka wierszy CSS i trochę kodu JavaScript, aby przypisać klasę przejścia onclick.
UWAGA: przejście obrotu nie jest prawidłowo renderowane w większości wersji Androida, ponieważ nie ma możliwości przekształcenia CSS w 3D. Niestety zamiast zignorować obrót, Android sprawia, że strona „odjeżdża” w bok, obracając się zamiast przewracać. Zalecamy rzadkie używanie tego przejścia, dopóki nie zostanie ono ulepszone.
Znaczniki (podstawowa koncepcja przodu i tyłu):
<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
<div id="contact-page" class="page">
<h1>Contact Page</h1>
</div>
</div>
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
Omówiliśmy już podstawowe przejścia, przyjrzyjmy się teraz mechanizmom ich działania i komponowania.
Aby przeprowadzić tę magiczną sesję debugowania, uruchom kilka przeglądarek i wybrane środowisko IDE. Najpierw uruchom Safari z wiersza poleceń, aby użyć niektórych zmiennych środowiskowych debugowania. Używam komputera Mac, więc polecenia mogą się różnić w zależności od systemu operacyjnego. Otwórz terminal i wpisz to polecenie:
- $> export CA_COLOR_OPAQUE=1
- $> export CA_LOG_MEMORY_USAGE=1
- $> /Applications/Safari.app/Contents/MacOS/Safari
Spowoduje to uruchomienie Safari z kilkoma narzędziami do debugowania. CA_COLOR_OPAQUE pokazuje, które elementy są faktycznie łączone lub przyspieszane. CA_LOG_MEMORY_USAGE pokazuje, ile pamięci używamy podczas wysyłania operacji rysowania do pamięci zapasowej. Dzięki temu dowiesz się, jak bardzo obciążasz urządzenie mobilne, i możesz uzyskać wskazówki, jak zużycie GPU może wyczerpywać baterię urządzenia docelowego.
Uruchommy teraz Chrome, aby zobaczyć informacje o klatkach na sekundę (FPS):
- Otwórz przeglądarkę Google Chrome.
- W pasku adresu wpisz about:flags.
- Przewiń w dół o kilka pozycji i kliknij „Włącz” przy opcji Licznik klatek na sekundę.
Jeśli otworzysz tę stronę w ulepszonej wersji Chrome, w lewym górnym rogu zobaczysz czerwony licznik FPS.
W ten sposób sprawdzisz, czy akceleracja sprzętowa jest włączona. Daje nam to też wyobrażenie o tym, jak działa animacja i czy nie ma żadnych wycieków (ciągłe animacje, które powinny zostać zatrzymane).
Innym sposobem na wizualizację akceleracji sprzętowej jest otwarcie tej samej strony w Safari (z użyciem wspomnianych powyżej zmiennych środowiskowych). Każdy przyspieszony element DOM ma czerwony odcień. Dzięki temu dokładnie widzimy, co jest łączone w poszczególnych warstwach. Zwróć uwagę, że biała nawigacja nie jest czerwona, ponieważ nie jest przyspieszona.
Podobne ustawienie dla Chrome jest też dostępne w sekcji about:flags „Composited render layer borders”.
Innym świetnym sposobem na sprawdzenie złożonych warstw jest wyświetlenie demonstracji opadających liści WebKit po zastosowaniu tego modułu.
Na koniec, aby w pełni zrozumieć wydajność sprzętu graficznego naszej aplikacji, przyjrzyjmy się zużyciu pamięci. Widzimy, że do buforów CoreAnimation w systemie macOS przesyłamy 1,38 MB instrukcji rysowania. Bufory pamięci Core Animation są współdzielone między OpenGL ES a GPU, aby tworzyć końcowe piksele widoczne na ekranie.
Gdy zmienimy rozmiar okna przeglądarki lub je zmaksymalizujemy, zobaczymy, że pamięć również się powiększa.
Dzięki temu możesz sprawdzić, jak pamięć jest wykorzystywana na urządzeniu mobilnym, ale tylko wtedy, gdy zmienisz rozmiar przeglądarki na odpowiedni. Jeśli debugujesz lub testujesz środowiska iPhone, zmień rozmiar na 480 x 320 pikseli. Wiemy już dokładnie, jak działa akceleracja sprzętowa i co jest potrzebne do debugowania. Czytanie o tym to jedno, ale zobaczenie, jak działają bufory pamięci GPU, to zupełnie inna sprawa.
Za kulisami: pobieranie i pamięć podręczna
Czas przenieść buforowanie stron i zasobów na wyższy poziom. Podobnie jak w przypadku JQuery Mobile i podobnych platform będziemy wstępnie pobierać i buforować strony za pomocą równoczesnych wywołań AJAX.
Przyjrzyjmy się kilku podstawowym problemom związanym z mobilną siecią i powodom, dla których musimy je rozwiązać:
- Pobieranie: wstępne pobieranie naszych stron umożliwia użytkownikom korzystanie z aplikacji w trybie offline i eliminuje oczekiwanie między działaniami nawigacyjnymi. Oczywiście nie chcemy ograniczać przepustowości urządzenia, gdy jest ono online, więc musimy korzystać z tej funkcji oszczędnie.
- Pamięć podręczna: następnie chcemy zastosować współbieżne lub asynchroniczne podejście do pobierania i buforowania 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 parsowanie odpowiedzi: używanie innerHTML() do wstawiania odpowiedzi AJAX do DOM jest niebezpieczne (i niezbyt wiarygodne?). Zamiast tego używamy niezawodnego mechanizmu do wstawiania odpowiedzi AJAX i obsługi jednoczesnych wywołań. Do analizowania
xhr.responseTextużywamy też nowych funkcji HTML5.
Korzystając z kodu z demonstracji przesuwania, odwracania i obracania, zaczynamy od dodania kilku dodatkowych stron i utworzenia do nich linków. Następnie przeanalizujemy linki i błyskawicznie utworzymy przejścia.
Zobacz prezentację pobierania i umieszczania w pamięci podręcznej
Jak widzisz, korzystamy tu z oznaczeń semantycznych. To 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 „page” itp. A oto strona szczegółów (podrzędna) znajdująca się w osobnym pliku HTML (/demo2/home-detail.html), który zostanie wczytany, 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>
Przyjrzyjmy się teraz JavaScriptowi. Dla uproszczenia pominę w kodzie wszelkie funkcje pomocnicze i optymalizacje. W tym przypadku po prostu przechodzimy przez określoną tablicę węzłów DOM, aby znaleźć linki do pobrania i zapisania w pamięci podręcznej.
Uwaga: w tej wersji demonstracyjnej ta metoda fetchAndCache() jest wywoływana podczas wczytywania strony. W następnej sekcji zmodyfikujemy go, gdy wykryjemy połączenie sieciowe i określimy, 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') &&
//'#' 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) &&
//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();
}
}
}
};
Zapewniamy prawidłowe asynchroniczne przetwarzanie końcowe za pomocą obiektu „AJAX”. Bardziej zaawansowane wyjaśnienie używania localStorage w wywołaniu AJAX znajdziesz w artykule Working Off the Grid with HTML5 Offline (Praca w trybie offline z HTML5). W tym przykładzie widać podstawowe użycie buforowania w przypadku każdego żądania i dostarczanie obiektów z pamięci podręcznej, gdy serwer zwraca odpowiedź inną niż pozytywna (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, ponieważ localStorage używa kodowania znaków UTF-16, każdy pojedynczy bajt jest przechowywany jako 2 bajty, co zmniejsza limit miejsca na dane z 5 MB do 2,6 MB. W następnej sekcji wyjaśnimy, dlaczego pobieramy i zapisujemy w pamięci podręcznej te strony lub znaczniki poza zakresem pamięci podręcznej aplikacji.
Dzięki niedawnym postępom w zakresie elementu iframe w HTML5 mamy teraz prosty i skuteczny sposób na analizowanie danych responseText, które otrzymujemy w odpowiedzi na wywołanie AJAX. Istnieje wiele parserów JavaScript i wyrażeń regularnych o długości 3000 wierszy, które usuwają tagi skryptów itp. Ale dlaczego nie pozwolić przeglądarce robić tego, co potrafi najlepiej? W tym przykładzie zapiszemy wartość responseText w tymczasowym ukrytym elemencie iframe. Używamy atrybutu HTML5 „sandbox”, który wyłącza skrypty i oferuje wiele funkcji zabezpieczeń…
Zgodnie ze specyfikacją: atrybut sandbox, jeśli jest określony, włącza zestaw dodatkowych ograniczeń dotyczących treści hostowanych przez element iframe. Jej wartość musi być nieuporządkowanym zbiorem unikalnych tokenów rozdzielonych spacjami, które nie uwzględniają wielkości liter ASCII. Dozwolone wartości to allow-forms, allow-same-origin, allow-scripts i allow-top-navigation. Gdy ten atrybut jest ustawiony, treść jest traktowana jako pochodząca z unikalnego źródła, formularze i skrypty są wyłączone, linki nie mogą kierować 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 niejawnego przeniesienia węzła z jednego dokumentu do drugiego. Jeśli nowy węzeł podrzędny został utworzony w innym dokumencie, wystąpi błąd. Używamy więc adoptNode i wszystko jest w porządku.
Dlaczego więc warto używać elementu iframe? Dlaczego nie używać po prostu innerHTML? Chociaż innerHTML jest teraz częścią specyfikacji HTML5, wstawianie odpowiedzi z serwera (złośliwego lub nie) w niezabezpieczonym obszarze jest niebezpieczne. Podczas pisania tego artykułu nie udało mi się znaleźć nikogo, kto używałby czegoś innego niż innerHTML. Wiem, że JQuery używa go w swoim rdzeniu, a w przypadku wyjątku stosuje tylko dodawanie. Korzysta z niej też jQuery Mobile. Nie przeprowadziłem jednak żadnych szczegółowych testów dotyczących „losowego przestawania działania” właściwości innerHTML, ale bardzo interesujące byłoby sprawdzenie, na których platformach występuje ten problem. Warto też sprawdzić, które podejście jest skuteczniejsze. Słyszałem opinie z obu stron.
Wykrywanie, obsługa i profilowanie typu sieci
Teraz, gdy mamy możliwość buforowania (lub predykcyjnego buforowania) naszej aplikacji internetowej, musimy udostępnić odpowiednie funkcje wykrywania połączenia, które sprawią, że nasza aplikacja będzie bardziej inteligentna. W tym przypadku tworzenie aplikacji mobilnych jest bardzo wrażliwe na tryby online i offline oraz szybkość połączenia. Wpisz The Network Information API (Interfejs 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żyć?”. Oto możliwy sposób skonfigurowania niezwykle inteligentnej mobilnej aplikacji internetowej.
Zacznijmy od nudnego, ale oczywistego scenariusza… Podczas korzystania z internetu na urządzeniu mobilnym w pociągu dużych prędkości sieć może w różnych momentach znikać, a w różnych regionach mogą być obsługiwane różne prędkości transmisji (np. W niektórych obszarach miejskich może być dostępna sieć HSPA lub 3G, ale na obszarach oddalonych mogą być obsługiwane znacznie wolniejsze technologie 2G). Poniższy kod obejmuje większość scenariuszy połączeń.
Ten kod zapewnia:
- Dostęp offline do
applicationCache. - Wykrywa, czy strona jest dodana do zakładek i dostępna offline.
- Wykrywa przełączanie między trybami offline i online.
- Wykrywa wolne połączenia i pobiera treści na podstawie typu sieci.
Wszystkie te funkcje 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 przypadku detektorów zdarzeń powyżej musimy poinformować kod, czy jest on wywoływany przez zdarzenie, czy przez rzeczywiste żądanie strony lub odświeżenie. Głównym powodem jest to, że zdarzenie onload nie jest wywoływane podczas przełączania między trybem online i offline.
Następnie sprawdzamy, czy wystąpiło zdarzenie ononline lub onload. Ten kod resetuje wyłączone linki podczas przełączania z trybu offline na online, ale gdyby ta aplikacja była bardziej zaawansowana, można by wstawić logikę, która wznawia pobieranie treści lub obsługuje 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 miejscu możesz dostosować aplikację do trybu offline i spróbować odzyskać transakcje, które były w toku w tle. Poniższy kod wyszukuje wszystkie nasze linki zewnętrzne i je wyłącza, przez co użytkownicy zostają na ZAWSZE uwięzieni w naszej aplikacji offline. Muahaha!
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 konkretów. Teraz, gdy nasza aplikacja wie, w jakim stanie połączenia się znajduje, możemy też sprawdzić typ połączenia, gdy jest ono aktywne, i odpowiednio dostosować działanie aplikacji. W komentarzach do każdego połączenia podaję typowe wartości pobierania i opóźnień u dostawców w Ameryce Północnej.
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 ja tylko określiłem, czy zasoby mają być pobierane asynchronicznie (true) czy synchronicznie (false) w przypadku danego połączenia.
Oś czasu żądania Edge (synchronicznego)
Oś czasu żądania (asynchronicznego) dotyczącego Wi-Fi
Umożliwia to przynajmniej częściowe dostosowanie wygody użytkownika do wolnego lub szybkiego połączenia. Nie jest to jednak rozwiązanie ostateczne. Kolejnym zadaniem jest wyświetlanie okna ładowania po kliknięciu linku (przy wolnych połączeniach), gdy aplikacja może nadal pobierać stronę tego linku w tle. Najważniejsze jest tu ograniczenie opóźnień przy jednoczesnym wykorzystaniu pełnych możliwości połączenia użytkownika dzięki najnowszym funkcjom HTML5. Wyświetl prezentację wykrywania sieci
Podsumowanie
Przygoda z mobilnymi aplikacjami HTML5 dopiero się zaczyna. Widzisz teraz bardzo proste i podstawowe fundamenty „platformy” mobilnej zbudowanej wyłącznie na HTML5 i powiązanych technologiach. Uważam, że deweloperzy powinni pracować nad tymi funkcjami i rozwiązywać problemy z nimi związane u ich podstaw, a nie maskować ich za pomocą otoczki.