परिचय
आज के मोबाइल वेब एनवायरमेंट में, पेज के बार-बार रीफ़्रेश होने, पेज के ट्रांज़िशन में रुकावट आने, और टैप इवेंट में समय-समय पर होने वाली देरी जैसी समस्याएं आम हैं. डेवलपर, नेटिव के ज़्यादा से ज़्यादा करीब पहुंचने की कोशिश कर रहे हैं. हालांकि, अक्सर हैक, रीसेट, और सख्त फ़्रेमवर्क की वजह से उन्हें परेशानी होती है.
इस लेख में, हम मोबाइल के लिए एचटीएमएल5 वेब ऐप्लिकेशन बनाने के लिए ज़रूरी चीज़ों के बारे में बताएंगे. इसका मुख्य मकसद, उन मुश्किलों को सामने लाना है जिन्हें आज के मोबाइल फ़्रेमवर्क छिपाने की कोशिश करते हैं. आपको कम से कम एलिमेंट वाला तरीका (कोर HTML5 एपीआई का इस्तेमाल करके) और बुनियादी बातें दिखेंगी. इनकी मदद से, अपना फ़्रेमवर्क लिखा जा सकता है या उस फ़्रेमवर्क में योगदान दिया जा सकता है जिसका इस्तेमाल फ़िलहाल किया जा रहा है.
हार्डवेयर से तेज़ी लाने की सुविधा
आम तौर पर, जीपीयू ज़्यादा जानकारी वाली 3D मॉडलिंग या सीएडी डायग्राम को हैंडल करते हैं. हालांकि, इस मामले में, हम चाहते हैं कि जीपीयू की मदद से, हमारी प्राइमटिव ड्रॉइंग (डिव, बैकग्राउंड, ड्रॉप शैडो वाला टेक्स्ट, इमेज वगैरह) आसानी से दिखें और ऐनिमेट हों. माफ़ करें, ज़्यादातर फ़्रंट-एंड डेवलपर, इस ऐनिमेशन प्रोसेस को किसी तीसरे पक्ष के फ़्रेमवर्क पर छोड़ रहे हैं. वे सेमेंटेटिक्स की चिंता नहीं कर रहे हैं. क्या इन मुख्य सीएसएस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>
ध्यान दें कि हमने पेजों को बाईं या दाईं ओर स्टैज करने का यह कॉन्सेप्ट कैसे बनाया है. यह किसी भी दिशा में हो सकता है, लेकिन यह सबसे आम है.
अब हमारे पास CSS की कुछ पंक्तियों के साथ एनिमेशन और हार्डवेयर त्वरण उपलब्ध है. असली ऐनिमेशन तब होता है, जब हम पेज के 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-based) डिवाइसों पर इस इवेंट को हैंडल करने के लिए, कुछ आसान 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;
}
हम पेज फ़्लिप के लिए, सीएसएस3 के ईज़-आउट ट्रांज़िशन का इस्तेमाल कर रहे हैं. इसलिए, सामान्य 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';
}
मैंने ट्रांज़िशन को सबसे अच्छा नेटिव फ़ील देने के लिए, cubic-bezier का इस्तेमाल करने की कोशिश की, लेकिन ease-out का इस्तेमाल करने से ही काम हो गया.
आखिर में, नेविगेशन करने के लिए, हमें पहले से तय किए गए उन 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 है. इसलिए, आपके ओएस के हिसाब से निर्देश अलग हो सकते हैं. Terminal खोलें और यह टाइप करें:
- $> export 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) की जानकारी देख सकें:
- Google Chrome वेब ब्राउज़र खोलें.
- यूआरएल बार में, about:flags टाइप करें.
- कुछ आइटम नीचे की ओर स्क्रोल करें और FPS काउंटर के लिए “चालू करें” पर क्लिक करें.
अगर Chrome के बेहतर वर्शन में यह पेज देखा जाता है, तो आपको सबसे ऊपर बाएं कोने में लाल रंग का एफ़पीएस काउंटर दिखेगा.
इससे हमें यह पता चलता है कि हार्डवेयर से तेज़ी लाने की सुविधा चालू है. इससे हमें यह भी पता चलता है कि ऐनिमेशन कैसे चलता है और क्या आपके पास कोई लीक है (ऐनिमेशन लगातार चल रहे हैं, जिन्हें रोकना चाहिए).
हार्डवेयर त्वरण को असल में विज़ुअलाइज़ करने का एक और तरीका यह है कि आप Safari में वही पेज खोलें (ऐसे एनवायरमेंट वैरिएबल जिनके बारे में मैंने ऊपर बताया है). त्वरित किए गए हर DOM एलिमेंट में लाल रंग का टिनट होता है. इससे हमें पता चलता है कि लेयर के हिसाब से क्या कंपोज़िट किया जा रहा है. ध्यान दें कि सफ़ेद नेविगेशन लाल नहीं है, क्योंकि इसे तेज़ नहीं किया गया है.
Chrome के लिए भी ऐसी ही सेटिंग, about:flags “कंपोज़िटेड रेंडर लेयर बॉर्डर” में उपलब्ध है.
कंपोज़िट लेयर को देखने का एक और बेहतरीन तरीका यह है कि इस बदलाव के लागू होने के दौरान, WebKit गिरती पत्तियों का डेमो देखें.
आखिर में, अपने ऐप्लिकेशन के ग्राफ़िक हार्डवेयर की परफ़ॉर्मेंस को सही से समझने के लिए, यह देखें कि मेमोरी का इस्तेमाल कैसे किया जा रहा है. यहां हम देख सकते हैं कि हम Mac OS पर CoreAnimation बफ़र में, ड्रॉइंग के 1.38 एमबी निर्देशों को डाल रहे हैं. Core Animation मेमोरी बफ़र को OpenGL ES और GPU के बीच शेयर किया जाता है, ताकि स्क्रीन पर दिखने वाले फ़ाइनल पिक्सल बनाए जा सकें.
ब्राउज़र विंडो का साइज़ बदलने या उसे बड़ा करने पर, हमें मेमोरी में भी बढ़ोतरी दिखती है.
इससे आपको यह अंदाज़ा मिल जाता है कि आपके मोबाइल डिवाइस की मेमोरी कैसे इस्तेमाल हो रही है. ऐसा तभी होगा, जब ब्राउज़र का साइज़ सही डाइमेंशन के हिसाब से बदला जा रहा हो. अगर iPhone एनवायरमेंट के लिए डीबगिंग या टेस्टिंग की जा रही थी, तो इमेज का साइज़ 480 x 320 पिक्सल पर सेट करें. अब हमें यह पता है कि हार्डवेयर एक्सेलरेशन कैसे काम करता है और इसे डीबग करने में क्या लगता है. इस बारे में पढ़ना एक अलग बात है, लेकिन जीपीयू मेमोरी बफ़र को विज़ुअल तौर पर काम करते हुए देखने से, हमें चीज़ों को बेहतर तरीके से समझने में मदद मिलती है.
पर्दे के पीछे की गतिविधियां: फ़ेच करना और कैश मेमोरी में सेव करना
अब समय आ गया है कि हम अपने पेज और संसाधनों को कैश मेमोरी में सेव करने की सुविधा को अगले लेवल पर ले जाएं. JQuery Mobile और मिलते-जुलते फ़्रेमवर्क के तरीके की तरह ही, हम एक साथ कई AJAX कॉल की मदद से अपने पेजों को पहले से फ़ेच और कैश मेमोरी में सेव करने जा रहे हैं.
आइए, मोबाइल वेब से जुड़ी कुछ मुख्य समस्याओं को हल करते हैं और ऐसा करने की वजहें बताते हैं:
- फ़ेच करना: हमारे पेजों को पहले से फ़ेच करने से, उपयोगकर्ता ऐप्लिकेशन को ऑफ़लाइन इस्तेमाल कर सकते हैं. साथ ही, नेविगेशन ऐक्शन के बीच इंतज़ार नहीं करना पड़ता. बेशक, हम डिवाइस के ऑनलाइन होने पर उसके बैंडविड्थ को रोकना नहीं चाहते, इसलिए हमें इस सुविधा का सीमित इस्तेमाल ही करना चाहिए.
- कैश मेमोरी में सेव करना: इसके बाद, हमें इन पेजों को फ़ेच और कैश मेमोरी में सेव करते समय, एक साथ या अलग-अलग तरीके से काम करने वाला तरीका चाहिए. हमें localStorage का भी इस्तेमाल करना होगा, क्योंकि यह सभी डिवाइसों पर काम करता है. हालांकि, यह असाइनोक्रोनस नहीं है.
- AJAX और जवाब को पार्स करना: AJAX के जवाब को DOM में डालने के लिए, innerHTML() का इस्तेमाल करना खतरनाक है (और भरोसेमंद नहीं?). इसके बजाय, हम AJAX रिस्पॉन्स डालने और एक साथ कई कॉल मैनेज करने के लिए, भरोसेमंद तरीके का इस्तेमाल करते हैं. हम
xhr.responseText
को पार्स करने के लिए, HTML5 की कुछ नई सुविधाओं का भी इस्तेमाल करते हैं.
स्लाइड, फ़्लिप, और घुमाने की सुविधा के डेमो में दिए गए कोड का इस्तेमाल करके, हम कुछ सेकंडरी पेज जोड़ते हैं और उन्हें लिंक करते हैं. इसके बाद, हम लिंक को पार्स करेंगे और ट्रांज़िशन बनाएंगे.
फ़ेच और कैश मेमोरी का डेमो यहां देखें.
जैसा कि आपको दिख रहा है, हम यहां सिमैंटिक मार्कअप का इस्तेमाल कर रहे हैं. किसी दूसरे पेज का लिंक. चाइल्ड पेज, उसी नोड/क्लास स्ट्रक्चर का पालन करता है जो उसका पैरंट है. हम इसे एक कदम आगे ले जा सकते हैं और “पेज” नोड वगैरह के लिए 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') &&
//'#' 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) &&
//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” ऑब्जेक्ट का इस्तेमाल करके, पोस्ट-प्रोसेसिंग को असाइनोक्रोनस तरीके से सही तरीके से पूरा करने की सुविधा देते हैं. एचटीएमएल5 के ऑफ़लाइन वर्शन का इस्तेमाल करके, इंटरनेट के बिना काम करना लेख में, 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 एलिमेंट में हाल ही में हुए बदलावों को ध्यान में रखते हुए, अब हमारे पास एक आसान और असरदार तरीका है, जिससे हम अपनी AJAX कॉल से वापस आने वाले responseText
को पार्स कर सकते हैं. 3,000 लाइन वाले कई JavaScript पार्सर्स और रेगुलर एक्सप्रेशन हैं, जो स्क्रिप्ट टैग वगैरह हटाते हैं. लेकिन ब्राउज़र को वह काम क्यों न करने दिया जाए जिसमें वह सबसे अच्छा है? इस उदाहरण में, हम responseText
को कुछ समय के लिए छिपे हुए iframe में लिखेंगे. हम एचटीएमएल5 “सैंडबॉक्स” एट्रिब्यूट का इस्तेमाल कर रहे हैं. यह एट्रिब्यूट, स्क्रिप्ट को बंद कर देता है और सुरक्षा से जुड़ी कई सुविधाएं देता है…
स्पेसिफ़िकेशन के मुताबिक: सैंडबॉक्स एट्रिब्यूट की वैल्यू सबमिट करने पर, iframe से होस्ट किए गए किसी भी कॉन्टेंट पर अतिरिक्त पाबंदियां लागू हो जाती हैं. इसकी वैल्यू, स्पेस लगाकर अलग किए गए ऐसे यूनीक टोकन का बिना क्रम वाला सेट होनी चाहिए जो ASCII केस-इनसेंसिटिव होते हैं. इन वैल्यू के लिए, अनुमति देने वाले फ़ॉर्म, एक ही ऑरिजिन को अनुमति दें, स्क्रिप्ट को अनुमति दें, और टॉप-नेविगेशन की अनुमति दें. इस एट्रिब्यूट को सेट करने पर, यह माना जाता है कि कॉन्टेंट किसी यूनीक ऑरिजिन से है, फ़ॉर्म और स्क्रिप्ट बंद हैं, लिंक अन्य ब्राउज़िंग कॉन्टेक्स्ट को टारगेट करने से रोक दिए जाते हैं, और प्लगिन बंद हो जाते हैं.
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 भी इसका इस्तेमाल करता है. हालांकि, मैंने innerHTML “काम करना बंद कर देता है” के बारे में कोई ज़्यादा टेस्टिंग नहीं की है. हालांकि, यह देखना बहुत दिलचस्प होगा कि इसका असर किन प्लैटफ़ॉर्म पर पड़ता है. यह देखना भी दिलचस्प होगा कि कौनसा तरीका ज़्यादा बेहतर है... इस बारे में भी मुझे दोनों तरफ़ से दावे मिले हैं.
नेटवर्क टाइप का पता लगाना, उसे मैनेज करना, और उसकी प्रोफ़ाइल बनाना
अब हमारे पास अपने वेब ऐप्लिकेशन को बफ़र करने (या अनुमानित कैश मेमोरी बनाने) की सुविधा है. इसलिए, हमें कनेक्शन का पता लगाने की सही सुविधाएं देनी होंगी, ताकि हमारा ऐप्लिकेशन स्मार्ट बन सके. यहीं पर मोबाइल ऐप्लिकेशन डेवलपमेंट, ऑनलाइन/ऑफ़लाइन मोड और कनेक्शन की स्पीड के लिए बेहद संवेदनशील हो जाता है. 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 प्रोसेस में कई तरह के बदलाव कर सकते हैं. हालांकि, मैंने यहां सिर्फ़ यह बताया है कि किसी कनेक्शन के लिए, संसाधनों को असाइनोक्रोनस (सही) या सिंक्रोनस (गलत) तरीके से फ़ेच किया जाए.
Edge (सिंक्रोनस) अनुरोध की टाइमलाइन
वाई-फ़ाई (एसिंक्रोनस) रिक्वेस्ट टाइमलाइन
इससे, धीमे या तेज़ कनेक्शन के आधार पर, उपयोगकर्ता अनुभव में बदलाव करने के कम से कम कुछ तरीके उपलब्ध होते हैं. हालांकि, यह समस्या का पूरा समाधान नहीं है. एक और काम यह है कि किसी लिंक पर क्लिक करने पर (धीमे कनेक्शन पर), लोडिंग मोडल दिखाया जाए. ऐसा तब किया जा सकता है, जब ऐप्लिकेशन बैकग्राउंड में उस लिंक के पेज को फ़ेच कर रहा हो. यहां सबसे अहम बात यह है कि उपयोगकर्ता के कनेक्शन की सभी क्षमताओं का फ़ायदा उठाते हुए, इंतज़ार का समय कम किया जाए. इसके लिए, HTML5 के नए और बेहतर वर्शन का इस्तेमाल किया जा सकता है. नेटवर्क की पहचान करने से जुड़ा डेमो यहां देखें.
नतीजा
मोबाइल के लिए HTML5 ऐप्लिकेशन बनाने की शुरुआत अभी-अभी हुई है. अब आपको मोबाइल “फ़्रेमवर्क” की बहुत ही आसान और बुनियादी जानकारी दिख रही है. यह फ़्रेमवर्क, सिर्फ़ HTML5 और उससे जुड़ी टेक्नोलॉजी के आधार पर बनाया गया है. मुझे लगता है कि डेवलपर को इन सुविधाओं को ठीक से समझना और उन पर काम करना चाहिए. उन्हें इन सुविधाओं को किसी कवर के तौर पर इस्तेमाल नहीं करना चाहिए.