مقدمة
تُعد عمليات إعادة التحميل الدوّارة، وعمليات الانتقال المتقطعة في الصفحات، والتأخيرات الدورية في أحداث النقر، بعض المشاكل التي تواجه بيئات الويب على الأجهزة الجوّالة في الوقت الحالي. يحاول المطوّرون الاقتراب من التطبيقات الأصلية قدر الإمكان، ولكن غالبًا ما يتم إيقافهم عن ذلك بسبب عمليات الاختراق وعمليات إعادة الضبط والأُطر الصارمة.
في هذه المقالة، سنناقش الحدّ الأدنى من المتطلبات اللازمة لإنشاء تطبيق ويب HTML5 للأجهزة الجوّالة. تتمثل النقطة الرئيسية في الكشف عن التعقيدات الخفية التي تحاول إطارات العمل للأجهزة الجوّالة حاليًا إخفاءها. ستلاحظ نهجًا بسيطًا (باستخدام واجهات برمجة تطبيقات HTML5 الأساسية) ومبادئ أساسية ستمكّنك من كتابة إطار عملك الخاص أو المساهمة في الإطار الذي تستخدمه حاليًا.
تسريع الأجهزة
في العادة، تعالج وحدات معالجة الرسومات النماذج الثلاثية الأبعاد التفصيلية أو المخططات البيانية لتصميمات CAD، ولكن في هذه الحالة، نريد أن تظهر رسوماتنا الأساسية (الأقسام الخلفيات والنصوص التي تحتوي على ظلال متساقطة والصور وما إلى ذلك) بشكل سلس ومتحرك بسلاسة من خلال وحدة معالجة الرسومات. من المؤسف أن معظم مطوِّري الواجهة الأمامية يستخدمون عملية إنشاء الصور المتحركة هذه في إطار عمل تابع لجهة خارجية بدون القلق بشأن دلالات الألفاظ، ولكن هل يجب إخفاء ميزات CSS3 الأساسية هذه؟ اسمحوا لي أن أقدم لكم بعض الأسباب التي تجعل الاهتمام بهذه الأمور مهمًا:
تخصيص الذاكرة والأعباء الحاسوبية — إذا حاولت إنشاء كل عنصر في نموذج العناصر في المستند (DOM) فقط من أجل تسريع الأجهزة، فقد يطاردك الشخص التالي الذي يعمل على رمزك البرمجي ويضربك بشدّة.
استهلاك الطاقة: من الواضح أنّه عندما يتم تشغيل الأجهزة، يتم أيضًا تشغيل البطارية. عند التطوير للأجهزة المحمولة، يتعين على المطورين أخذ مجموعة واسعة من قيود الأجهزة في الاعتبار أثناء كتابة تطبيقات الويب للأجهزة المحمولة. وسيكون هذا أكثر انتشارًا حيث يبدأ مصممو المتصفحات في إتاحة الوصول إلى المزيد والمزيد من مكونات الأجهزة.
التعارضات — واجهت مشكلة في السلوك عند تطبيق تسريع الأجهزة على أجزاء من الصفحة تم تسريعها بالفعل. لذلك، من الأهمية بمكان معرفة ما إذا كان لديك تسارع متداخل.
لكي يكون تفاعل المستخدم سلسًا وقريبًا من التفاعل الأصلي قدر الإمكان، يجب أن يعمل المتصفّح على مساعدتنا. من الأفضل أن تُجري وحدة المعالجة المركزية للجهاز الجوّال عملية إعداد الحركة الأولى، ثم تتولى وحدة معالجة الرسومات عملية دمج الطبقات المختلفة فقط أثناء عملية إنشاء الحركة. وهذا ما تفعله العناصر translate3d وscale3d وtranslateZ، إذ تمنح العناصر المتحركة طبقتها الخاصة، ما يسمح للجهاز بعرض كل العناصر معًا بسلاسة. لمعرفة المزيد عن ميزة "التركيب المُسرَّع" وطريقة عمل WebKit، يقدّم Ariya Hidayat الكثير من المعلومات المفيدة على مدوّنته.
انتقالات الصفحة
دعنا نلقي نظرة على ثلاثة من أكثر طرق تفاعل المستخدم شيوعًا عند تطوير تطبيق ويب للهاتف المحمول: تأثيرات الشرائح والقلب والتدوير.
يمكنك الاطّلاع على هذا الرمز البرمجي هنا http://slidfast.appspot.com/slide-flip-rotate.html (ملاحظة: تم تصميم هذا العرض التجريبي للأجهزة الجوّالة، لذا عليك تشغيل محاكي أو استخدام هاتفك أو جهازك اللوحي أو تصغير حجم نافذة المتصفّح إلى 1024 بكسل تقريبًا أو أقل).
أولاً، سنحلّل عمليات الانتقال إلى الشريحة والقلب والدوران وكيفية تسريعها. لاحظ أنّ كلّ صورة متحركة لا تتطلّب سوى ثلاثة أو أربعة أسطر من CSS و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 المخصّص للكمبيوتر المكتبي يسرع جميع العناصر التي تم تحويلها (بغض النظر عمّا إذا كانت ثنائية الأبعاد أو ثلاثية الأبعاد)، من المنطقي إنشاء طلب وسائط واستبعاد التسارع على هذا المستوى. تجدر الإشارة إلى أنّ حيل تسريع الأجهزة لا تحسّن السرعة في الإصدار 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;
}
}
انعكاس
على الأجهزة الجوّالة، يُعرف التقديم أو الإيقاف باسم التمرير السريع للصفحة. في ما يلي بعض تعليمات JavaScript البسيطة لمعالجة هذا الحدث على أجهزة iOS وAndroid (المستندة إلى WebKit).
يمكنك الاطّلاع على الميزة في العمل 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 درجة لإظهار الجانب العكسي من خلال النقر على خيار القائمة "جهات الاتصال". مرة أخرى، لا يستغرق ذلك سوى بضع سطور من CSS وبعض JavaScript لتحديد فئة انتقال onclick
.
ملاحظة: لا يتم عرض انتقال الدوران بشكل صحيح على معظم إصدارات Android لأنّه لا يتضمّن إمكانات تحويل CSS ثلاثية الأبعاد. بدلاً من تجاهل عملية التقديم أو الإيقاف، يُجري 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 مقدار الذاكرة التي نستخدمها عند إرسال عمليات الرسم إلى مساحة التخزين الاحتياطية. وهذا يوضح لك بالضبط مقدار الإجهاد الذي تبذله على الجهاز الجوّال، وربما يقدم تلميحات حول مدى استنزاف بطارية الجهاز المستهدف من خلال استخدام وحدة GPU.
لنشغّل الآن متصفّح Chrome لنرى بعض المعلومات المفيدة عن عدد اللقطات في الثانية:
- افتح متصفّح الويب Google Chrome.
- في شريط عنوان URL، اكتب about:flags.
- انتقِل للأسفل وصولاً إلى بعض العناصر وانقر على "تفعيل" (Enable) لعدّاد اللقطات في الثانية.
إذا اطّلعت على هذه الصفحة في الإصدار المحسَّن من Chrome، سيظهر لك عداد عدد اللقطات في الثانية باللون الأحمر في أعلى يمين الصفحة.
بهذه الطريقة، نعرف أن ميزة تسريع الأجهزة مفعَّلة. كما أنه يمنحنا فكرة حول كيفية تشغيل الرسوم المتحركة وما إذا كان لديك أي تسرب (رسوم متحركة قيد التشغيل مستمر يجب إيقافها).
هناك طريقة أخرى لعرض تسارع الأجهزة، وهي فتح الصفحة نفسها في Safari (مع متغيّرات البيئة التي ذكرناها أعلاه). يتميز كل عنصر DOM مُسرَّع بلون أحمر. يوضّح لنا هذا الإجراء بالضبط ما يتم دمجه حسب الطبقة. يُرجى ملاحظة أنّ شريط التنقّل الأبيض ليس أحمر لأنّه لم يتم تسريعه.
يتوفّر أيضًا إعداد مشابه لمتصفّح Chrome في about:flags "حدود طبقة العرض المجمّعة".
هناك طريقة أخرى رائعة للاطّلاع على الطبقات المركبة وهي مشاهدة العرض التوضيحي لأوراق الخريف في WebKit أثناء تطبيق هذا التعديل.
وأخيرًا، لفهم أداء أجهزة الرسومات في تطبيقنا، لنلقِ نظرة على كيفية استهلاك الذاكرة. يتضح لنا هنا أننا ننقل تعليمات الرسم بحجم 1.38 ميغابايت إلى الموارد الاحتياطية CoreAnimation على نظام التشغيل Mac OS. تتم مشاركة وحدات تخزين ذاكرة Core Animation بين OpenGL ES ووحدة معالجة الرسومات لإنشاء وحدات البكسل النهائية التي تظهر على الشاشة.
عند تغيير حجم نافذة المتصفّح أو تكبيرها، نلاحظ أيضًا توسيع الذاكرة.
وهذا يعطيك فكرة عن كيفية استهلاك الذاكرة على جهازك الجوال فقط إذا قمت بتغيير حجم المتصفح إلى الأبعاد الصحيحة. إذا كنت بصدد تصحيح الأخطاء أو الاختبار في بيئات iPhone، عليك تغيير الحجم إلى 480 × 320 بكسل. ندرك الآن تمامًا آلية عمل ميزة "تسريع الأجهزة" والخطوات المطلوبة لتصحيح الأخطاء. من الجيد الاطّلاع على المعلومات حول هذا الموضوع، ولكن من الأفضل رؤية ذاكرة وحدة معالجة الرسومات تعمل بشكل مرئي.
لقطات من وراء الكواليس: الجلب والتخزين المؤقت
حان الآن وقت الانتقال إلى المستوى التالي في ميزة تخزين الصفحات والموارد المؤقت. تمامًا مثل النهج الذي تستخدمه JQuery Mobile والأطر المتشابهة، سنستخدم طلبات AJAX المتزامنة لجلب صفحاتنا مسبقًا وتخزينها مؤقتًا.
لنطّلِع على بعض المشاكل الأساسية في الويب على الأجهزة الجوّالة والأسباب التي تدفعنا إلى معالجتها:
- الجلب: يتيح جلب صفحاتنا مسبقًا للمستخدمين استخدام التطبيق بلا اتصال بالإنترنت، كما يتيح عدم الانتظار بين إجراءات التنقّل. بالطبع، لا نريد أن نستهلك معدل نقل البيانات في الجهاز عندما يكون متصلاً بالإنترنت، لذا علينا استخدام هذه الميزة بشكل مقتصد.
- التخزين المؤقت: بعد ذلك، نريد نهجًا متزامنًا أو غير متزامن عند جلب هذه الصفحات وتخزينها مؤقتًا. نحتاج أيضًا إلى استخدام localStorage (لأنّه متاح على جميع الأجهزة) والذي لا يكون غير متزامن.
- AJAX وتحليل الردّ: إنّ استخدام innerHTML() لإدراج ردّ AJAX في DOM أمر خطير (وغير موثوق به؟). وبدلاً من ذلك، نستخدم آلية موثوقة لإدراج استجابة AJAX ومعالجة الطلبات المتزامنة. نستفيد أيضًا من بعض الميزات الجديدة في HTML5 لتحليل
xhr.responseText
.
استنادًا إلى الرمز البرمجي من العرض التجريبي لميزة "العرض الشرائحي" و"التدوير" و"القلب"، سنبدأ بإضافة بعض الصفحات الثانوية وربطها ببعضها. سنحلّل الروابط بعد ذلك وننشئ عمليات انتقال فورية.
يمكنك الاطّلاع على العرض التوضيحي لميزة "الاسترداد والتخزين المؤقت" هنا.
كما ترى، نحن نستفيد من الترميز الدلالي هنا. رابط يؤدي إلى صفحة أخرى تتبع الصفحة الفرعية نفس بنية العقدة/الفئة مثل الصفحة الرئيسية. يمكننا المضي قدمًا واستخدام سمة data-* لعُقد "الصفحة" وما إلى ذلك. وإليك صفحة التفاصيل (التابعة) في ملف html منفصل (/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') &&
//'#' 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. يتوفّر شرح أكثر تفصيلاً لاستخدام localStorage ضمن طلب AJAX في مقالة العمل بلا اتصال بالإنترنت باستخدام HTML5 بلا إنترنت. في هذا المثال، يمكنك الاطّلاع على الاستخدام الأساسي للتخزين المؤقت لكل طلب وتقديم العناصر المخزّنة مؤقتًا عندما يعرض الخادم أي استجابة غير استجابة ناجحة (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 ميغابايت إجماليًا. يوضح القسم التالي السبب الكامل لجلب هذه الصفحات أو الترميز وتخزينها مؤقتًا خارج نطاق ذاكرة التخزين المؤقت للتطبيق.
مع التطورات الأخيرة في عنصر iframe باستخدام HTML5، أصبح لدينا الآن طريقة بسيطة وفعّالة لتحليل responseText
الذي نحصل عليه من طلب AJAX. هناك الكثير من برامج تحليل JavaScript التي تتألف من 3,000 سطر والتعبيرات العادية التي تزيل علامات النصوص البرمجية وما إلى ذلك. ولكن لماذا لا نترك المتصفّح يُجري ما يُجيده؟ في هذا المثال، سنكتب responseText
في إطار iframe مخفي مؤقت. نحن نستخدم سمة "منطقة اختبار" في HTML5 التي تعمل على إيقاف النصوص البرمجية وتقدّم العديد من ميزات الأمان.
من المواصفات: تعمل سمة sandbox، عند تحديدها، على تفعيل مجموعة من القيود الإضافية على أي محتوى يستضيفه إطار 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 أيضًا. ومع ذلك، لم أجرِ أي اختبارات مكثفة بشأن innerHTML "توقّف عن العمل بشكل عشوائي"، ولكن سيكون من المثير للاهتمام معرفة جميع المنصات التي يؤثر فيها ذلك. سيكون من المثير للاهتمام أيضًا معرفة المنهج الذي يحقّق أداءً أفضل… لقد سمعتُ ادعاءات من كلا الجانبَين حول هذا الموضوع أيضًا.
رصد نوع الشبكة ومعالجته وإنشاء ملف تعريف له
والآن بعد أن أصبح بإمكاننا تخزين تطبيقات الويب لدينا مؤقتًا (أو في ذاكرة التخزين المؤقت التوقّعي)، علينا توفير ميزات رصد الاتصال المناسبة التي تجعل تطبيقنا أكثر ذكاءً. وفي هذه الحالة، يصبح تطوير التطبيقات المتوافقة مع الأجهزة الجوّالة حساسًا للغاية لسرعة الاتصال ووضعَي الاتصال بالإنترنت أو عدم الاتصال به. أدخِل Network Information API. في كل مرة أعرض فيها هذه الميزة في عرض تقديمي، يرفع أحد الأشخاص يده ويسأله "ما الذي يمكنني استخدام هذا من أجله؟". لذا، إليك طريقة ممكنة لإعداد تطبيق ويب ذكي للغاية للأجهزة الجوّالة.
سيناريو المنطق السليم أولًا... أثناء التفاعل مع الويب من جهاز جوّال على متن قطار عالي السرعة، قد تختفي الشبكة كثيرًا في لحظات مختلفة وقد تتوافق مناطق جغرافية مختلفة مع سرعات الإرسال المختلفة (على سبيل المثال، قد تتوفّر شبكة HSPA أو شبكة الجيل الثالث في بعض المناطق الحضرية، ولكن قد تتوفّر في المناطق البعيدة تقنيات شبكة الجيل الثاني التي تكون أبطأ بكثير). يتناول الرمز البرمجي التالي معظم سيناريوهات الاتصال.
يوفّر الرمز البرمجي التالي ما يلي:
- الوصول بلا إنترنت من خلال
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 (الطلبات المتزامنة)
المخطط الزمني لطلب شبكة Wi-Fi (غير متزامن)
يتيح ذلك إمكانية تعديل تجربة المستخدم استنادًا إلى سرعات الاتصال البطيئة أو السريعة. هذا الحلّ ليس حلاً تامًا. من المهام الأخرى التي يجب تنفيذها عرض نافذة منبثقة لتحميل المحتوى عند النقر على رابط (في حالات الاتصال البطيء) عندما لا يزال التطبيق يُجلب صفحة هذا الرابط في الخلفية. والنقطة المهمة هنا هي تقليل أوقات الاستجابة مع الاستفادة من الإمكانات الكاملة لاتصال المستخدم من خلال أحدث ميزات HTML5 وأفضلها. يمكنك الاطّلاع على العرض التوضيحي لميزة "اكتشاف الشبكة" هنا.
الخاتمة
وهذه ليست سوى بداية رحلة تطبيقات HTML5 للجوّال. أنت الآن ترى الأسس البسيطة والأساسية جدًا لـ "إطار عمل" الجوّال الذي تم إنشاؤه فقط حول HTML5 وتقنياته الداعمة. أعتقد أنّه من المهم أن يعمل المطوّرون مع هذه الميزات الأساسية ويعالجونها بدون إخفاءها في حزمة.