Teknik HTML5 untuk mengoptimalkan performa seluler

Wesley Hales
Wesley Hales

Pengantar

Penyegaran yang berputar, transisi halaman yang putus-putus, dan penundaan berkala pada peristiwa ketuk hanyalah beberapa hal yang membingungkan dalam lingkungan web seluler saat ini. Developer berusaha sedekat mungkin dengan native, tetapi sering kali tergelincir oleh peretasan, reset, dan framework yang kaku.

Dalam artikel ini, kita akan membahas hal-hal minimum yang diperlukan untuk membuat aplikasi web HTML5 seluler. Poin utamanya adalah menyingkap kompleksitas tersembunyi yang coba disembunyikan oleh framework seluler saat ini. Anda akan melihat pendekatan minimalis (menggunakan API HTML5 inti) dan dasar-dasar dasar yang akan memberdayakan Anda untuk menulis kerangka kerja sendiri atau berkontribusi pada kerangka yang sedang digunakan.

Akselerasi hardware

Biasanya, GPU menangani pemodelan 3D atau diagram CAD yang mendetail, tetapi dalam hal ini, kita ingin gambar primitif (divs, latar belakang, teks dengan drop shadow, gambar, dll.) tampak mulus dan bergerak dengan lancar melalui GPU. Sayangnya, sebagian besar developer front-end mengalihkan proses animasi ini ke framework pihak ketiga tanpa mengkhawatirkan semantiknya, tetapi apakah fitur CSS3 inti ini harus disamarkan? Izinkan saya menjelaskan beberapa alasan mengapa kepedulian tentang hal ini penting:

  1. Alokasi memori dan beban komputasi — Jika Anda berkeliling mengomposisikan setiap elemen dalam DOM hanya demi akselerasi perangkat keras, orang berikutnya yang mengerjakan kode Anda mungkin akan mengejar Anda dan mengalahkan Anda dengan susah payah.

  2. Konsumsi Daya — Tentu saja, saat perangkat keras digunakan, baterai akan demikian. Saat mengembangkan untuk seluler, developer dipaksa untuk mempertimbangkan berbagai batasan perangkat saat menulis aplikasi web seluler. Hal ini akan semakin lazim terjadi karena pembuat browser mulai memungkinkan akses ke semakin banyak hardware perangkat.

  3. Konflik — Saya menemukan perilaku gangguan saat menerapkan akselerasi hardware ke bagian halaman yang sudah dipercepat. 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 berfungsi untuk kita. Idealnya, kita ingin CPU perangkat seluler menyiapkan animasi awal, lalu meminta GPU bertanggung jawab untuk hanya mengomposisikan lapisan yang berbeda selama proses animasi. Inilah yang dilakukan translate3d, scale3d, dan translateZ — ketiganya memberikan elemen animasi dengan lapisan mereka sendiri, sehingga memungkinkan perangkat merender semuanya dengan lancar. Untuk mengetahui lebih lanjut tentang pengomposisian yang dipercepat dan cara kerja WebKit, Ariya Hidayat memiliki banyak informasi bermanfaat di blog-nya.

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 aktifkan emulator, gunakan ponsel atau tablet, atau kurangi ukuran jendela browser menjadi ~1024 piksel atau kurang).

Pertama, kita akan membedah transisi slide, balik, dan rotasi serta bagaimana transisinya dipercepat. Perhatikan, setiap animasi hanya membutuhkan tiga atau empat baris CSS dan JavaScript.

Geser

Yang paling umum dari tiga pendekatan transisi, transisi halaman geser meniru nuansa native aplikasi seluler. Transisi slide dipanggil untuk menghadirkan area konten baru ke dalam port tampilan.

Untuk efek slide, kita mendeklarasikan markup terlebih dahulu:

<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 halaman staging di kiri atau kanan. Intinya bisa ke mana saja, tetapi ini yang paling umum.

Sekarang kita memiliki animasi serta akselerasi hardware hanya dengan beberapa baris CSS. Animasi yang sebenarnya terjadi ketika 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 "perakitan".

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

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 bergeser ke port tampilan tengah. Kami sepenuhnya bergantung pada CSS3 untuk melakukan tugas berat.

.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 pada perangkat seluler. Hal ini juga berguna untuk menerapkan akselerasi hardware per perangkat. Misalnya, karena WebKit versi desktop mempercepat semua elemen yang ditransformasi (terlepas apakah itu 2-D atau 3-D), masuk akal untuk membuat kueri media dan mengecualikan akselerasi pada tingkat itu. Perhatikan bahwa trik akselerasi hardware tidak memberikan peningkatan kecepatan apa pun di Android Froyo 2.2+. Semua komposisi dilakukan di 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

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

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

Ketika berhadapan dengan peristiwa sentuh dan transisi, hal pertama yang perlu Anda lakukan adalah mendapatkan handle pada posisi elemen saat ini. Lihat dokumen ini untuk 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 easy-out CSS3 untuk pembalikan halaman, element.offsetLeft biasa tidak akan berfungsi.

Selanjutnya, kita ingin mengetahui ke arah mana pengguna membalik dan menetapkan ambang batas 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 juga mengukur swipeTime dalam milidetik. Hal ini memungkinkan peristiwa navigasi diaktifkan jika pengguna menggeser layar dengan cepat untuk membalik halaman.

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

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 bermain-main dengan cubic-bezier untuk memberikan nuansa native terbaik pada transisi, tetapi easy-out berhasil.

Terakhir, untuk melakukan navigasi, kita harus memanggil metode slideTo() yang telah ditentukan sebelumnya, yang digunakan 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. Anda dapat memutar halaman yang sedang Anda lihat 180 derajat kapan saja 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 pada sebagian besar versi Android karena tidak memiliki kemampuan transformasi CSS 3D. Sayangnya, Android membuat halaman "berputar" dengan memutar, bukan membaliknya, bukan mengabaikan flip. Sebaiknya gunakan transisi ini seperlunya hingga dukungan membaik.

Markup (konsep dasar bagian 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 membahas transisi dasar, mari kita lihat mekanisme cara kerjanya dan komposisinya.

Untuk mewujudkan sesi proses debug yang ajaib ini, mari jalankan beberapa browser dan IDE pilihan Anda. Pertama-tama, jalankan Safari dari command line untuk memanfaatkan beberapa variabel lingkungan proses debug. Saya menggunakan Mac, jadi perintahnya mungkin berbeda tergantung OS Anda. Buka Terminal dan ketik perintah berikut:

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

Ini akan memulai Safari dengan beberapa helper proses debug. CA_COLOR_OPAQUE menunjukkan kepada kita elemen mana yang sebenarnya digabungkan atau dipercepat. CA_LOG_MEMORY_USAGE menunjukkan berapa banyak memori yang digunakan saat mengirim operasi gambar ke backing store. Ini menunjukkan dengan tepat berapa banyak ketegangan yang Anda berikan pada perangkat seluler, dan mungkin memberikan petunjuk tentang bagaimana penggunaan GPU Anda mungkin menghabiskan baterai perangkat target.

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

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

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

FPS Chrome

Beginilah cara kami mengetahui bahwa akselerasi hardware diaktifkan. Hal ini juga memberi kita gambaran tentang bagaimana animasi berjalan dan apakah ada kebocoran (animasi yang berjalan terus-menerus yang harus dihentikan).

Cara lain untuk benar-benar memvisualisasikan akselerasi hardware adalah jika Anda membuka halaman yang sama di Safari (dengan variabel lingkungan yang saya sebutkan di atas). Setiap elemen DOM yang dipercepat memiliki tint merah. Ini menunjukkan kepada kita apa yang sedang disusun oleh lapisan. Perhatikan bahwa navigasi putih tidak merah karena tidak dipercepat.

Kontak Gabungan

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

Cara bagus lainnya untuk melihat lapisan gabungan adalah dengan melihat demo daun jatuh WebKit saat mod ini diterapkan.

Daun bergelombang

Dan terakhir, untuk benar-benar memahami performa hardware grafis aplikasi kita, mari kita lihat bagaimana memori dikonsumsi. Di sini kita melihat bahwa kita mendorong 1,38 MB instruksi menggambar ke buffer CoreAnimation di Mac OS. Buffer memori Core Animation digunakan bersama oleh OpenGL ES dan GPU untuk membuat piksel akhir yang Anda lihat di layar.

Animasi Inti 1

Bila kita hanya mengubah ukuran atau memaksimalkan jendela browser, kita melihat memori juga bertambah.

Animasi Inti 2

Hal ini memberi Anda gambaran tentang bagaimana memori dipakai di perangkat seluler hanya jika Anda mengubah ukuran browser ke dimensi yang benar. Jika Anda melakukan debug atau menguji lingkungan iPhone, ubah ukurannya menjadi 480px kali 320px. Sekarang kita telah memahami dengan pasti cara kerja akselerasi perangkat keras dan apa yang diperlukan untuk melakukan debug. Ini adalah satu hal yang perlu dibaca, tetapi melihat buffer memori GPU berfungsi secara visual benar-benar membawa sesuatu ke dalam perspektif.

Di Balik Layar: Mengambil dan Menyimpan ke Cache

Sekarang saatnya meningkatkan cache halaman dan resource kita. Sama seperti pendekatan yang digunakan JQuery Mobile dan framework serupa, kita akan melakukan pra-pengambilan dan meng-cache halaman kita dengan panggilan AJAX serentak.

Mari kita bahas beberapa masalah utama terkait web seluler dan alasan mengapa kita perlu melakukan ini:

  • Pengambilan: Mengambil halaman lebih dahulu memungkinkan pengguna menjadikan aplikasi offline serta memungkinkan tidak menunggu di antara tindakan navigasi. Tentu saja, kami tidak ingin menghabiskan bandwidth perangkat ketika perangkat online, jadi kami perlu menggunakan fitur ini seperlunya.
  • Caching: Berikutnya, kita menginginkan pendekatan serentak atau asinkron saat mengambil dan meng-cache halaman ini. Kita juga perlu menggunakan localStorage (karena sudah didukung dengan baik di antara perangkat) yang sayangnya tidak asinkron.
  • AJAX dan mengurai respons: Menggunakan innerHTML() untuk menyisipkan respons AJAX ke dalam DOM adalah berbahaya (dan tidak dapat diandalkan?). Sebagai gantinya, kami menggunakan mekanisme yang dapat diandalkan untuk penyisipan respons AJAX dan menangani panggilan serentak. Kami juga memanfaatkan beberapa fitur baru HTML5 untuk mengurai xhr.responseText.

Dengan memanfaatkan kode dari demo Geser, Balik, dan Putar, kita memulai dengan menambahkan beberapa halaman sekunder dan menautkannya. Kemudian, kita akan mengurai link dan membuat transisi dengan cepat.

Beranda iPhone

Lihat demo Pengambilan dan Cache di sini.

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

<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. Demi kesederhanaan, saya meninggalkan kode bantuan atau pengoptimalan. Yang kita lakukan di sini hanyalah melakukan loop melalui array simpul DOM yang ditentukan untuk menggali tautan untuk diambil dan disimpan di cache. Catatan—Untuk demo ini, metode fetchAndCache() ini dipanggil saat pemuatan halaman. Kita akan mengerjakannya kembali di bagian berikutnya saat mendeteksi koneksi jaringan dan menentukan kapan koneksi jaringan 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 pasca-pemrosesan asinkron yang tepat melalui penggunaan objek "AJAX". Ada penjelasan lebih lanjut tentang penggunaan localStorage dalam panggilan AJAX dalam Bekerja dari Petak dengan HTML5 Offline. Dalam contoh ini, Anda melihat penggunaan dasar caching pada setiap permintaan dan menyediakan objek yang di-cache ketika server mengembalikan apa pun kecuali 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 kami dari 5 MB menjadi total 2,6 MB. Seluruh alasan untuk mengambil dan meng-cache halaman/markup ini di luar cakupan cache aplikasi akan diungkapkan di bagian berikutnya.

Dengan kemajuan terbaru dalam elemen iframe dengan HTML5, sekarang kita 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, sebaiknya biarkan browser melakukan yang terbaiknya? 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, akan mengaktifkan serangkaian pembatasan tambahan pada konten apa pun yang dihosting oleh iframe. Nilainya harus berupa set token tidak berurutan yang dipisahkan spasi dan tidak peka huruf besar/kecil ASCII. Nilai yang diizinkan adalah allow-forms, allow-same-origin, allow-scripts, dan allow-top-navigation. Jika atribut ditetapkan, konten diperlakukan sebagai berasal dari asal yang unik, formulir dan skrip dinonaktifkan, link dicegah agar tidak menargetkan konteks penjelajahan lain, dan plugin 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 secara implisit memindahkan node dari satu dokumen ke dokumen lainnya. Error terjadi jika node turunan baru dibuat di dokumen yang berbeda. Jadi, di sini kita menggunakan adoptNode dan semuanya berjalan lancar.

Jadi mengapa iframe? Mengapa tidak menggunakan innerHTML saja? Meskipun innerHTML sekarang menjadi bagian dari spesifikasi HTML5, memasukkan respons dari server (jahat atau baik) ke dalam area yang tidak dicentang adalah praktik yang berbahaya. Selama penulisan artikel ini, saya tidak dapat menemukan siapa pun yang menggunakan apa pun selain innerHTML. Saya tahu JQuery menggunakannya pada intinya dengan menambahkan fallback pada pengecualian saja. Dan JQuery Mobile juga menggunakannya. Namun, saya belum melakukan pengujian berat sehubungan dengan innerHTML “stops working random”, tetapi akan sangat menarik untuk melihat semua platform yang terpengaruh. Menarik juga untuk melihat pendekatan mana yang lebih berperforma tinggi... Saya juga pernah mendengar klaim dari kedua belah pihak tentang hal ini.

Deteksi, penanganan, dan pembuatan profil jenis jaringan

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

Skenario akal sehat terlebih dahulu... Saat berinteraksi dengan Web dari perangkat seluler di kereta berkecepatan tinggi, jaringan dapat menghilang pada waktu yang berbeda-beda dan lokasi geografis 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 membahas sebagian besar skenario koneksi.

Kode berikut memberikan:

  • Akses offline melalui applicationCache.
  • Mendeteksi apakah di-bookmark 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 memerlukan kode yang sangat sedikit. 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);

Pada EventListeners di atas, kita harus memberi tahu kode jika dipanggil dari sebuah peristiwa atau permintaan maupun muat ulang halaman yang sebenarnya. Alasan utamanya adalah karena peristiwa onload isi 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 memasukkan 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 Anda untuk mode offline dan mencoba memulihkan transaksi apa pun yang terjadi di balik layar. Kode di bawah ini menggali semua link eksternal kami dan menonaktifkannya—menjebak 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, mari kita bahas hal-hal yang bagus. Setelah aplikasi mengetahui status terhubung, kita juga dapat memeriksa jenis koneksi saat online dan menyesuaikannya. Saya telah mencantumkan download dan latensi penyedia di Amerika Utara yang umum 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 mengambil resource asinkron (true) atau sinkron (false) untuk koneksi tertentu.

Linimasa Permintaan Edge (Sinkron)

Sinkronisasi Edge

Linimasa Permintaan Wi-Fi (Asinkron)

Asinkron Wi-Fi

Hal ini memungkinkan setidaknya beberapa metode penyesuaian pengalaman pengguna berdasarkan koneksi yang lambat atau cepat. Ini bukanlah solusi yang menyeluruh. Hal lain yang perlu dilakukan adalah menampilkan modal pemuatan saat link diklik (pada koneksi yang lambat) sementara aplikasi mungkin masih mengambil halaman link tersebut di latar belakang. Poin utama di sini adalah untuk mengurangi latensi sambil memanfaatkan kemampuan penuh koneksi pengguna dengan HTML5 terbaru dan terbaik yang ditawarkan. Lihat demo deteksi jaringan di sini.

Kesimpulan

Perjalanan dalam pengembangan aplikasi HTML5 seluler baru saja dimulai. Sekarang Anda melihat dasar-dasar yang sangat sederhana dan mendasar dari "kerangka kerja" seluler yang dibangun hanya di sekitar HTML5 dan mendukung teknologi pendukungnya. Menurut saya, developer harus bekerja dengan dan menangani fitur ini sebagai intinya serta tidak disamarkan oleh wrapper.