Teknik HTML5 untuk mengoptimalkan performa seluler

Pengantar

Pemuatan ulang berputar, transisi halaman yang tersendat-sendat, dan penundaan berkala dalam peristiwa ketuk hanyalah beberapa masalah yang sering terjadi di lingkungan web seluler saat ini. Developer mencoba untuk mendekati native sedekat mungkin, tetapi sering kali terganggu oleh peretasan, reset, dan framework yang kaku.

Dalam artikel ini, kita akan membahas persyaratan minimum untuk membuat aplikasi web HTML5 seluler. Poin utamanya adalah mengungkap kompleksitas tersembunyi yang coba disembunyikan oleh framework seluler saat ini. Anda akan melihat pendekatan minimalis (menggunakan API HTML5 inti) dan dasar-dasar yang akan memungkinkan Anda menulis framework sendiri atau berkontribusi pada framework yang saat ini Anda gunakan.

Akselerasi hardware

Biasanya, GPU menangani diagram CAD atau pemodelan 3D yang mendetail, tetapi dalam kasus ini, kita ingin gambar primitif (div, latar belakang, teks dengan drop shadow, gambar, dll.) kita muncul dengan lancar dan dianimasikan dengan lancar melalui GPU. Sayangnya, sebagian besar developer front-end menyerahkan proses animasi ini ke framework pihak ketiga tanpa mengkhawatirkan semantiknya, tetapi apakah fitur CSS3 inti ini harus disamarkan? Izinkan saya memberi Anda beberapa alasan mengapa peduli terhadap hal ini penting:

  1. Alokasi memori dan beban komputasi — Jika Anda menggabungkan setiap elemen di DOM hanya demi akselerasi hardware, orang berikutnya yang mengerjakan kode Anda mungkin akan mengejar Anda dan memukuli Anda dengan parah.

  2. Konsumsi Daya — Tentu saja, saat hardware mulai bekerja, baterai juga akan terkuras. Saat mengembangkan aplikasi untuk perangkat seluler, developer harus mempertimbangkan berbagai batasan perangkat saat menulis aplikasi web seluler. Hal ini akan menjadi lebih umum karena pembuat browser mulai mengaktifkan akses ke semakin banyak hardware perangkat.

  3. Konflik — Saya mengalami perilaku tidak lancar saat menerapkan akselerasi hardware ke bagian halaman yang sudah diakselerasi. Jadi, mengetahui apakah Anda memiliki akselerasi yang tumpang-tindih sangat penting.

Untuk membuat interaksi pengguna lancar dan sedekat mungkin dengan native, kita harus membuat browser bekerja untuk kita. Idealnya, kita ingin CPU perangkat seluler menyiapkan animasi awal, lalu GPU bertanggung jawab untuk menggabungkan berbagai lapisan selama proses animasi. Inilah yang dilakukan translate3d, scale3d, dan translateZ — mereka memberikan lapisan sendiri pada elemen yang dianimasikan, sehingga memungkinkan perangkat merender semuanya secara lancar. Untuk mengetahui lebih lanjut tentang komposisi yang dipercepat dan cara kerja WebKit, Ariya Hidayat memiliki banyak info bagus di blognya.

Transisi halaman

Mari kita lihat tiga pendekatan interaksi pengguna yang paling umum saat mengembangkan aplikasi web seluler: efek geser, balik, dan rotasi.

Anda dapat melihat cara kerja kode ini di sini http://slidfast.appspot.com/slide-flip-rotate.html (Catatan: Demo ini dibuat untuk perangkat seluler, jadi buka emulator, gunakan ponsel atau tablet, atau kurangi ukuran jendela browser Anda menjadi ~1024 piksel atau kurang).

Pertama, kita akan membedah transisi slide, balik, dan rotasi serta cara mempercepatnya. Perhatikan bagaimana setiap animasi hanya memerlukan tiga atau empat baris CSS dan JavaScript.

Menggeser

Pendekatan transisi yang paling umum dari ketiga pendekatan transisi, transisi halaman geser meniru nuansa aplikasi seluler native. Transisi slide dipanggil untuk menampilkan area konten baru ke dalam area tampilan.

Untuk efek slide, pertama-tama kita mendeklarasikan markup:

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

Perhatikan bagaimana kita memiliki konsep penataan halaman ke kiri atau kanan. Pada dasarnya bisa ke arah mana saja, tetapi ini yang paling umum.

Sekarang kita memiliki animasi plus akselerasi hardware hanya dengan beberapa baris CSS. Animasi sebenarnya terjadi saat kita menukar class pada elemen div halaman.

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

translate3d(0,0,0) dikenal sebagai pendekatan “peluru ajaib”.

Saat pengguna mengklik elemen navigasi, kita menjalankan JavaScript berikut untuk menukar class. Tidak ada framework pihak ketiga yang digunakan, ini adalah JavaScript murni. ;)

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 atau stage-right menjadi stage-center dan memaksa halaman meluncur ke area pandang tengah. Kita sepenuhnya bergantung pada CSS3 untuk melakukan tugas berat ini.

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

.stage-right {
  left: 480px;
}

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

Selanjutnya, mari kita lihat CSS yang menangani deteksi dan orientasi perangkat seluler. Kita dapat menangani setiap perangkat dan setiap resolusi (lihat resolusi kueri media). Saya hanya menggunakan beberapa contoh sederhana dalam demo ini untuk mencakup sebagian besar tampilan potret dan lanskap di perangkat seluler. Hal ini juga berguna untuk menerapkan akselerasi hardware per perangkat. Misalnya, karena WebKit versi desktop mempercepat semua elemen yang diubah (terlepas dari apakah elemen tersebut 2D atau 3D), sebaiknya buat kueri media dan kecualikan akselerasi di tingkat tersebut. Perhatikan bahwa trik akselerasi hardware tidak memberikan peningkatan kecepatan apa pun di Android Froyo 2.2+. Semua komposisi dilakukan dalam software.

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

Membalik

Di perangkat seluler, membalikkan dikenal sebagai menggeser halaman. Di sini kita menggunakan beberapa JavaScript sederhana untuk menangani peristiwa ini di perangkat iOS dan Android (berbasis WebKit).

Lihat cara kerjanya di http://slidfast.appspot.com/slide-flip-rotate.html.

Saat menangani peristiwa sentuh dan transisi, hal pertama yang perlu Anda lakukan adalah mendapatkan posisi elemen saat ini. Lihat dokumen ini untuk mengetahui informasi selengkapnya tentang WebKitCSSMatrix.

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

Karena kita menggunakan transisi ease-out CSS3 untuk membalik halaman, element.offsetLeft biasa tidak akan berfungsi.

Selanjutnya, kita ingin mengetahui arah balik pengguna dan menetapkan nilai minimum agar peristiwa (navigasi halaman) terjadi.

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

Anda juga akan melihat bahwa kami mengukur swipeTime dalam milidetik. Hal ini memungkinkan peristiwa navigasi diaktifkan jika pengguna menggeser layar dengan cepat untuk membuka halaman.

Untuk memosisikan halaman dan membuat animasi terlihat native saat jari menyentuh layar, kita menggunakan transisi CSS3 setelah setiap peristiwa diaktifkan.

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

Saya mencoba bereksperimen dengan cubic-bezier untuk memberikan nuansa native terbaik pada transisi, tetapi ease-out berhasil.

Terakhir, untuk melakukan navigasi, kita harus memanggil metode slideTo() yang telah ditentukan sebelumnya yang kita gunakan dalam demo terakhir.

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

Memutar

Selanjutnya, mari kita lihat animasi rotasi yang digunakan dalam demo ini. Kapan saja, Anda dapat memutar halaman yang sedang Anda lihat 180 derajat untuk menampilkan sisi sebaliknya dengan mengetuk opsi menu “Kontak”. Sekali lagi, ini hanya memerlukan beberapa baris CSS dan beberapa JavaScript untuk menetapkan class transisi onclick. CATATAN: Transisi rotasi tidak dirender dengan benar di sebagian besar versi Android karena tidak memiliki kemampuan transformasi CSS 3D. Sayangnya, alih-alih mengabaikan pembalikan, Android membuat halaman "berputar" dengan memutar, bukan membalik. Sebaiknya gunakan transisi ini dengan hemat hingga dukungannya ditingkatkan.

Markup (konsep dasar depan dan belakang):

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

Men-debug akselerasi hardware

Setelah kita membahas transisi dasar, mari kita lihat mekanisme cara kerjanya dan cara menggabungkannya.

Untuk melakukan sesi penelusuran bug yang ajaib ini, mari buka beberapa browser dan IDE pilihan Anda. Pertama, mulai Safari dari command line untuk menggunakan beberapa variabel lingkungan proses debug. Saya menggunakan Mac, jadi perintahnya mungkin berbeda berdasarkan OS Anda. Buka Terminal, lalu ketik berikut ini:

  • $> export CA_COLOR_OPAQUE=1
  • $> export CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

Tindakan ini akan memulai Safari dengan beberapa helper debug. CA_COLOR_OPAQUE menunjukkan elemen mana yang sebenarnya dikomposisikan atau dipercepat. CA_LOG_MEMORY_USAGE menunjukkan jumlah memori yang kita gunakan saat mengirim operasi gambar ke backing store. Hal ini memberi tahu Anda secara persis seberapa besar tekanan yang Anda berikan pada perangkat seluler, dan mungkin memberikan petunjuk tentang bagaimana penggunaan GPU Anda dapat menguras baterai perangkat target.

Sekarang, mari kita buka Chrome agar kita dapat melihat beberapa informasi frame per detik (FPS) yang baik:

  1. Buka browser web Google Chrome.
  2. Di kolom URL, ketik about:flags.
  3. Scroll ke bawah beberapa item, lalu klik “Aktifkan” untuk Penghitung FPS.

Jika Anda melihat halaman ini di Chrome versi yang telah dioptimalkan, Anda akan melihat penghitung FPS merah di sudut kiri atas.

FPS Chrome

Dengan cara ini, kita tahu bahwa akselerasi hardware diaktifkan. Hal ini juga memberi kita gambaran tentang cara animasi berjalan dan apakah Anda mengalami kebocoran (animasi yang terus berjalan yang seharusnya dihentikan).

Cara lain untuk memvisualisasikan akselerasi hardware adalah dengan membuka halaman yang sama di Safari (dengan variabel lingkungan yang saya sebutkan di atas). Setiap elemen DOM yang dipercepat memiliki warna merah. Tindakan ini menunjukkan dengan tepat apa yang dikomposisikan berdasarkan lapisan. Perhatikan bahwa navigasi putih tidak berwarna merah karena tidak dipercepat.

Kontak Gabungan

Setelan serupa untuk Chrome juga tersedia di “Batas lapisan rendering gabungan” about:flags.

Cara lain yang bagus untuk melihat lapisan komposit adalah dengan melihat demo daun berguguran WebKit saat mod ini diterapkan.

Daun yang Disusun

Terakhir, untuk benar-benar memahami performa hardware grafis aplikasi kita, mari kita lihat cara penggunaan memori. Di sini kita melihat bahwa kita mengirimkan 1,38 MB petunjuk gambar ke buffer CoreAnimation di Mac OS. Buffer memori Core Animation dibagikan antara OpenGL ES dan GPU untuk membuat piksel akhir yang Anda lihat di layar.

Coreanimation 1

Saat kita mengubah ukuran atau memaksimalkan jendela browser, kita juga akan melihat memori diperluas.

Coreanimation 2

Hal ini memberi Anda gambaran tentang penggunaan memori di perangkat seluler Anda hanya jika Anda mengubah ukuran browser ke dimensi yang benar. Jika Anda men-debug atau menguji lingkungan iPhone, ubah ukurannya menjadi 480x320 piksel. Sekarang kita memahami persis cara kerja akselerasi hardware dan apa yang diperlukan untuk men-debug. Membaca tentangnya adalah satu hal, tetapi melihat buffer memori GPU bekerja secara visual benar-benar memberikan perspektif.

Di Balik Layar: Mengambil dan Menyimpan dalam Cache

Sekarang saatnya meningkatkan kualitas penayangan halaman dan penyimpanan cache resource. Seperti pendekatan yang digunakan JQuery Mobile dan framework serupa, kita akan melakukan pengambilan data dan menyimpan halaman dalam cache dengan panggilan AJAX serentak.

Mari kita bahas beberapa masalah inti web seluler dan alasan mengapa kita perlu melakukannya:

  • Pengambilan: Melakukan pra-pengambilan halaman kami memungkinkan pengguna menggunakan aplikasi secara offline dan juga memungkinkan tidak ada waktu tunggu di antara tindakan navigasi. Tentu saja, kita tidak ingin membatasi bandwidth perangkat saat perangkat terhubung ke internet, jadi kita perlu menggunakan fitur ini dengan hemat.
  • Caching: Selanjutnya, kita menginginkan pendekatan serentak atau asinkron saat mengambil dan menyimpan halaman ini dalam cache. Kita juga perlu menggunakan localStorage (karena didukung dengan baik di antara perangkat) yang sayangnya tidak bersifat asinkron.
  • AJAX dan mengurai respons: Menggunakan innerHTML() untuk menyisipkan respons AJAX ke dalam DOM berbahaya (dan tidak dapat diandalkan?). Sebagai gantinya, kita menggunakan mekanisme yang andal untuk penyisipan respons AJAX dan penanganan panggilan serentak. Kami juga memanfaatkan beberapa fitur baru HTML5 untuk mengurai xhr.responseText.

Dengan memanfaatkan kode dari demo Slide, Balik, dan Putar, kita mulai dengan menambahkan beberapa halaman sekunder dan menautkannya. Kemudian, kami akan mengurai link dan membuat transisi secara langsung.

Layar Utama iPhone

Lihat demo Pengambilan dan Cache di sini.

Seperti yang dapat Anda lihat, kita memanfaatkan markup semantik di sini. Hanya link ke halaman lain. Halaman turunan mengikuti struktur node/class yang sama dengan induknya. Kita dapat melangkah lebih jauh dan menggunakan atribut data-* untuk node “halaman”, dll. Berikut adalah halaman detail (turunan) yang berada dalam file html terpisah (/demo2/home-detail.html) yang akan dimuat, di-cache, dan disiapkan untuk transisi saat aplikasi dimuat.

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

Sekarang, mari kita lihat JavaScript-nya. Demi kesederhanaan, saya tidak menyertakan helper atau pengoptimalan apa pun dalam kode. Yang kita lakukan di sini hanyalah melakukan looping melalui array node DOM tertentu untuk menemukan link yang akan diambil dan di-cache. Catatan—Untuk demo ini, metode fetchAndCache() ini dipanggil saat halaman dimuat. Kita akan mengerjakannya kembali di bagian berikutnya saat mendeteksi koneksi jaringan dan menentukan kapan harus dipanggil.

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

Kami memastikan pemrosesan pasca-pemrosesan asinkron yang tepat melalui penggunaan objek “AJAX”. Ada penjelasan yang lebih mendalam tentang penggunaan localStorage dalam panggilan AJAX di Working Off the Grid with HTML5 Offline. Dalam contoh ini, Anda akan melihat penggunaan dasar caching pada setiap permintaan dan menyediakan objek yang di-cache saat server menampilkan respons selain respons yang berhasil (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;
      }
    }
  }
}

Sayangnya, karena localStorage menggunakan UTF-16 untuk encoding karakter, setiap byte tunggal disimpan sebagai 2 byte sehingga batas penyimpanan kita dari 5 MB menjadi total 2,6 MB. Seluruh alasan untuk mengambil dan menyimpan dalam cache halaman/markup ini di luar cakupan cache aplikasi akan diungkapkan di bagian berikutnya.

Dengan kemajuan terbaru dalam elemen iframe dengan HTML5, kita kini memiliki cara yang sederhana dan efektif untuk mengurai responseText yang kita dapatkan kembali dari panggilan AJAX. Ada banyak parser JavaScript dan ekspresi reguler 3000 baris yang menghapus tag skrip dan sebagainya. Namun, mengapa tidak membiarkan browser melakukan yang terbaik? Dalam contoh ini, kita akan menulis responseText ke dalam iframe tersembunyi sementara. Kami menggunakan atribut “sandbox” HTML5 yang menonaktifkan skrip dan menawarkan banyak fitur keamanan…

Dari spesifikasi: Atribut sandbox, jika ditentukan, mengaktifkan serangkaian batasan tambahan pada konten apa pun yang dihosting oleh iframe. Nilainya harus berupa kumpulan token unik yang tidak berurutan dan dipisahkan spasi yang tidak peka huruf besar/kecil ASCII. Nilai yang diizinkan adalah allow-forms, allow-same-origin, allow-scripts, dan allow-top-navigation. Jika atribut ini disetel, konten akan dianggap berasal dari origin unik, formulir dan skrip akan dinonaktifkan, link tidak dapat menargetkan konteks penjelajahan lain, dan plugin akan dinonaktifkan.

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 menolak untuk memindahkan node secara implisit dari satu dokumen ke dokumen lain dengan benar. Error akan muncul jika node turunan baru dibuat dalam dokumen yang berbeda. Jadi di sini kita menggunakan adoptNode dan semuanya berjalan lancar.

Jadi, mengapa iframe? Mengapa tidak menggunakan innerHTML saja? Meskipun innerHTML kini menjadi bagian dari spesifikasi HTML5, memasukkan respons dari server (jahat atau baik) ke area yang tidak diperiksa adalah praktik yang berbahaya. Saat menulis artikel ini, saya tidak menemukan siapa pun yang menggunakan selain innerHTML. Saya tahu JQuery menggunakannya di intinya dengan penggantian penambahan hanya pada pengecualian. JQuery Mobile juga menggunakannya. Namun, saya belum melakukan pengujian berat terkait “innerHTML berhenti berfungsi secara acak”, tetapi akan sangat menarik untuk melihat semua platform yang terpengaruh. Menarik juga untuk melihat pendekatan mana yang lebih berperforma… Saya juga telah mendengar klaim dari kedua belah pihak mengenai hal ini.

Deteksi, penanganan, dan pembuatan profil jenis jaringan

Sekarang setelah kita memiliki kemampuan untuk melakukan buffering (atau cache prediktif) aplikasi web, kita harus menyediakan fitur deteksi koneksi yang tepat agar aplikasi kita lebih pintar. Di sinilah pengembangan aplikasi seluler menjadi sangat sensitif terhadap mode online/offline dan kecepatan koneksi. Masukkan Network Information API. Setiap kali saya menunjukkan fitur ini dalam presentasi, seseorang di audiens mengangkat tangan dan bertanya “Untuk apa saya menggunakannya?”. Jadi, berikut adalah kemungkinan cara untuk menyiapkan aplikasi web seluler yang sangat cerdas.

Skenario akal sehat yang membosankan terlebih dahulu… Saat berinteraksi dengan Web dari perangkat seluler di kereta berkecepatan tinggi, jaringan mungkin hilang pada berbagai waktu dan geografi yang berbeda mungkin mendukung kecepatan transmisi yang berbeda (misalnya, HSPA atau 3G mungkin tersedia di beberapa area perkotaan, tetapi area terpencil mungkin mendukung teknologi 2G yang jauh lebih lambat). Kode berikut menangani sebagian besar skenario koneksi.

Kode berikut memberikan:

  • Akses offline melalui applicationCache.
  • Mendeteksi apakah ditandai dan offline.
  • Mendeteksi saat beralih dari offline ke online dan sebaliknya.
  • Mendeteksi koneksi lambat dan mengambil konten berdasarkan jenis jaringan.

Sekali lagi, semua fitur ini hanya memerlukan sedikit kode. Pertama, kita mendeteksi peristiwa dan skenario pemuatan:

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

Di EventListener di atas, kita harus memberi tahu kode kita apakah kode tersebut dipanggil dari peristiwa atau permintaan atau pemuatan ulang halaman yang sebenarnya. Alasan utamanya adalah karena peristiwa onload body tidak akan diaktifkan saat beralih antara mode online dan offline.

Selanjutnya, kita memiliki pemeriksaan sederhana untuk peristiwa ononline atau onload. Kode ini mereset link yang dinonaktifkan saat beralih dari offline ke online, tetapi jika aplikasi ini lebih canggih, Anda dapat menyisipkan logika yang akan melanjutkan pengambilan konten atau menangani UX untuk koneksi yang terputus-putus.

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

Hal yang sama berlaku untuk processOffline(). Di sini, Anda akan memanipulasi aplikasi untuk mode offline dan mencoba memulihkan transaksi apa pun yang sedang berlangsung di latar belakang. Kode di bawah ini akan menggali semua link eksternal kami dan menonaktifkannya—memerangkap pengguna di aplikasi offline kami SELAMANYA, 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);

    }
  }
}

Oke, sekarang kita bahas hal-hal yang menarik. Sekarang setelah aplikasi kita mengetahui status koneksinya, kita juga dapat memeriksa jenis koneksi saat aplikasi online dan menyesuaikannya. Saya telah mencantumkan kecepatan download dan latensi penyedia umum di Amerika Utara dalam komentar untuk setiap koneksi.

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

Ada banyak penyesuaian yang dapat kita lakukan pada proses fetchAndCache, tetapi yang saya lakukan di sini hanyalah memberi tahu proses tersebut untuk mengambil resource secara asinkron (benar) atau sinkron (salah) untuk koneksi tertentu.

Linimasa Permintaan Edge (Sinkron)

Sinkronisasi Tepi

Linimasa Permintaan WIFI (Asinkron)

WIFI Async

Hal ini memungkinkan setidaknya beberapa metode penyesuaian pengalaman pengguna berdasarkan koneksi lambat atau cepat. Ini sama sekali bukan solusi yang sempurna. Tugas lain yang dapat dilakukan adalah menampilkan modal pemuatan saat link diklik (pada koneksi lambat) sementara aplikasi mungkin masih mengambil halaman link tersebut di latar belakang. Poin penting di sini adalah mengurangi latensi sekaligus memanfaatkan kemampuan penuh koneksi pengguna dengan HTML5 terbaru dan terbaik. Lihat demo deteksi jaringan di sini.

Kesimpulan

Perjalanan menuju aplikasi HTML5 seluler baru saja dimulai. Sekarang Anda melihat dasar-dasar yang sangat sederhana dari “framework” seluler yang dibangun hanya di sekitar HTML5 dan teknologi pendukungnya. Saya rasa penting bagi developer untuk menangani dan mengatasi fitur ini pada intinya, bukan menyembunyikannya dengan wrapper.