تقنيات HTML5 لتحسين أداء الجوّال

مقدمة

تُعد عمليات التحديث الدورية وعمليات النقل المتقطعة للصفحات والتأخيرات الدورية في أحداث النقر مجرد عدد قليل من المشكلات في بيئات الويب على الأجهزة الجوّالة اليوم. ويحاول المطورون الاقتراب قدر الإمكان من المحتوى الأصلي، إلا أنّهم غالبًا ما يخرجون عن مساره بسبب عمليات الاختراق وعمليات إعادة الضبط ووضع أُطر العمل الصارمة.

في هذه المقالة، سنناقش الحد الأدنى من المتطلبات لإنشاء تطبيق ويب HTML5 للأجهزة الجوّالة. والهدف الرئيسي من ذلك هو كشف التعقيدات الخفية التي تحاول أُطر عمل الأجهزة الجوّالة في الوقت الحالي إخفاءها. سترى أسلوبًا مبسّطًا (باستخدام واجهات برمجة تطبيقات HTML5 الأساسية) وأساسيات أساسية تمكّنك من كتابة إطار العمل الخاص بك أو المساهمة في الإطار الذي تستخدمه حاليًا.

تسريع الأجهزة

عادةً ما تتعامل وحدات معالجة الرسومات مع النمذجة الثلاثية الأبعاد التفصيلية أو الرسوم التخطيطية بتنسيق CAD، ولكن في هذه الحالة، نريد أن تظهر رسوماتنا الأساسية (divs والخلفيات والنص ذو الظلال الخلفية والصور وما إلى ذلك) على نحو سلس ويتم تحريكه بسلاسة عبر وحدة معالجة الرسومات. والأمر المؤسف هو أنّ معظم مطوّري الواجهات الأمامية يوفّرون عملية إنشاء الرسوم المتحركة هذه على إطار عمل تابع لجهة خارجية بدون القلق بشأن دلالات الألفاظ، ولكن هل يجب إخفاء ميزات CSS3 الأساسية هذه؟ دعني أقدم لك بعض الأسباب التي تجعل الاهتمام بهذه الأشياء مهمًا:

  1. تخصيص الذاكرة وعبء الحساب - إذا تبدلت لتركيب كل عنصر من عناصر DOM بهدف تسريع الأجهزة، قد يلاحقك الشخص التالي الذي يعمل على الرمز البرمجي ويضربك بقوة.

  2. استهلاك الطاقة - بالطبع، عند تشغيل الجهاز، تتأثر البطارية أيضًا. عند التطوير للأجهزة المحمولة، يضطر المطورون إلى أخذ مجموعة واسعة من قيود الأجهزة في الاعتبار أثناء كتابة تطبيقات ويب الأجهزة المحمولة. وسيكون هذا أكثر انتشارًا حيث يبدأ مصممو المتصفحات في إتاحة الوصول إلى المزيد والمزيد من أجهزة الأجهزة.

  3. التعارضات - واجهتُ سلوكًا معقدًا عند تطبيق تسريع الأجهزة على أجزاء من الصفحة تم تسريعها من قبل. لذا، من المهم جدًا معرفة ما إذا كان هناك تداخل في تسريع المعدَّل.

لضمان تفاعل المستخدمين بسلاسة وجعله أقرب ما يكون إلى المحتوى الأصلي، يجب أن نجعل المتصفِّح يعمل من أجلك. من الناحية المثالية، نريد من وحدة المعالجة المركزية للجهاز المحمول إعداد الرسوم المتحركة الأولية، ثم جعل وحدة معالجة الرسومات مسؤولة عن تركيب طبقات مختلفة فقط أثناء عملية الرسم المتحرك. وهذا ما تفعله translate3d وscale3d وtranslateZ - فهي تمنح العناصر المتحركة طبقتها الخاصة، مما يسمح للجهاز بعرض كل شيء معًا بسلاسة. لمعرفة المزيد حول الإنشاء السريع وآلية عمل WebKit، تملك "آريا هدايت" الكثير من المعلومات المفيدة على مدونته.

عمليات انتقال الصفحة

لنلقِ نظرة على ثلاثة من أكثر أساليب تفاعل المستخدم شيوعًا عند تطوير تطبيق ويب على الأجهزة الجوّالة: تأثيرات التمرير، والقلب، والتدوير.

يمكنك مشاهدة هذا الرمز عمليًا هنا 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;
}

لنلقِ نظرة الآن على خدمة CSS التي تتعامل مع رصد الأجهزة الجوّالة واتجاهها. ويمكننا معالجة جميع الأجهزة وجميع الحلول (يمكنك الاطّلاع على دقة الاستعلام عن الوسائط). لقد استخدمتُ بعض الأمثلة البسيطة في هذا العرض التوضيحي لتغطية معظم طرق العرض الرأسية والأفقية على الأجهزة الجوّالة. هذا مفيد أيضًا لتطبيق تسريع الأجهزة لكل جهاز. على سبيل المثال، بما أن إصدار سطح المكتب من WebKit يعمل على تسريع جميع العناصر التي تم تحويلها (بغض النظر عما إذا كان ثنائي الأبعاد أو ثلاثي الأبعاد)، من المنطقي إنشاء استعلام عن الوسائط واستبعاد التسريع على هذا المستوى. تجدر الإشارة إلى أنّ حيل تسريع الأجهزة لا توفر أي تحسين للسرعة ضمن الإصدار 2.2 أو الإصدارات الأحدث من نظام التشغيل Android Froyo، حيث تتم جميع عمليات الإنشاء داخل البرنامج.

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

خدمة CSS:

/*----------------------------flip transition */
#back,
#front {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  -webkit-transition-duration: .5s;
  -webkit-transform-style: preserve-3d;
}

.normal {
  -webkit-transform: rotateY(0deg);
}

.flipped {
  -webkit-user-select: element;
  -webkit-transform: rotateY(180deg);
}

تصحيح أخطاء تسريع الأجهزة

الآن بعد أن تناولنا الانتقالات الأساسية، لنلقِ نظرة على آليات كيفية عملها وتركيبها.

لإتمام جلسة تصحيح الأخطاء السحرية هذه، دعنا نشغل اثنين من المتصفحات وبيئة التطوير التي تختارها. ابدأ أولاً بتشغيل Safari من سطر الأوامر للاستفادة من بعض متغيرات بيئة تصحيح الأخطاء. أنا أستخدم جهاز Mac، لذا قد تختلف الأوامر بناءً على نظام التشغيل الذي تستخدمه. افتح المحطة الطرفية واكتب ما يلي:

  • $> تصدير 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. في شريط عناوين URL، اكتب about:flags.
  3. قم بالتمرير لأسفل بعض العناصر وانقر على "تمكين" لعدّاد اللقطات في الثانية (FPS).

إذا اطّلعت على هذه الصفحة في الإصدار الذي تم إعداده من Chrome، سترى عدّاد اللقطات في الثانية الأحمر في أعلى يمين الشاشة.

عدد اللقطات في الثانية في Chrome

وهذه هي الطريقة التي نعرف من خلالها أن ميزة "تسريع الأجهزة" مفعَّلة. ويعطينا أيضًا فكرةً عن كيفية تشغيل الرسوم المتحركة وما إذا كانت هناك أي تسرب (الرسوم المتحركة المستمرة التي يجب إيقافها).

هناك طريقة أخرى لعرض تسريع الأجهزة فعليًا وهي فتح الصفحة نفسها في Safari (باستخدام متغيرات البيئة التي ذكرتها أعلاه). يحتوي كل عنصر DOM المسرّع على تلوين أحمر اللون. يوضح لنا هذا بالضبط ما يتم تركيبه في الطبقة. لاحظ أن التنقل الأبيض ليس باللون الأحمر لأنه لا يتم تسريعه.

جهة اتصال مركبة

يتوفر أيضًا إعداد مشابه لـ Chrome في about:flags "حدود طبقة العرض المركبة".

هناك طريقة أخرى رائعة للاطّلاع على الطبقات المركّبة وهي عرض العرض التوضيحي للأوراق المتساقطة من WebKit أثناء تطبيق هذا التعديل.

أوراق مرسومة

وأخيرًا، لنفهم أداء أجهزة الرسومات في تطبيقنا بشكل صحيح، فلنلقِ نظرة على كيفية استهلاك الذاكرة. ونرى هنا أنّنا ندفع 1.38 ميغابايت من تعليمات الرسم إلى المخازن المؤقتة CoreAnimation على نظام التشغيل Mac. تتم مشاركة المخازن المؤقتة للذاكرة Core Animation بين OpenGL ES ووحدة معالجة الرسومات لإنشاء وحدات البكسل النهائية التي تراها على الشاشة.

رسوم متحركة 1

عندما نغير حجم نافذة المتصفح أو نكبرها، نلاحظ أيضًا توسعًا في الذاكرة.

رسوم متحركة 2

يمنحك هذا فكرة عن كيفية استهلاك الذاكرة على جهازك الجوّال فقط في حالة تغيير حجم المتصفح إلى الأبعاد الصحيحة. إذا كنت تصحح الأخطاء أو تختبر بيئات iPhone، اضبط الحجم إلى 480 × 320 بكسل. الآن نفهم بالضبط كيفية عمل ميزة "تسريع الأجهزة" والإجراءات المطلوبة لتصحيح الأخطاء. لا شكّ في أنّ استخدام إحدى وحدات معالجة الرسومات يعمل بشكل مرئي، ما يجعل الأمر واضحًا لك.

وراء الكواليس: الجلب والتخزين المؤقت

والآن، حان الوقت للارتقاء بعملية التخزين المؤقت للصفحات والموارد. كما هو الحال في JQuery Mobile وأُطر العمل المماثلة إلى حد كبير، سنجلب صفحاتنا ونخزنها مسبقًا مع استدعاءات AJAX المتزامنة.

لنتناول بعض المشكلات الأساسية لويب الجوّال وأسباب الحاجة إلى ذلك:

  • الجلب: يتيح الجلب المسبق لصفحاتنا للمستخدمين تنزيل التطبيق بلا اتصال بالإنترنت كما يتيح لهم عدم الانتظار بين إجراءات التنقل. بالطبع، لا نريد إيقاف معدل نقل البيانات للجهاز عندما يكون الجهاز متصلاً بالإنترنت، لذلك نحتاج إلى استخدام هذه الميزة باعتدال.
  • التخزين المؤقت: نريد منهجًا متزامنًا أو غير متزامن عند جلب هذه الصفحات وتخزينها مؤقتًا. نحتاج أيضًا إلى استخدام خيار LocalStorage (لأنه متوافق جيدًا بين الأجهزة) وهو للأسف غير متزامن.
  • استخدام AJAX وتحليل الاستجابة: استخدام innerHTML() لإدراج استجابة AJAX في نموذج العناصر في المستند (DOM) أمر خطير (وغير موثوق به؟). بدلاً من ذلك، نستخدم آلية موثوقة لإدراج استجابة AJAX والتعامل مع الطلبات المتزامنة. نستفيد أيضًا من بعض الميزات الجديدة في HTML5 لتحليل xhr.responseText.

بناءً على الرمز من العرض التوضيحي للشرائح والقلب والتدوير، نبدأ بإضافة بعض الصفحات الثانوية والربط بها. ثم سنقوم بعد ذلك بتحليل الروابط وإنشاء الانتقالات على الفور.

شاشة iPhone الرئيسية

اطّلع على العرض التوضيحي لميزة "الجلب وذاكرة التخزين المؤقت" هنا.

وكما ترون، نحن نستفيد من الترميز الدلالي هنا. مجرد رابط إلى صفحة أخرى. تتبع الصفحة الفرعية بنية العقدة/الفئة نفسها مثل الصفحة الرئيسية. يمكننا أن ننتقل إلى أبعد من ذلك واستخدام السمة 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') &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". هناك شرح أكثر تقدمًا لاستخدام 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 لترميز الأحرف، يتم تخزين كل بايت منفرد كـ 2 بايت مما يصل إلى حد مساحة التخزين لدينا من 5 ميغابايت إلى إجمالي 2.6 ميغابايت. نوضح في القسم التالي السبب الكامل لجلب هذه الصفحات/الترميز والتخزين المؤقت خارج نطاق ذاكرة التخزين المؤقت للتطبيق.

مع التطورات الأخيرة في عنصر iframe مع HTML5، أصبح لدينا الآن طريقة بسيطة وفعالة لتحليل responseText الذي نسترده من استدعاء AJAX. هناك العديد من محللات JavaScript المكونة من 3000 سطر والتعبيرات العادية التي تزيل علامات النصوص البرمجية وما إلى ذلك. ولكن لمَ لا نترك المتصفح يقوم بعمله على أفضل وجه؟ في هذا المثال، سنكتب responseText في إطار iframe مخفي مؤقت. نستخدم سمة HTML5 "وضع الحماية"، والتي تعمل على إيقاف النصوص البرمجية وتوفير العديد من ميزات الأمان...

من المواصفات: تمكّن سمة وضع الحماية، عند تحديدها، مجموعة من القيود الإضافية على أي محتوى يستضيفه iframe. ويجب أن تكون قيمتها مجموعة غير مرتَّبة من الرموز المميزة الفريدة المفصولة بمسافات والتي تكون غير حساسة لحالة الأحرف بترميز ASCII. والقيم المسموح بإدراجها هي "allows (السماح)" و"allow-origin" (السماح بالمصدر نفسه) و"السماح بالنصوص البرمجية" و"السماح بالتنقل الأساسي". عند ضبط السمة، يتم التعامل مع المحتوى على أنّه من أصل فريد، ويتم إيقاف النماذج والنصوص البرمجية، ومنع الروابط من استهداف سياقات تصفّح أخرى، ويتم إيقاف المكوّنات الإضافية.

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 وكل شيء على ما يرام.

فلماذا إذًا؟ لماذا لا يتم استخدام innerHTML فقط؟ على الرغم من أن innerHTML الآن جزء من مواصفات HTML5، إلا أن إدراج الاستجابة من خادم (سيئ أو خير) في منطقة غير محددة يُعد ممارسة خطيرة. أثناء كتابة هذه المقالة، لم أعثر على أي شخص يستخدم أي شيء سوى HTML الداخلي. أعلم أنّ JQuery يستخدمها في الأساس مع إلحاق احتياطي على الاستثناء فقط. ويستخدمه JQuery Mobile أيضًا. ومع ذلك، لم أُجرِ أي اختبار مكثف في ما يتعلق بـ "التوقف عن العمل عشوائيًا" في HTML الداخلي، ولكن سيكون من المثير للاهتمام جدًا معرفة جميع الأنظمة الأساسية التي يؤثر ذلك فيها. ومن المثير للاهتمام أيضًا معرفة الطريقة الأفضل أداءً... لقد سمعتُ ادعاءات من كلا الجانبين في هذا الصدد أيضًا.

رصد أنواع الشبكة والتعامل معها وتحديد ملامحها

والآن بعد أن أصبح لدينا القدرة على التخزين المؤقت (أو التخزين المؤقت التنبؤي) لتطبيق الويب الخاص بنا، علينا توفير ميزات اكتشاف الاتصال المناسبة التي تجعل تطبيقنا أكثر ذكاءً. هذا هو المكان الذي يصبح فيه تطوير تطبيقات الأجهزة المحمولة حساسًا للغاية لأوضاع الاتصال بالإنترنت/عدم الاتصال وسرعة الاتصال. أدخِل Network Information API. وفي كل مرة أعرض فيها هذه الميزة في عرض تقديمي، يرفع أحد الحاضرين يده ويسأل "لماذا يمكنني استخدام تلك الميزة؟". إذًا إليك طريقة ممكنة لإعداد تطبيق ويب ذكي جدًا على الأجهزة الجوّالة.

السيناريو المُمِل للذكاء المنطقي أولاً... أثناء التفاعل مع الويب من جهاز جوّال في قطار عالي السرعة، قد تختفي الشبكة في أوقات مختلفة وقد توفِّر مناطق جغرافية مختلفة سرعات نقل مختلفة (على سبيل المثال، قد تتوفر تقنية HSPA أو شبكة الجيل الثالث في بعض المناطق الحضرية، ولكن قد تتوفر في المناطق النائية تقنيات 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(). يمكنك هنا التحكم في تطبيقك في وضع عدم الاتصال بالإنترنت ومحاولة استرداد أي معاملات كانت تتم خلف الكواليس. يعمل هذا الرمز البرمجي أدناه على اكتشاف جميع الروابط الخارجية وإيقافها - مما يؤدي إلى اصطياد المستخدمين في تطبيقنا بلا اتصال بالإنترنت FOREVER muhahaha!

function processOffline() {
  setupApp();

  // disable external links until we come back - setting the bounds of app
  disabledLinks = getUnconvertedLinks(document);

  // helper for onlcick below
  var onclickHelper = function(e) {
    return function(f) {
      alert('This app is currently offline and cannot access the hotness');return false;
    }
  };

  for (var i = 0; i < disabledLinks.length; i++) {
    if (disabledLinks[i].onclick == null) {
      //alert user we're not online
      disabledLinks[i].onclick = onclickHelper(disabledLinks[i].href);

    }
  }
}

حسنًا، لننتقل إلى الأشياء الجيدة. الآن بعد أن تعرّف تطبيقنا على حالة الاتصال المتصلة بالإنترنت، يمكننا أيضًا التحقق من نوع الاتصال عندما يكون متصلاً بالإنترنت وتعديله وفقًا لذلك. لقد أدرجتُ عمليات تنزيل مقدّمي الخدمة المعتادة في أمريكا الشمالية وأوقات الاستجابة في التعليقات لكل عملية اتصال.

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

هناك العديد من التعديلات التي يمكننا إجراؤها على عملية matchAndCache، لكن كل ما فعلته هو أن تطلب من عملية جلب الموارد غير متزامنة (صحيح) أو متزامنة (خطأ) لاتصال معيّن.

المخطط الزمني لطلب Edge (المتزامن)

مزامنة Edge

المخطط الزمني لطلب شبكة Wi-Fi (غير المتزامن)

شبكة Wi-Fi غير متزامنة

يسمح هذا بطريقة معيّنة على الأقل لتعديل تجربة المستخدم استنادًا إلى الاتصالات البطيئة أو السريعة. وهذا لا يعني بأي شكل من الأشكال أنه حل نهائي لكل شيء. من المهام الأخرى إنشاء نمط تحميل عند النقر على رابط (في الاتصالات البطيئة) بينما قد يجلب التطبيق صفحة هذا الرابط في الخلفية. تتمثل المغزى هنا في تقليل أوقات الاستجابة مع الاستفادة من الإمكانات الكاملة لاتصال المستخدم مع أحدث وأروع ما يقدمه HTML5. يمكنك الاطّلاع على العرض التوضيحي لرصد الشبكة.

الخاتمة

الرحلة المستقبلية لتطبيقات HTML5 للجوّال قد بدأت للتو. وترى الآن الأسس البسيطة والأساسية جدًا لـ "إطار عمل" الجوّال الذي يعتمد فقط على HTML5 وتقنياته الداعمة. أعتقد أنّه من المهم أن يتعاون المطوّرون مع هذه الميزات وأن يعالجوها في جوهرها، وليس من خلال برنامج تضمين.