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

वेस्ली हेल्स
वेसली हेल्स

शुरुआती जानकारी

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

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

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

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

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

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

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

उपयोगकर्ता इंटरैक्शन को आसान और नेटिव बनाने के लिए, हमें ब्राउज़र को अपने हिसाब से बनाना होगा. आम तौर पर, हम चाहते हैं कि मोबाइल डिवाइस का सीपीयू, शुरुआती ऐनिमेशन को सेट अप करे. इसके बाद, ऐनिमेशन की प्रोसेस के दौरान सिर्फ़ अलग-अलग लेयर कंपोज़ करने की ज़िम्मेदारी जीपीयू पर होगी. ये काम में Translate3d, dynamic3d, और TranslationZ कहते हैं — ये ऐनिमेशन वाले एलिमेंट को अपनी खुद की लेयर बनाते हैं. इनकी मदद से, डिवाइस हर चीज़ को आसानी से एक साथ रेंडर कर पाता है. एक्सेलरेटेड कंपोज़िटिंग और WebKit के काम करने के तरीके के बारे में अधिक जानने के लिए, आरिया हिदायत ने अपने ब्लॉग पर बहुत अच्छी जानकारी दी है.

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

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

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

सबसे पहले, हम स्लाइड, फ़्लिप, और ट्रांज़िशन के हिसाब से, ट्रांज़िशन के बारे में बात करेंगे. साथ ही, हम समझते हैं कि उन्हें किस तरह तेज़ बनाना है. ध्यान दें कि हर ऐनिमेशन कैसे सीएसएस और 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);
}

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

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

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

  • $> एक्सपोर्ट CA_COLOR_OPAQUE=1
  • $> CA_LOG_MEMORY_USAGE=1 एक्सपोर्ट करें
  • $> /Applications/Safari.app/Contents/MacOS/Safari

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

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

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

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

Chrome FPS

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

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

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

Chrome के लिए इससे मिलती-जुलती सेटिंग about:flags “कंपोज़िट रेंडर लेयर बॉर्डर” में भी उपलब्ध है.

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

विपरीत पत्तियां

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

कोरेनिमेशन 1

जब हम ब्राउज़र विंडो का साइज़ बड़ा करते हैं या उसे बड़ा करते हैं, तो हम मेमोरी भी बढ़ाते हैं.

कोरेनिमेशन 2

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

पर्दे के पीछे की गतिविधियां: फ़ेच और कैश करना

अब समय आ गया है कि हम अपने पेज और रिसॉर्स को कैश मेमोरी में सेव करें. जिस तरह JQuery Mobile और इसके जैसे फ़्रेमवर्क इस्तेमाल होते हैं उसी तरह हम अपने पेजों को भी मौजूदा AJAX कॉल की मदद से प्री-फ़ेच और कैश करेंगे.

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

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

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

iPhone होम

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

जैसा कि देखा जा सकता है, हम यहां सिमैंटिक मार्कअप का इस्तेमाल कर रहे हैं. किसी दूसरे पेज का लिंक. चाइल्ड पेज उसी नोड/क्लास स्ट्रक्चर का पालन करता है, जिसका पालन उसका पैरंट पेज है. हम इसे एक कदम आगे ले जा सकते हैं और “पेज” नोड वगैरह के लिए डेटा-* एट्रिब्यूट का इस्तेमाल कर सकते हैं. यहां एक अलग एचटीएमएल फ़ाइल (/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 के बारे में जानते हैं. आसानी के लिए, मैं सभी हेल्पर या ऑप्टिमाइज़ेशन को कोड से बाहर छोड़ रहा हूं. हम बस इतना ही कर रहे हैं कि हम DOM नोड के एक तय कलेक्शन से होकर, लिंक फ़ेच करने और कैश मेमोरी में सेव करने की कोशिश करें. ध्यान दें—इस डेमो के लिए, यह 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” ऑब्जेक्ट का इस्तेमाल करके, सही एसिंक्रोनस पोस्ट-प्रोसेसिंग पक्का करते हैं. HTML5 ऑफ़लाइन के साथ ग्रिड से अलग काम करना में, 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 का इस्तेमाल करता है, इसलिए हर एक बाइट को दो बाइट में स्टोर किया जाता है, जिसकी वजह से हमारी स्टोरेज सीमा 5 एमबी से कुल 2.6 एमबी हो जाती है. ऐप्लिकेशन कैश सीमा के बाहर इन पेजों/मार्कअप को फ़ेच और कैश मेमोरी में सेव करने की पूरी वजह, अगले सेक्शन में बताई गई है.

HTML5 के साथ iframe एलिमेंट में हाल ही में हुए बदलावों के साथ, अब हमारे पास responseText को पार्स करने का एक आसान और असरदार तरीका है. यह तरीका हम अपनी AJAX कॉल से वापस लाते हैं. काफ़ी संख्या में 3000-लाइन वाले 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 अपने मूल रूप में इसका इस्तेमाल अपवाद के तौर पर एक एपेंड फ़ॉलबैक के साथ करता है. साथ ही, JQuery Mobile भी इसका इस्तेमाल करता है. हालांकि, मैंने inerHTML के “बिना किसी क्रम के काम करना बंद करता है” से जुड़े कोई बड़े पैमाने पर टेस्ट नहीं किया है, लेकिन जिन प्लैटफ़ॉर्म पर इसका असर पड़ता है उन्हें देखना बहुत दिलचस्प होगा. यह देखना भी दिलचस्प होगा कि कौनसा तरीका ज़्यादा परफ़ॉर्म करने वाला है... मुझे दोनों ओर से इस बारे में दावे भी सुनने को मिले हैं.

नेटवर्क टाइप की पहचान, हैंडलिंग, और प्रोफ़ाइलिंग

अब हमारे पास अपने वेब ऐप्लिकेशन को बफ़र (या अनुमान लगाने वाली कैश मेमोरी) की सुविधा है, इसलिए हमें कनेक्शन का पता लगाने की ऐसी सही सुविधाएं देनी होंगी जो हमारे ऐप्लिकेशन को बेहतर बनाती हैं. यहां मोबाइल ऐप्लिकेशन डेवलप करना, ऑनलाइन/ऑफ़लाइन मोड और कनेक्शन की स्पीड को लेकर बेहद संवेदनशील हो जाता है. 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 इवेंट से जुड़ी जानकारी देंगे. यह कोड, ऑफ़लाइन से ऑनलाइन पर स्विच करने पर बंद किए गए लिंक को रीसेट कर देता है. हालांकि, अगर यह ऐप्लिकेशन बेहतर तरीके से काम करता है, तो ऐसा लॉजिक शामिल किया जा सकता है जिससे कॉन्टेंट को फ़ेच करना फिर से शुरू हो जाए. इसके अलावा, बार-बार एक ही तरह के कनेक्शन के लिए UX को हैंडल किया जा सकता है.

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

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

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

Edge सिंक करें

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

वाई-फ़ाई एक साथ काम नहीं करने वाली प्रोसेस

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

नतीजा

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