Mobil performansı optimize etmek için HTML5 teknikleri

Vedat Hales
Wesley Hales

Giriş

Dönen yenilemeler, kesik sayfa geçişleri ve dokunma etkinliklerindeki periyodik gecikmeler günümüzün mobil web ortamlarındaki sorunlardan yalnızca birkaçıdır. Geliştiriciler yerel reklamlara olabildiğince yaklaşmaya çalışırlar, ancak genellikle bilgisayar korsanlığı, sıfırlamalar ve katı çerçeveler nedeniyle hayal kırıklığına uğrarlar.

Bu makalede, bir mobil HTML5 web uygulaması oluşturmak için gereken minimum koşulları ele alacağız. Ana konu, günümüzün mobil çerçevelerinin gizlemeye çalıştığı gizli karmaşıklıkları ortaya çıkarmaktır. Kendi çerçevenizi yazmanıza veya şu anda kullandığınız çerçeveye katkıda bulunmanıza yardımcı olacak minimalist bir yaklaşım (temel HTML5 API'ları kullanan) ve temel temel ilkeleri göreceksiniz.

Donanım hızlandırma

Normalde GPU'lar, ayrıntılı 3D modellemeyi veya CAD diyagramlarını işler, ancak bu durumda temel çizimlerimizin (div'ler, arka planlar, gölge içeren metinler, resimler vb.) düzgün görünmesini ve GPU aracılığıyla animasyonlarının pürüzsüz olmasını isteriz. Ne yazık ki çoğu kullanıcı arabirimi geliştiricisi, bu animasyon sürecini anlamsal kaygılar yaşamadan üçüncü taraf bir taraf çerçevesine taşıyor. Ancak, bu temel CSS3 özelliklerinin maskelenmesi gerekiyor mu? Bu konuyu önemsemenin önemli olmasının birkaç nedenini belirtmek istiyorum:

  1. Bellek tahsisi ve hesaplama yükü: Donanım hızlandırması amacıyla DOM'daki her öğeyi bir araya getirirseniz kodunuz üzerinde çalışan bir sonraki kişi sizi kovalayabilir ve sizi ciddi bir şekilde dövebilir.

  2. Güç Tüketimi — Elbette, donanım devreye girdiğinde pil de çalışır. Geliştiriciler mobil için geliştirme yaparken mobil web uygulamaları yazarken çok çeşitli cihaz kısıtlamalarını dikkate almak zorunda kalırlar. Tarayıcı geliştiricileri giderek daha fazla cihaz donanımına erişim sağlamaya başladıkça bu durum daha da yaygınlaşacaktır.

  3. Çakışmalar — Sayfanın önceden hızlandırılmış bölümlerine donanım hızlandırmayı uygularken hatayla karşılaştım. Dolayısıyla çakışan hızlarınızın olup olmadığını bilmek çok önemlidir.

Kullanıcı etkileşiminin sorunsuz ve olabildiğince yerel olması için tarayıcının bizim için çalışmasını sağlamamız gerekiyor. İdeal olarak, mobil cihazın CPU'nun başlangıç animasyonu oluşturmasını ve animasyon işlemi sırasında yalnızca farklı katmanların birleştirilmesinden sorumlu GPU'nun olmasını isteriz. çeviri3d, ölçek3d ve çeviriZ’in yaptığı budur. Animasyonlu öğelere kendi katmanlarını verirler ve böylece cihazın her şeyi birlikte düzgün bir şekilde oluşturmasını sağlarlar. Ariya Hidayat, hızlandırılmış birleştirme ve WebKit'in nasıl çalıştığı konusunda daha fazla bilgi edinmek için blogunda pek çok faydalı bilgi bulunuyor.

Sayfa geçişleri

Bir mobil web uygulaması geliştirirken kullanılan en yaygın kullanıcı etkileşimi yaklaşımlarından üçüne, yani kaydırma, çevirme ve döndürme efektlerine göz atalım.

Bu kodu çalışırken görmek için http://slidfast.appspot.com/slide-flip-rotate.html adresine gidin. (Not: Bu demo mobil cihazlar için oluşturulmuştur. Bu nedenle bir emülatörü çalıştırın, telefonunuzu veya tabletinizi kullanın ya da tarayıcı pencerenizin boyutunu yaklaşık 1024 piksele indirin.)

İlk olarak slayt, çevirme ve döndürme geçişlerini ve bunların nasıl hızlandırıldığını inceleyeceğiz. Her animasyonun yalnızca üç veya dört satır CSS ve JavaScript aldığına dikkat edin.

Kayan

Üç geçiş yaklaşımından en yaygını olan kayan sayfa geçişleri, mobil uygulamaların doğal hissini taklit eder. Görünüm alanına yeni bir içerik alanı getirmek için slayt geçişi çağrılır.

Slayt efekti için önce işaretlememizi açıklıyoruz:

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

Sayfaları sola ya da sağa hazırlama konseptini nasıl belirlediğimize dikkat edin. Aslında herhangi bir yönde ilerleme olabilir ancak en yaygın olanı budur.

Artık yalnızca birkaç CSS satırıyla animasyon ve donanım hızlandırmayı uyguluyoruz. Asıl animasyon, sayfa div öğelerindeki sınıfları değiştirdiğimizde ortaya çıkar.

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

translate3d(0,0,0), "sihirli değnek" yaklaşımı olarak bilinir.

Kullanıcı bir gezinme öğesini tıkladığında, sınıfları değiştirmek için aşağıdaki JavaScript'i çalıştırırız. Üçüncü taraf çerçeveleri kullanılmaz. Bunlar doğrudan JavaScript'tir. ;)

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 veya stage-right, stage-center haline gelir ve sayfayı ortadaki görünüm bağlantı noktasına kaydırmaya zorlar. İşin zor kısmını tamamen CSS3'e güveniyoruz.

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

.stage-right {
  left: 480px;
}

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

Şimdi de mobil cihaz algılama ve yönlendirme işlemlerini gerçekleştiren CSS'ye göz atalım. Her cihazı ve her çözümü ele alabiliriz (medya sorgusu çözümü bölümünü inceleyin). Mobil cihazlardaki çoğu dikey ve yatay görünümü kapsamak için bu demoda yalnızca birkaç basit örnek kullandım. Bu özellik, her bir cihaz için donanım hızlandırma uygulama konusunda da faydalıdır. Örneğin, WebKit'in masaüstü sürümü, dönüştürülen tüm öğeleri hızlandırdığından (2D veya 3D olmasından bağımsız olarak) bir medya sorgusu oluşturup bu düzeyde hızlandırmayı hariç tutmak mantıklıdır. Donanım hızlandırma hilelerinin Android Froyo 2.2+ sürümünde herhangi bir hız iyileştirmesi sağlamadığını unutmayın. Tüm oluşturma işlemi yazılımda yapılır.

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

Çevirme

Mobil cihazlarda ise sayfayı çevirme, sayfayı dışarı doğru kaydırma olarak bilinir. Burada, iOS ve Android (WebKit tabanlı) cihazlarda bu etkinliği işlemek için basit bir JavaScript kodu kullanılmaktadır.

Bunu uygulamalı olarak görüntüleyin http://slidfast.appspot.com/slide-flip-rotate.html.

Dokunma etkinlikleri ve geçişleri üzerinde çalışırken kullanacağınız ilk şey öğenin mevcut konumunu kontrol etmektir. WebKitCSSMatrix hakkında daha fazla bilgi için bu dokümana bakın.

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

Sayfa çevirme için CSS3 yumuşak geçiş geçişi kullandığımızdan, normal element.offsetLeft çalışmaz.

Ardından, kullanıcının hangi yöne doğru döndüğünü öğrenmek ve etkinliğin gerçekleşmesi için (sayfada gezinme) bir eşik belirlemek isteriz.

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

swipeTime metriğinin milisaniye cinsinden ölçüldüğünü de göreceksiniz. Bu, kullanıcı bir sayfayı çevirmek için ekranı hızlı bir şekilde kaydırırsa gezinme etkinliğinin etkinleşmesini sağlar.

Bir parmağınız ekrana dokunurken sayfayı konumlandırmak ve animasyonların yerel görünmesini sağlamak için her etkinlik tetiklendikten sonra CSS3 geçişlerini kullanırız.

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

Geçişlere en iyi yerel hissi vermek için kübik bezier'ı kullanmaya çalıştım ama yumuşak geçişle başarıya ulaştım.

Son olarak, gezinmeyi sağlamak için son demoda kullandığımız, önceden tanımlanmış slideTo() yöntemlerimizi çağırmamız gerekir.

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

Döndürme

Daha sonra, bu demoda kullanılan döndürme animasyonuna göz atalım. Görüntülemekte olduğunuz sayfayı istediğiniz zaman 180 derece döndürerek arka tarafının görünmesi için "İletişim" menü seçeneğine dokunabilirsiniz. Yine, onclick geçiş sınıfını atamak için yalnızca birkaç satır CSS ve bir miktar JavaScript gerekir. NOT: Döndürme geçişi, 3D CSS dönüştürme özelliklerinden yararlanamadığı için Android'in çoğu sürümünde doğru şekilde oluşturulmaz. Ne yazık ki Android, çevirmeyi yoksaymak yerine sayfayı çevirmek yerine döndürerek "alışveriş sepetini" uzaklaştırıyor. Destek iyileşene kadar bu geçişi tutumlu bir şekilde kullanmanızı öneririz.

İşaretleme (ön ve arka temel kavramı):

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

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

Donanım hızlandırmada hata ayıklama

Temel geçişlerimizi ele aldığımıza göre şimdi bunların işleyişini ve birleştirilmesiyle ilgili mekaniklere bir göz atalım.

Bu sihirli hata ayıklama oturumunu gerçekleştirmek için birkaç tarayıcıyı ve tercih ettiğiniz IDE'yi etkinleştirelim. Bazı hata ayıklama ortamı değişkenlerinden yararlanmak için önce komut satırından Safari'yi başlatın. Mac kullandığım için komutlar işletim sistemine göre değişebilir. Terminal'i açın ve aşağıdakileri yazın:

  • $> dışa aktar CA_COLOR_OPAQUE=1
  • $> dışa aktar CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

Bu işlem, Safari'yi birkaç hata ayıklama yardımcısıyla başlatır. CA_COLOR_OPAQUE, hangi öğelerin aslında birleştirilmiş veya hızlandırılmış olduğunu bize gösterir. CA_LOG_MEMORY_USAGE, çizim işlemlerimizi yedekleme deposuna gönderirken ne kadar bellek kullandığımızı gösterir. Bu veri, mobil cihazı tam olarak ne kadar zorladığınızı gösterir ve GPU kullanımınızın hedef cihazın pilini nasıl tükettiğine dair ipuçları verebilir.

Şimdi, saniyedeki kare sayısı (FPS) ile ilgili bazı iyi bilgileri görebilmek için Chrome'u başlatalım:

  1. Google Chrome web tarayıcısını açın.
  2. URL çubuğuna about:flags yazın.
  3. Birkaç öğe aşağı kaydırın ve FPS Sayacı için "Etkinleştir"i tıklayın.

Bu sayfayı Chrome'un geliştirilmiş sürümünüzde görüntülerseniz sol üst köşede kırmızı FPS sayacını görürsünüz.

Chrome FPS

Donanım hızlandırmanın etkin olduğunu bu şekilde biliriz. Ayrıca animasyonun nasıl çalıştığı ve herhangi bir sızıntı (durdurulması gereken sürekli çalışan animasyonlar) olup olmadığı konusunda da bize fikir veriyor.

Donanım hızlandırmasını gerçek anlamda görselleştirmenin bir başka yolu da aynı sayfayı Safari'de açmaktır (yukarıda bahsettiğim ortam değişkenleri ile). Her hızlandırılmış DOM öğesinde kırmızı bir renk bulunur. Bu bize, katman tarafından tam olarak neyin birleştirildiğini gösterir. Beyaz gezinmenin hızlandırılmadığı için kırmızı olmadığına dikkat edin.

Birleşik Kişi

Chrome için benzer bir ayar about:flags "Birleştirilmiş oluşturma katmanı kenarlıkları"nda da mevcuttur.

Birleştirilmiş katmanları görmenin bir başka güzel yolu da, bu değişiklik uygulanırken WebKit düşen yapraklar demosunu izlemektir.

üst üste kıvrımlı Yapraklar

Son olarak, uygulamamızın grafik donanım performansını tam anlamıyla anlamak için belleğin nasıl kullanıldığına bir göz atalım. Burada, Mac OS'taki CoreAnimation arabelleklerine 1,38 MB'lık çizim talimatları aktardığımızı görüyoruz. Çekirdek Animasyon bellek arabellekleri, ekranda gördüğünüz son piksellerin oluşturulması için OpenGL ES ve GPU arasında paylaşılır.

Animasyon 1

Tarayıcı penceresini yeniden boyutlandırdığımızda veya ekranı kapladığında, belleğin de genişlediğini görürüz.

Animasyon 2

Bu, yalnızca tarayıcının boyutunu doğru şekilde ayarladığınızda mobil cihazınızda belleğin nasıl tüketildiğine dair bir fikir verir. iPhone ortamları için hata ayıklama veya test yapıyorsanız 480 piksele 320 piksel olarak yeniden boyutlandırın. Artık donanım hızlandırmanın tam olarak nasıl çalıştığını ve hata ayıklamak için neler gerektiğini biliyoruz. Okunacak bir konu var, ancak GPU bellek arabelleklerinin görsel olarak çalıştığını gerçekten görmek her şeyi gerçek bir bakış açısıyla ortaya koyuyor.

Kamera Arkası: Getirme ve Önbelleğe Alma

Şimdi sayfamızı ve kaynak önbelleğe alma işlemini bir üst seviyeye taşıma zamanı. JQuery Mobile'ın ve benzer çerçevelerin kullandığı yaklaşıma çok benzer şekilde, eşzamanlı AJAX çağrılarıyla sayfalarımızı önceden getirip önbelleğe alacağız.

Şimdi birkaç temel mobil web sorununu ve bunu yapmamızın nedenlerini ele alalım:

  • Getirme: Sayfalarımızın önceden getirilmesi, kullanıcıların uygulamayı çevrimdışına almalarına olanak tanır ve ayrıca, gezinme işlemleri arasında beklemenin olmamasını sağlar. Elbette, cihaz internete bağlandığında cihazın bant genişliğini boğmak istemeyiz. Bu yüzden, bu özelliği tutumlu bir şekilde kullanmamız gerekir.
  • Önbelleğe alma: Ardından, bu sayfaları getirirken ve önbelleğe alırken eşzamanlı veya eşzamansız bir yaklaşım uygulanmasını istiyoruz. Maalesef eşzamansız olmayan yerel depolama alanını da (cihazlar tarafından iyi desteklendiği için) kullanmamız gerekiyor.
  • AJAX ve yanıtı ayrıştırma: AJAX yanıtını DOM'ye eklemek için nerHTML() kullanmak tehlikeli (ve güvenilir değil?). Bunun yerine AJAX yanıtı ekleme ve eşzamanlı çağrıları işleme için güvenilir bir mekanizma kullanırız. xhr.responseText kodunu ayrıştırmak için HTML5'in bazı yeni özelliklerinden de yararlanıyoruz.

Slayt, Çevir ve Döndürme demosu'ndaki kodu temel alarak işe birkaç ikincil sayfa ekleyip bu sayfaların bağlantılarını ekleyerek başlıyoruz. Ardından bağlantıları ayrıştırır ve geçişleri anında oluştururuz.

iPhone Ana Sayfası

Getir ve Önbellek demosunu buradan görüntüleyebilirsiniz.

Gördüğünüz gibi, burada semantik işaretlemeden yararlanıyoruz. Yalnızca başka bir sayfanın bağlantısı. Alt sayfa, üst sayfasıyla aynı düğüm/sınıf yapısını izler. Bunu bir adım öteye taşıyarak "page" düğümleri için data-* özelliğini kullanabiliriz. Burada da ayrı bir HTML dosyasında (/demo2/home-detail.html) bulunan ayrıntı sayfası (alt) verilmiştir. Bu sayfa yüklenir, önbelleğe alınır ve uygulama yüklenirken geçiş için ayarlanır.

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

Şimdi JavaScript'e göz atalım. Kolaylık olması açısından, kod dışında herhangi bir yardımcı veya optimizasyon bırakıyorum. Burada tek yaptığımız, belirli bir DOM düğümleri dizisi üzerinden döngüye alarak getirilecek ve önbelleğe alacak bağlantıları bulmaktır. Not: Bu demo için, bu fetchAndCache() yöntemi sayfa yüklemede çağrılmaktadır. Bir sonraki bölümde ağ bağlantısını tespit ettiğimizde ve ne zaman çağrılması gerektiğini belirlediğimizde bu işlemi yeniden yaparız.

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

"AJAX" nesnesini kullanarak uygun eşzamansız son işlemeyi sağlıyoruz. Çevrimdışı HTML5 ile Ağ Üzerinden Çalışma konusunda, bir AJAX çağrısında localStorage'ı kullanmayla ilgili daha ayrıntılı bir açıklama bulabilirsiniz. Bu örnekte, sunucu başarılı (200) yanıt dışında bir şey döndürdüğünde her istekte önbelleğe almanın ve önbelleğe alınan nesneleri sağlamanın temel kullanımını görüyorsunuz.

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

Maalesef localStorage karakter kodlaması için UTF-16 kullandığından her bir bayt 2 bayt olarak depolanıyor.Böylece depolama sınırımız 5 MB'tan toplam 2,6 MB'a düşüyor. Bu sayfaları/işaretlemeyi uygulama önbelleği kapsamının dışında alma ve önbelleğe almanın tüm nedeni bir sonraki bölümde açıklanmıştır.

HTML5'e sahip iframe öğesinde yapılan son gelişmelerle birlikte artık AJAX çağrımızdan geri aldığımız responseText öğesini ayrıştırmak için basit ve etkili bir yolumuz var. Komut dosyası etiketlerini vb. kaldıran çok sayıda 3000 satırlık JavaScript ayrıştırıcı ve normal ifade vardır. Peki tarayıcının en iyi yaptığı şeyi yapmasına izin vermemeye ne dersiniz? Bu örnekte, responseText öğesini geçici olarak gizli bir iframe'e yazacağız. Komut dosyalarını devre dışı bırakan ve birçok güvenlik özelliği sunan HTML5 “korumalı alan” özelliğini kullanıyoruz...

Spesifikasyondan: Korumalı alan özelliği belirtildiğinde, iframe tarafından barındırılan tüm içerikte bir dizi ek kısıtlamayı etkinleştirir. Bu değerin değeri, büyük/küçük harfe duyarlı olmayan, sıralanmamış, boşlukla ayrılmış benzersiz jetonlar grubu olmalıdır. izin verilen değerler: allow-forms, allow-same-origin, allow-scripts ve allow-top-navigation. Bu özellik ayarlandığında, içerik benzersiz bir kaynaktan geliyormuş gibi değerlendirilir, formlar ve komut dosyaları devre dışı bırakılır, bağlantıların diğer göz atma bağlamlarını hedeflemesi engellenir ve eklentiler devre dışı bırakılır.

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, bir düğümü dolaylı yoldan bir dokümandan diğerine taşımayı doğru şekilde reddediyor. Yeni alt düğüm farklı bir dokümanda oluşturulursa hata oluşur. Burada adoptNode kullanıyoruz ve her şey yolunda.

Peki neden iframe? Neden sadece nerHTML'yi kullanmıyoruz? InnerHTML artık HTML5 spesifikasyonunun bir parçası olsa da, bir sunucudan gelen yanıtın (kötü veya iyi) işaretlenmemiş bir alana yerleştirilmesi tehlikeli bir uygulamadır. Bu makaleyi yazarken nerHTML dışında bir şey kullanan kimseye ulaşamadım. JQuery'nin temelde yalnızca istisnai durumlarda yedek ekleyerek kullandığını biliyorum. JQuery Mobile da bunu kullanıyor. Bununla birlikte, InnerHTML'nin "rastgele çalışmayı durdurması" konusunda herhangi bir ciddi test yapmadım ancak bunun etkilediği tüm platformları görmek çok ilginç olurdu. Hangi yaklaşımın daha etkili olduğunu görmek de ilginç olurdu... Bu konuda her iki taraftan da iddialar duydum.

Ağ türünü algılama, işleme ve profil çıkarma

Artık web uygulamamızı arabelleğe alma (veya tahmine dayalı önbellek) sağlayabildiğimize göre uygulamamızı daha akıllı hale getiren uygun bağlantı algılama özelliklerini sağlamamız gerekiyor. Mobil uygulama geliştirmenin çevrimiçi/çevrimdışı modlar ve bağlantı hızı konusunda son derece hassas hale geldiği nokta burasıdır. The Network Information API'yi girin. Bir sunumda bu özelliği her gösterdiğimde izleyicilerden biri söz istiyor ve “Bunu ne için kullanırım?” diye soruyor. Son derece akıllı bir mobil web uygulaması oluşturmanın olası bir yolunu burada bulabilirsiniz.

Öncelikle sıkıcı bir sağduyulu senaryo... Yüksek hızlı tren kullanırken bir mobil cihazdan web'e bağlanılırken, ağ çeşitli anlarda kaybolabilir ve farklı coğrafyalar farklı iletim hızlarını destekleyebilir (ör. Bazı kentsel alanlarda HSPA veya 3G kullanılabilir, ancak uzak bölgeler çok daha yavaş 2G teknolojilerini destekleyebilir). Aşağıdaki kod, bağlantı senaryolarının çoğuna yöneliktir.

Aşağıdaki kod şunları sağlar:

  • applicationCache aracılığıyla çevrimdışı erişim.
  • Yer işareti eklenmişse ve çevrimdışıysa algılar.
  • Çevrimdışından çevrimiçine geçerken ve çevrimiçinden çevrimiçine geçiş yaparken algılar.
  • Yavaş bağlantıları algılar ve ağ türüne göre içeriği getirir.

Bu özelliklerin tümü için çok az kod gerekir. İlk olarak etkinliklerimizi ve yükleme senaryolarımızı tespit ederiz:

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

Yukarıdaki EventListeners bölümünde, kodumuzun bir etkinlikten mi yoksa gerçek bir sayfa isteğinden veya yenilemeden mi çağrıldığını bildirmemiz gerekir. Bunun temel nedeni, çevrimiçi ve çevrimdışı mod arasında geçiş yapılırken onload gövdesi etkinliğinin tetiklenmemesidir.

Şimdi de ononline veya onload etkinliği için basit bir kontrol yapalım. Bu kod, çevrimdışından çevrimiçiye geçerken devre dışı olan bağlantıları sıfırlar, ancak bu uygulama daha gelişmiş olsaydı içerik getirmeye devam edecek bir mantık ekleyebilir veya kesintili bağlantılar için kullanıcı deneyimini işleyebilirsiniz.

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

Aynı durum processOffline() için de geçerlidir. Burada uygulamanızı çevrimdışı mod için değiştirir ve arka planda gerçekleşen işlemleri kurtarmaya çalışırsınız. Aşağıdaki kod tüm harici bağlantılarımızı bulur ve devre dışı bırakır. Böylece, kullanıcılar çevrimdışı uygulamamızda ÖNCEKİ vaha'ya mahkumdur!

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

    }
  }
}

Tamam, elimizdeki güzel işlere devam edelim. Uygulamamız artık hangi bağlı durumda olduğunu bildiğine göre, internete bağlıyken bağlantı türünü de kontrol edip gerekli ayarları yapabiliriz. Kuzey Amerikalı sağlayıcıların tipik indirme işlemlerini ve her bir bağlantıyla ilgili yorumlarda yaşanan gecikmeleri listeledim.

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

FetchAndCache sürecimizde pek çok ayarlama yapabildik, ancak burada tek yaptığım belirli bir bağlantı için kaynakları eşzamansız (true) veya eşzamanlı (false) getirmesini söylemekti.

Edge (Eşzamanlı) İstek Zaman Çizelgesi

Edge Senkronizasyonu

Kablosuz (Asenkron) İstek Zaman Çizelgesi

Kablosuz Ağ Eş Zamansız

Bu, yavaş veya hızlı bağlantılara dayalı olarak en azından bir miktar kullanıcı deneyimi ayarlamasına olanak tanır. Bu, nihai sonuç sağlayacak bir çözüm değildir. Bir diğer yöntem de bağlantı tıklandığında (yavaş bağlantılarda) yükleme kalıcı iletişim kutusunu açmak ve uygulama bu bağlantının sayfasını arka planda almaya devam etmektir. Buradaki önemli nokta, gecikmeleri azaltırken kullanıcının HTML5'in sunduğu en yeni ve en iyi öğelerle bağlantısının tüm yeteneklerinden yararlanmaktır. Ağ algılama demosunu buradan görüntüleyebilirsiniz.

Sonuç

Mobil HTML5 uygulamalarındaki yolculuk daha yeni başlıyor. Artık, yalnızca HTML5'e dayanan bir mobil "çerçevenin" çok basit ve temel temellerini ve destekleyici teknolojileri görebilirsiniz. Geliştiricilerin bu özelliklerle çalışması, özünde ele almaları ve bir sarmalayıcı tarafından maskelenmemeleri gerektiğini düşünüyorum.