मोबाइल परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के लिए HTML5 तकनीकें

परिचय

आज के मोबाइल वेब एनवायरमेंट में, कई समस्याएं आती हैं. जैसे, पेज रीफ़्रेश होने में समय लगना, पेज ट्रांज़िशन में रुकावट आना, और टैप इवेंट में समय-समय पर देरी होना. डेवलपर, नेटिव ऐप्लिकेशन के जितना हो सके उतना करीब पहुंचने की कोशिश कर रहे हैं. हालांकि, वे अक्सर हैक, रीसेट, और मुश्किल फ़्रेमवर्क की वजह से ऐसा नहीं कर पाते.

इस लेख में, हम मोबाइल एचटीएमएल5 वेब ऐप्लिकेशन बनाने के लिए ज़रूरी चीज़ों के बारे में बात करेंगे. हमारा मुख्य मकसद, उन मुश्किलों को सामने लाना है जिन्हें आज के मोबाइल फ़्रेमवर्क छिपाने की कोशिश करते हैं. आपको कम से कम संसाधनों का इस्तेमाल करके (कोर एचटीएमएल5 एपीआई का इस्तेमाल करके) और बुनियादी सिद्धांतों के बारे में बताया जाएगा. इससे आपको अपना फ़्रेमवर्क लिखने या फ़िलहाल इस्तेमाल किए जा रहे फ़्रेमवर्क में योगदान देने में मदद मिलेगी.

हार्डवेयर की मदद से तेज़ी लाने की सुविधा

आम तौर पर, GPU का इस्तेमाल 3D मॉडलिंग या सीएडी डायग्राम बनाने के लिए किया जाता है. हालांकि, इस मामले में हमें अपनी प्रिमिटिव ड्रॉइंग (डिव, बैकग्राउंड, ड्रॉप शैडो वाला टेक्स्ट, इमेज वगैरह) को GPU की मदद से स्मूद दिखाना है और उन्हें स्मूद तरीके से ऐनिमेट करना है. अफ़सोस की बात यह है कि ज़्यादातर फ़्रंट-एंड डेवलपर, इस ऐनिमेशन प्रोसेस को तीसरे पक्ष के फ़्रेमवर्क को सौंप रहे हैं. वे सिमैंटिक के बारे में नहीं सोचते. हालाँकि, क्या इन मुख्य CSS3 सुविधाओं को छिपाया जाना चाहिए? हम आपको कुछ वजहें बताते हैं कि इन बातों का ध्यान रखना क्यों ज़रूरी है:

  1. मेमोरी का इस्तेमाल और कंप्यूटेशनल बर्डन — अगर सिर्फ़ हार्डवेयर ऐक्सेलरेट करने के लिए, DOM में मौजूद हर एलिमेंट को कंपोज़िट किया जाता है, तो आपके कोड पर काम करने वाला अगला व्यक्ति आपसे नाराज़ हो सकता है.

  2. पावर की खपत — ज़ाहिर है, हार्डवेयर का इस्तेमाल करने पर बैटरी की खपत भी होती है. मोबाइल के लिए डेवलपमेंट करते समय, डेवलपर को मोबाइल वेब ऐप्लिकेशन लिखते समय, डिवाइस से जुड़ी कई तरह की पाबंदियों को ध्यान में रखना पड़ता है. ब्राउज़र बनाने वाली कंपनियां, डिवाइस के ज़्यादा से ज़्यादा हार्डवेयर को ऐक्सेस करने की सुविधा चालू करेंगी. इससे, इस तरह की टेक्नोलॉजी का इस्तेमाल और भी ज़्यादा किया जाएगा.

  3. विरोध — मुझे पेज के उन हिस्सों पर हार्डवेयर से तेज़ी लाने की सुविधा लागू करते समय गड़बड़ी का सामना करना पड़ा जिन पर यह सुविधा पहले से लागू थी. इसलिए, यह जानना बहुत ज़रूरी है कि क्या आपके पास ओवरलैपिंग ऐक्सेलरेटेड है.

उपयोगकर्ता के इंटरैक्शन को आसान और नेटिव ऐप्लिकेशन के जितना संभव हो उतना करीब बनाने के लिए, हमें ब्राउज़र का इस्तेमाल करना होगा. हम चाहते हैं कि मोबाइल डिवाइस का सीपीयू, शुरुआती ऐनिमेशन सेट अप करे. इसके बाद, ऐनिमेशन प्रोसेस के दौरान जीपीयू सिर्फ़ अलग-अलग लेयर को कंपोज़ करे. translate3d, scale3d, और translateZ यही काम करते हैं. ये ऐनिमेशन वाले एलिमेंट को उनकी लेयर देते हैं. इससे डिवाइस, सभी एलिमेंट को एक साथ आसानी से रेंडर कर पाता है. ऐक्सलरेटेड कंपोज़िटिंग और WebKit के काम करने के तरीके के बारे में ज़्यादा जानने के लिए, Ariya Hidayat ने अपने ब्लॉग पर बहुत अच्छी जानकारी दी है.

पेज ट्रांज़िशन

आइए, मोबाइल वेब ऐप्लिकेशन डेवलप करते समय, उपयोगकर्ता के इंटरैक्शन के तीन सबसे सामान्य तरीकों पर एक नज़र डालें: स्लाइड, फ़्लिप, और रोटेशन इफ़ेक्ट.

इस कोड को यहां http://slidfast.appspot.com/slide-flip-rotate.html पर देखा जा सकता है (ध्यान दें: यह डेमो मोबाइल डिवाइस के लिए बनाया गया है. इसलिए, एम्युलेटर चालू करें, अपने फ़ोन या टैबलेट का इस्तेमाल करें या अपनी ब्राउज़र विंडो का साइज़ घटाकर ~1024 पिक्सल या इससे कम करें).

सबसे पहले, हम स्लाइड, फ़्लिप, और रोटेशन ट्रांज़िशन के बारे में जानेंगे. साथ ही, यह भी जानेंगे कि इन्हें कैसे तेज़ किया जाता है. ध्यान दें कि हर ऐनिमेशन में, सीएसएस और JavaScript की सिर्फ़ तीन या चार लाइनें होती हैं.

स्लाइडिंग

स्लाइडिंग पेज ट्रांज़िशन, ट्रांज़िशन के तीन तरीकों में से सबसे आम तरीका है. यह मोबाइल ऐप्लिकेशन के नेटिव फ़ील की तरह होता है. स्लाइड ट्रांज़िशन को इसलिए शुरू किया जाता है, ताकि व्यू पोर्ट में नया कॉन्टेंट एरिया दिखे.

स्लाइड इफ़ेक्ट के लिए, सबसे पहले हम अपने मार्कअप की जानकारी देते हैं:

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

ध्यान दें कि हमने पेजों को बाईं या दाईं ओर स्टेज करने का यह कॉन्सेप्ट कैसे इस्तेमाल किया है. यह किसी भी दिशा में हो सकता है, लेकिन आम तौर पर ऐसा होता है.

अब हमारे पास, सीएसएस की कुछ ही लाइनों के साथ एनिमेशन और हार्डवेयर ऐक्सेलरेट करने की सुविधा है. असल ऐनिमेशन तब होता है, जब हम पेज के div एलिमेंट पर क्लास स्वैप करते हैं.

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

translate3d(0,0,0) को “सिल्वर बुलेट” अप्रोच कहा जाता है.

जब उपयोगकर्ता किसी नेविगेशन एलिमेंट पर क्लिक करता है, तो हम क्लास बदलने के लिए इस JavaScript को एक्ज़ीक्यूट करते हैं. इसमें तीसरे पक्ष के किसी फ़्रेमवर्क का इस्तेमाल नहीं किया जा रहा है. यह सीधे तौर पर 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 या stage-right, stage-center में बदल जाता है. इससे पेज, सेंटर व्यू पोर्ट में स्लाइड हो जाता है. हम ज़्यादातर काम के लिए, CSS3 पर पूरी तरह से निर्भर हैं.

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

.stage-right {
  left: 480px;
}

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

इसके बाद, आइए उस सीएसएस पर एक नज़र डालते हैं जो मोबाइल डिवाइस का पता लगाने और ओरिएंटेशन को मैनेज करती है. हम हर डिवाइस और हर रिज़ॉल्यूशन को टारगेट कर सकते हैं (मीडिया क्वेरी रिज़ॉल्यूशन देखें). इस डेमो में, मैंने कुछ आसान उदाहरणों का इस्तेमाल किया है, ताकि मोबाइल डिवाइसों पर पोर्ट्रेट और लैंडस्केप व्यू को कवर किया जा सके. यह सुविधा, हर डिवाइस के लिए हार्डवेयर की मदद से तेज़ी लाने की सुविधा को लागू करने के लिए भी काम आती है. उदाहरण के लिए, WebKit का डेस्कटॉप वर्शन, सभी ट्रांसफ़ॉर्म किए गए एलिमेंट को तेज़ी से रेंडर करता है. भले ही, वे 2-D हों या 3-D. इसलिए, मीडिया क्वेरी बनाना और उस लेवल पर ऐक्सलरेशन को शामिल न करना सही है. ध्यान दें कि हार्डवेयर से तेज़ी लाने वाली तरकीबें, Android Froyo 2.2+ पर काम नहीं करती हैं. सभी कंपोज़िशन सॉफ़्टवेयर में की जाती हैं.

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

फ़्लिप करना

मोबाइल डिवाइसों पर, फ़्लिप करने का मतलब है कि पेज को स्वाइप करके हटाना. यहां हमने iOS और Android (WebKit पर आधारित) डिवाइसों पर इस इवेंट को मैनेज करने के लिए, कुछ सामान्य JavaScript का इस्तेमाल किया है.

इसे इस्तेमाल करने का तरीका http://slidfast.appspot.com/slide-flip-rotate.html पर देखें.

टच इवेंट और ट्रांज़िशन से निपटने के दौरान, आपको सबसे पहले एलिमेंट की मौजूदा पोज़िशन के बारे में जानकारी चाहिए होगी. WebKitCSSMatrix के बारे में ज़्यादा जानने के लिए, यह दस्तावेज़ देखें.

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

पेज फ़्लिप करने के लिए, हम CSS3 ईज़-आउट ट्रांज़िशन का इस्तेमाल कर रहे हैं. इसलिए, सामान्य element.offsetLeft काम नहीं करेगा.

इसके बाद, हमें यह पता लगाना है कि उपयोगकर्ता किस दिशा में फ़्लिप कर रहा है. साथ ही, हमें किसी इवेंट (पेज नेविगेशन) के लिए थ्रेशोल्ड सेट करना है.

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 को मिलीसेकंड में भी मेज़र कर रहे हैं. इससे उपयोगकर्ता के पेज बदलने के लिए स्क्रीन को तेज़ी से स्वाइप करने पर, नेविगेशन इवेंट ट्रिगर हो जाता है.

पेज को पोज़िशन करने और स्क्रीन को छूते समय ऐनिमेशन को नेटिव जैसा दिखाने के लिए, हम हर इवेंट के ट्रिगर होने के बाद 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';
}

मैंने ट्रांज़िशन को सबसे अच्छा नेटिव फ़ील देने के लिए, क्यूबिक-बेज़ियर का इस्तेमाल करने की कोशिश की. हालांकि, ईज़-आउट ने यह काम कर दिया.

आखिर में, नेविगेशन को चालू करने के लिए, हमें पहले से तय की गई slideTo() विधियों को कॉल करना होगा. इनका इस्तेमाल हमने पिछले डेमो में किया था.

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

घुमाया जा रहा है

अब इस डेमो में इस्तेमाल किए जा रहे रोटेट ऐनिमेशन पर एक नज़र डालते हैं. “संपर्क करें” मेन्यू विकल्प पर टैप करके, मौजूदा पेज को 180 डिग्री पर घुमाया जा सकता है. इससे आपको पेज का दूसरा हिस्सा दिखेगा. ट्रांज़िशन क्लास onclick असाइन करने के लिए, सीएसएस की कुछ लाइनों और कुछ JavaScript की ज़रूरत होती है. ध्यान दें: Android के ज़्यादातर वर्शन पर रोटेट ट्रांज़िशन ठीक से रेंडर नहीं होता है, क्योंकि इसमें 3D सीएसएस ट्रांसफ़ॉर्म करने की सुविधाएं नहीं होती हैं. माफ़ करें, Android फ़्लिप करने के बजाय पेज को घुमाकर "कार्टव्हील" कर देता है. हमारा सुझाव है कि जब तक इस सुविधा में सुधार नहीं हो जाता, तब तक इसका इस्तेमाल कम करें.

मार्कअप (सामने और पीछे का बुनियादी कॉन्सेप्ट):

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

सीएसएस:

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

हार्डवेयर की मदद से तेज़ी लाने की सुविधा को डीबग करना

अब हमने बुनियादी ट्रांज़िशन के बारे में जान लिया है. आइए, अब देखते हैं कि ये कैसे काम करते हैं और इन्हें कैसे कंपोज़ किया जाता है.

इस शानदार डीबगिंग सेशन को शुरू करने के लिए, कुछ ब्राउज़र और अपनी पसंद का आईडीई खोलें. कुछ डीबगिंग एनवायरमेंट वैरिएबल का इस्तेमाल करने के लिए, सबसे पहले कमांड लाइन से Safari शुरू करें. मैं Mac का इस्तेमाल कर रहा/रही हूं. इसलिए, आपके ओएस के हिसाब से कमांड अलग-अलग हो सकती हैं. टर्मिनल खोलें और यह टाइप करें:

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

इससे Safari, डीबग करने में मदद करने वाले कुछ टूल के साथ शुरू होता है. CA_COLOR_OPAQUE से पता चलता है कि कौनसे एलिमेंट असल में कंपोज़िट किए गए हैं या तेज़ी से रेंडर किए गए हैं. CA_LOG_MEMORY_USAGE से पता चलता है कि बैकअप स्टोर को ड्रॉइंग ऑपरेशन भेजते समय, हम कितनी मेमोरी का इस्तेमाल कर रहे हैं. इससे आपको यह पता चलता है कि मोबाइल डिवाइस पर कितना लोड पड़ रहा है. साथ ही, इससे यह भी पता चल सकता है कि जीपीयू के इस्तेमाल की वजह से, टारगेट किए गए डिवाइस की बैटरी कितनी खत्म हो रही है.

अब Chrome को शुरू करते हैं, ताकि हम फ़्रेम प्रति सेकंड (एफ़पीएस) की कुछ अच्छी जानकारी देख सकें:

  1. Google Chrome वेब ब्राउज़र खोलें.
  2. यूआरएल बार में, about:flags टाइप करें.
  3. कुछ आइटम नीचे की ओर स्क्रोल करें और FPS काउंटर के लिए “चालू करें” पर क्लिक करें.

अगर आपने Chrome का अपग्रेड किया गया वर्शन इस्तेमाल किया है, तो इस पेज को देखने पर, आपको सबसे ऊपर बाएं कोने में लाल रंग का एफ़पीएस काउंटर दिखेगा.

Chrome FPS

इससे हमें पता चलता है कि हार्डवेयर से तेज़ी लाने की सुविधा चालू है. इससे हमें यह भी पता चलता है कि ऐनिमेशन कैसे काम करता है. साथ ही, यह भी पता चलता है कि कहीं कोई लीक तो नहीं है. लीक का मतलब है कि ऐनिमेशन लगातार चल रहे हैं, जबकि उन्हें बंद होना चाहिए.

हार्डवेयर ऐक्सेलरेट को विज़ुअलाइज़ करने का एक और तरीका यह है कि आप उसी पेज को Safari में खोलें. इसके लिए, आपको ऊपर बताए गए एनवायरमेंट वैरिएबल का इस्तेमाल करना होगा. तेज़ी से लोड होने वाले हर DOM एलिमेंट पर लाल रंग का हल्का निशान होता है. इससे हमें यह पता चलता है कि हर लेयर में क्या-क्या शामिल किया जा रहा है. ध्यान दें कि सफ़ेद नेविगेशन लाल रंग का नहीं है, क्योंकि इसे तेज़ नहीं किया गया है.

मिश्रित संपर्क

Chrome के लिए भी ऐसी ही सेटिंग उपलब्ध है. इसके लिए, about:flags में जाकर “Composited render layer borders” पर जाएं.

कंपोज़िट की गई लेयर देखने का एक और शानदार तरीका है. इसके लिए, इस मॉड को लागू करने के दौरान WebKit फ़ॉलिंग लीव्स डेमो देखें.

कंपोज़िट लीफ़

आखिर में, हमारे ऐप्लिकेशन के ग्राफ़िक्स हार्डवेयर की परफ़ॉर्मेंस को बेहतर तरीके से समझने के लिए, आइए देखते हैं कि मेमोरी का इस्तेमाल कैसे किया जा रहा है. यहां हम देख सकते हैं कि Mac OS पर CoreAnimation बफ़र में, ड्राइंग के 1.38 एमबी के निर्देश भेजे जा रहे हैं. Core Animation के मेमोरी बफ़र, OpenGL ES और GPU के बीच शेयर किए जाते हैं. इससे स्क्रीन पर दिखने वाले फ़ाइनल पिक्सल बनाए जाते हैं.

Coreanimation 1

ब्राउज़र विंडो का साइज़ बदलने या उसे बड़ा करने पर, हमें मेमोरी का इस्तेमाल बढ़ता हुआ दिखता है.

Coreanimation 2

इससे आपको यह पता चलता है कि आपके मोबाइल डिवाइस पर मेमोरी का इस्तेमाल कैसे किया जा रहा है. हालांकि, ऐसा सिर्फ़ तब होता है, जब ब्राउज़र का साइज़ सही डाइमेंशन में बदला जाता है. अगर आपको iPhone के एनवायरमेंट के लिए डीबग या टेस्टिंग करनी है, तो इमेज का साइज़ बदलकर 480 पिक्सल x 320 पिक्सल करें. अब हमें यह अच्छी तरह से समझ आ गया है कि हार्डवेयर ऐक्सेलरेट कैसे काम करता है और इसे डीबग करने के लिए क्या करना होता है. इसके बारे में पढ़ना एक बात है, लेकिन GPU मेमोरी बफ़र को विज़ुअली काम करते हुए देखना, चीज़ों को सही तरीके से समझने में मदद करता है.

बिहाइंड द सीन्स: फ़ेच करना और कैश मेमोरी में सेव करना

अब समय आ गया है कि हम अपने पेज और संसाधन की कैश मेमोरी को अगले लेवल पर ले जाएं. JQuery Mobile और इसी तरह के फ़्रेमवर्क की तरह, हम एक साथ कई AJAX कॉल करके अपने पेजों को पहले से फ़ेच और कैश करेंगे.

आइए, मोबाइल वेब से जुड़ी कुछ मुख्य समस्याओं और उन्हें ठीक करने की वजहों के बारे में जानें:

  • फ़ेच करना: हमारे पेजों को पहले से फ़ेच करने की सुविधा से, उपयोगकर्ता ऐप्लिकेशन को ऑफ़लाइन मोड में इस्तेमाल कर पाते हैं. साथ ही, इससे नेविगेशन से जुड़ी कार्रवाइयों के बीच इंतज़ार करने की ज़रूरत नहीं पड़ती. बेशक, हम नहीं चाहते कि डिवाइस के ऑनलाइन होने पर, डिवाइस का बैंडविथ कम हो जाए. इसलिए, हमें इस सुविधा का इस्तेमाल कम करना होगा.
  • कैशिंग: इसके बाद, हमें इन पेजों को फ़ेच और कैश करते समय, एक साथ या एसिंक्रोनस तरीके का इस्तेमाल करना है. हमें localStorage का भी इस्तेमाल करना होगा, क्योंकि यह सभी डिवाइसों पर काम करता है. हालांकि, यह एसिंक्रोनस नहीं है.
  • AJAX और जवाब को पार्स करना: AJAX से मिले जवाब को DOM में डालने के लिए innerHTML() का इस्तेमाल करना खतरनाक है. साथ ही, यह भरोसेमंद नहीं है. इसके बजाय, हम AJAX रिस्पॉन्स डालने और एक साथ कई कॉल हैंडल करने के लिए, भरोसेमंद तरीके का इस्तेमाल करते हैं. हम xhr.responseText को पार्स करने के लिए, HTML5 की कुछ नई सुविधाओं का भी इस्तेमाल करते हैं.

स्लाइड, फ़्लिप, और रोटेट डेमो के कोड के आधार पर, हम कुछ सेकंडरी पेज जोड़ते हैं और उन्हें लिंक करते हैं. इसके बाद, हम लिंक को पार्स करेंगे और तुरंत ट्रांज़िशन बना देंगे.

iPhone का होम पेज

फ़ेच और कैश मेमोरी के डेमो को यहां देखें.

जैसा कि आप देख सकते हैं, हम यहां सिमैंटिक मार्कअप का इस्तेमाल कर रहे हैं. सिर्फ़ किसी दूसरे पेज का लिंक. चाइल्ड पेज, पैरंट पेज के नोड/क्लास स्ट्रक्चर को फ़ॉलो करता है. हम इसे एक कदम आगे ले जाकर, “page” नोड वगैरह के लिए data-* एट्रिब्यूट का इस्तेमाल कर सकते हैं. यहां अलग एचटीएमएल फ़ाइल (/demo2/home-detail.html) में मौजूद जानकारी वाला पेज (चाइल्ड) दिया गया है. इसे ऐप्लिकेशन लोड होने पर लोड किया जाएगा, कैश मेमोरी में सेव किया जाएगा, और ट्रांज़िशन के लिए सेट अप किया जाएगा.

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

अब JavaScript पर एक नज़र डालते हैं. आसानी के लिए, मैं कोड में किसी भी हेल्पर या ऑप्टिमाइज़ेशन को शामिल नहीं कर रहा/रही हूं. यहां हम सिर्फ़ डीओएम नोड के तय किए गए ऐरे में लूप कर रहे हैं, ताकि फ़ेच और कैश करने के लिए लिंक मिल सकें. ध्यान दें—इस डेमो के लिए, पेज लोड होने पर इस तरीके fetchAndCache() को कॉल किया जा रहा है. जब हमें नेटवर्क कनेक्शन का पता चलता है और हमें यह तय करना होता है कि इसे कब कॉल किया जाना चाहिए, तब हम अगले सेक्शन में इसे फिर से बनाते हैं.

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” ऑब्जेक्ट का इस्तेमाल करके, एसिंक्रोनस पोस्ट-प्रोसेसिंग को सही तरीके से पूरा करते हैं. Working Off the Grid with HTML5 Offline में, AJAX कॉल में localStorage का इस्तेमाल करने के बारे में ज़्यादा जानकारी दी गई है. इस उदाहरण में, हर अनुरोध पर कैश मेमोरी का इस्तेमाल करने और सर्वर से 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;
      }
    }
  }
}

माफ़ करें, localStorage में कैरेक्टर एन्कोडिंग के लिए UTF-16 का इस्तेमाल किया जाता है. इसलिए, हर बाइट को 2 बाइट के तौर पर सेव किया जाता है. इससे स्टोरेज की सीमा 5 एमबी से घटकर कुल 2.6 एमबी हो जाती है. इन पेजों/मार्कअप को ऐप्लिकेशन की कैश मेमोरी के स्कोप से बाहर फ़ेच और कैश करने की पूरी वजह, अगले सेक्शन में बताई गई है.

HTML5 के साथ iframe एलिमेंट में हाल ही में हुए सुधारों की वजह से, अब हमारे पास AJAX कॉल से वापस मिले responseText को पार्स करने का एक आसान और असरदार तरीका है. 3,000 लाइन वाले कई JavaScript पार्सर और रेगुलर एक्सप्रेशन मौजूद हैं. ये स्क्रिप्ट टैग वगैरह को हटाते हैं. हालांकि, ब्राउज़र को वह काम क्यों न करने दें जो वह सबसे अच्छी तरह से करता है? इस उदाहरण में, हम responseText को कुछ समय के लिए छिपे हुए iframe में लिखेंगे. हम HTML5 के “सैंडबॉक्स” एट्रिब्यूट का इस्तेमाल कर रहे हैं. इससे स्क्रिप्ट बंद हो जाती हैं और सुरक्षा से जुड़ी कई सुविधाएं मिलती हैं…

स्पेसिफ़िकेशन के मुताबिक: सैंडबॉक्स एट्रिब्यूट को सेट करने पर, iframe में होस्ट किए गए किसी भी कॉन्टेंट पर अतिरिक्त पाबंदियां लागू हो जाती हैं. इसकी वैल्यू, स्पेस से अलग किए गए ऐसे टोकन का अनऑर्डर किया गया सेट होना चाहिए जो यूनीक हों. साथ ही, ये ASCII केस-इनसेंसिटिव होने चाहिए. इन वैल्यू का इस्तेमाल किया जा सकता है: allow-forms, allow-same-origin, allow-scripts, और allow-top-navigation. इस एट्रिब्यूट को सेट करने पर, कॉन्टेंट को यूनीक ओरिजिन से माना जाता है. साथ ही, फ़ॉर्म और स्क्रिप्ट बंद हो जाती हैं, लिंक को अन्य ब्राउज़िंग कॉन्टेक्स्ट को टारगेट करने से रोका जाता है, और प्लगिन बंद हो जाते हैं.

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, किसी नोड को एक दस्तावेज़ से दूसरे दस्तावेज़ में ले जाने के अनुरोध को सही तरीके से अस्वीकार करता है. अगर नया चाइल्ड नोड किसी दूसरे दस्तावेज़ में बनाया गया है, तो गड़बड़ी का मैसेज दिखता है. इसलिए, यहां हम adoptNode का इस्तेमाल करते हैं और सब ठीक है.

तो iframe का इस्तेमाल क्यों करें? सिर्फ़ innerHTML का इस्तेमाल क्यों नहीं किया जाता? innerHTML अब HTML5 स्पेसिफ़िकेशन का हिस्सा है. हालांकि, सर्वर (बुरा या अच्छा) से मिले जवाब को बिना जांच किए किसी भी जगह पर डालना खतरनाक हो सकता है. इस लेख को लिखते समय, मुझे कोई ऐसा व्यक्ति नहीं मिला जो innerHTML के अलावा किसी और चीज़ का इस्तेमाल कर रहा हो. मुझे पता है कि JQuery इसका इस्तेमाल अपने कोर में करता है. साथ ही, अपवाद होने पर ही append फ़ॉलबैक का इस्तेमाल करता है. JQuery Mobile भी इसका इस्तेमाल करता है. हालांकि, मैंने innerHTML “stops working randomly” के बारे में कोई खास टेस्टिंग नहीं की है. हालांकि, यह देखना बहुत दिलचस्प होगा कि यह समस्या किन-किन प्लैटफ़ॉर्म पर होती है. यह देखना भी दिलचस्प होगा कि कौनसी रणनीति ज़्यादा बेहतर परफ़ॉर्म करती है… मैंने इस बारे में दोनों पक्षों से दावे सुने हैं.

नेटवर्क टाइप का पता लगाना, उसे मैनेज करना, और उसकी प्रोफ़ाइलिंग करना

अब हमारे पास वेब ऐप्लिकेशन को बफ़र (या अनुमानित कैश मेमोरी) करने की सुविधा है. इसलिए, हमें कनेक्शन का पता लगाने वाली सही सुविधाएं देनी होंगी, ताकि हमारा ऐप्लिकेशन ज़्यादा स्मार्ट बन सके. मोबाइल ऐप्लिकेशन डेवलपमेंट में, ऑनलाइन/ऑफ़लाइन मोड और कनेक्शन की स्पीड काफ़ी अहम होती है. The Network Information API डालें. जब भी मैं किसी प्रज़ेंटेशन में इस सुविधा के बारे में बताता हूं, तो ऑडियंस में से कोई न कोई व्यक्ति हाथ उठाकर पूछता है कि “मैं इसका इस्तेमाल किस काम के लिए करूंगा?”. इसलिए, यहां एक बेहद स्मार्ट मोबाइल वेब ऐप्लिकेशन सेट अप करने का तरीका बताया गया है.

सबसे पहले, सामान्य ज्ञान के हिसाब से एक बोरिंग उदाहरण… तेज़ रफ़्तार से चलने वाली ट्रेन में मोबाइल डिवाइस से वेब का इस्तेमाल करते समय, नेटवर्क कई बार बंद हो सकता है.साथ ही, अलग-अलग जगहों पर ट्रांसमिशन की स्पीड अलग-अलग हो सकती है. उदाहरण के लिए, कुछ शहरी इलाकों में HSPA या 3G उपलब्ध हो सकता है. हालांकि, दूर-दराज के इलाकों में 2G टेक्नोलॉजी का इस्तेमाल किया जा सकता है, जिसकी स्पीड बहुत कम होती है). यहां दिया गया कोड, कनेक्शन से जुड़ी ज़्यादातर स्थितियों के बारे में बताता है.

नीचे दिए गए कोड से यह जानकारी मिलती है:

  • applicationCache के ज़रिए ऑफ़लाइन ऐक्सेस.
  • यह कुकी पता लगाती है कि वीडियो को बुकमार्क किया गया है या नहीं और उसे ऑफ़लाइन देखा जा सकता है या नहीं.
  • यह कुकी, ऑफ़लाइन से ऑनलाइन और ऑनलाइन से ऑफ़लाइन पर स्विच करने का पता लगाती है.
  • यह कुकी, धीमी स्पीड वाले कनेक्शन का पता लगाती है और नेटवर्क टाइप के आधार पर कॉन्टेंट फ़ेच करती है.

इन सभी सुविधाओं के लिए, बहुत कम कोड की ज़रूरत होती है. सबसे पहले, हम अपने इवेंट और लोडिंग के उदाहरणों का पता लगाते हैं:

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

ऊपर दिए गए EventListeners में, हमें अपने कोड को यह बताना होगा कि इसे किसी इवेंट से कॉल किया जा रहा है या पेज के लिए असल अनुरोध या रीफ़्रेश से. इसकी मुख्य वजह यह है कि ऑनलाइन और ऑफ़लाइन मोड के बीच स्विच करने पर, बॉडी onload इवेंट ट्रिगर नहीं होगा.

इसके बाद, हमारे पास ononline या onload इवेंट के लिए एक सामान्य जांच है. यह कोड, ऑफ़लाइन से ऑनलाइन पर स्विच करने पर बंद किए गए लिंक को रीसेट करता है. हालांकि, अगर यह ऐप्लिकेशन ज़्यादा बेहतर होता, तो ऐसा लॉजिक डाला जा सकता था जो कॉन्टेंट फ़ेच करने की प्रोसेस को फिर से शुरू कर दे या कुछ समय के लिए कनेक्शन बंद होने पर यूज़र एक्सपीरियंस को मैनेज कर सके.

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

processOffline() के लिए भी यही नियम लागू होता है. यहां आपको ऑफ़लाइन मोड के लिए अपने ऐप्लिकेशन में बदलाव करना होगा. साथ ही, पर्दे के पीछे हो रहे किसी भी लेन-देन को वापस लाने की कोशिश करनी होगी. नीचे दिया गया कोड, हमारे सभी बाहरी लिंक को ढूंढकर उन्हें बंद कर देता है. इससे उपयोगकर्ता हमेशा के लिए हमारे ऑफ़लाइन ऐप्लिकेशन में फंस जाते हैं. मुहाहाहा!

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

    }
  }
}

ठीक है, अब हम कुछ ज़रूरी बातें करेंगे. अब हमारे ऐप्लिकेशन को पता है कि वह किस स्थिति में कनेक्ट है. इसलिए, जब ऐप्लिकेशन ऑनलाइन होता है, तब हम कनेक्शन के टाइप की जांच भी कर सकते हैं और उसके हिसाब से बदलाव कर सकते हैं. मैंने हर कनेक्शन के लिए, टिप्पणियों में उत्तरी अमेरिका में इंटरनेट सेवा देने वाली कंपनियों के डाउनलोड और लेटेंसी की जानकारी दी है.

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 प्रोसेस में कई तरह के बदलाव किए जा सकते हैं. हालांकि, यहां मैंने सिर्फ़ यह बताया है कि किसी कनेक्शन के लिए, संसाधनों को एसिंक्रोनस (true) या सिंक्रोनस (false) तरीके से फ़ेच किया जाए.

Edge (सिंक्रोनस) अनुरोध की टाइमलाइन

Edge Sync

वाई-फ़ाई (एसिंक्रोनस) अनुरोध की टाइमलाइन

WIFI Async

इससे, इंटरनेट की रफ़्तार के हिसाब से उपयोगकर्ता अनुभव को बेहतर बनाने के लिए कम से कम कुछ तरीके इस्तेमाल किए जा सकते हैं. यह समस्या का पूरी तरह से समाधान नहीं है. एक और काम यह होगा कि जब कोई लिंक क्लिक किया जाए, तो (धीमे कनेक्शन पर) एक लोडिंग मोडल दिखाया जाए. ऐसा तब किया जाता है, जब ऐप्लिकेशन अब भी बैकग्राउंड में उस लिंक का पेज फ़ेच कर रहा हो. यहाँ सबसे अहम बात यह है कि HTML5 की नई और बेहतरीन सुविधाओं का इस्तेमाल करते समय, लेटेंसी को कम किया जाए. नेटवर्क का पता लगाने से जुड़ा डेमो यहां देखें.

नतीजा

मोबाइल एचटीएमएल5 ऐप्लिकेशन के विकास की शुरुआत अभी हुई है. अब आपको सिर्फ़ HTML5 और उससे जुड़ी टेक्नोलॉजी पर आधारित मोबाइल “फ़्रेमवर्क” की बुनियादी जानकारी मिल गई है. हमें लगता है कि डेवलपर को इन सुविधाओं के साथ काम करना चाहिए और इन्हें अपने ऐप्लिकेशन में शामिल करना चाहिए. साथ ही, इन्हें रैपर के ज़रिए छिपाना नहीं चाहिए.