تکنیک های HTML5 برای بهینه سازی عملکرد موبایل

معرفی

به‌روزرسانی‌های چرخشی، جابه‌جایی صفحه‌های متلاطم، و تأخیرهای دوره‌ای در رویدادهای ضربه زدن تنها تعدادی از سردردهای موجود در محیط‌های وب موبایل امروزی هستند. توسعه دهندگان سعی می کنند تا جایی که ممکن است به بومی نزدیک شوند، اما اغلب توسط هک ها، بازنشانی ها و چارچوب های سفت و سخت از مسیر خارج می شوند.

در این مقاله، حداقل چیزی که برای ایجاد یک برنامه وب HTML5 موبایل نیاز است را مورد بحث قرار خواهیم داد. نکته اصلی این است که پیچیدگی‌های پنهانی را که فریمورک‌های موبایل امروزی سعی در پنهان کردن آن‌ها دارند، پنهان کنیم. یک رویکرد مینیمالیستی (با استفاده از APIهای اصلی HTML5) و اصول اساسی را خواهید دید که به شما قدرت می‌دهد تا چارچوب خود را بنویسید یا در چارچوبی که در حال حاضر استفاده می‌کنید مشارکت کنید.

شتاب سخت افزاری

به طور معمول، پردازنده‌های گرافیکی مدل‌سازی سه‌بعدی دقیق یا نمودارهای CAD را انجام می‌دهند، اما در این مورد، ما می‌خواهیم نقشه‌های ابتدایی ما (divs، پس‌زمینه، متن با سایه‌های در حال سقوط، تصاویر و غیره) صاف به نظر برسند و از طریق GPU به راحتی متحرک شوند. نکته تاسف بار این است که اکثر توسعه دهندگان فرانت اند این فرآیند انیمیشن را بدون نگرانی در مورد معنایی آن به یک چارچوب شخص ثالث منتقل می کنند، اما آیا این ویژگی های اصلی CSS3 باید پوشانده شوند؟ بگذارید چند دلیل به شما بگویم که چرا اهمیت دادن به این چیزها مهم است:

  1. تخصیص حافظه و بار محاسباتی - اگر فقط به خاطر شتاب سخت‌افزاری به ترکیب هر عنصر در DOM بپردازید، فرد بعدی که روی کد شما کار می‌کند ممکن است شما را تعقیب کند و به شدت شما را شکست دهد.

  2. مصرف برق - بدیهی است که وقتی سخت افزار وارد کار می شود، باتری نیز وارد می شود. هنگام توسعه برای تلفن همراه، توسعه دهندگان مجبور می شوند هنگام نوشتن برنامه های وب تلفن همراه، مجموعه وسیعی از محدودیت های دستگاه را در نظر بگیرند. زمانی که سازندگان مرورگر شروع به فعال کردن دسترسی بیشتر و بیشتر به سخت‌افزار دستگاه می‌کنند، این امر حتی رایج‌تر خواهد شد.

  3. تضادها - هنگام اعمال شتاب سخت‌افزاری برای بخش‌هایی از صفحه که قبلاً شتاب‌دهی شده بودند، با رفتار بدی مواجه شدم. بنابراین دانستن اینکه آیا شتاب همپوشانی دارید یا نه بسیار مهم است.

برای اینکه تعامل کاربر روان و تا حد امکان نزدیک به بومی باشد، باید مرورگر را برای ما کار کند. در حالت ایده‌آل، ما می‌خواهیم که CPU دستگاه تلفن همراه انیمیشن اولیه را راه‌اندازی کند، سپس GPU تنها مسئول ترکیب لایه‌های مختلف در طول فرآیند انیمیشن باشد. این همان کاری است که translate3d، scale3d و translateZ انجام می‌دهند - آنها به عناصر متحرک لایه خاص خود را می‌دهند، بنابراین به دستگاه اجازه می‌دهند همه چیز را با هم به راحتی رندر کند. برای کسب اطلاعات بیشتر در مورد ترکیب شتاب و نحوه کار WebKit، آریا هدایت اطلاعات خوبی در وبلاگ خود دارد.

انتقال صفحه

بیایید به سه مورد از متداول ترین رویکردهای تعامل با کاربر در هنگام توسعه یک برنامه وب تلفن همراه نگاهی بیندازیم: جلوه های اسلاید، تلنگر و چرخش.

می‌توانید این کد را در عمل در اینجا مشاهده کنید http://slidfast.appspot.com/slide-flip-rotate.html (توجه: این نسخه نمایشی برای یک دستگاه تلفن همراه ساخته شده است، بنابراین یک شبیه‌ساز را روشن کنید، از تلفن یا رایانه لوحی خود استفاده کنید، یا اندازه پنجره مرورگر خود را به ~1024 پیکسل یا کمتر کاهش دهید).

ابتدا، انتقال اسلاید، چرخش و چرخش و نحوه شتاب گرفتن آنها را تشریح می کنیم. توجه داشته باشید که چگونه هر انیمیشن فقط سه یا چهار خط CSS و جاوا اسکریپت می گیرد.

کشویی

متداول‌ترین روش از سه رویکرد انتقال، انتقال صفحه کشویی حس بومی برنامه‌های موبایل را تقلید می‌کند. انتقال اسلاید برای آوردن یک ناحیه محتوای جدید به پورت view فراخوانی می شود.

برای افکت اسلاید، ابتدا نشانه گذاری خود را اعلام می کنیم:

<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) به عنوان رویکرد "گلوله نقره ای" شناخته می شود.

هنگامی که کاربر روی یک عنصر ناوبری کلیک می کند، جاوا اسکریپت زیر را برای تعویض کلاس ها اجرا می کنیم. هیچ چارچوب شخص ثالثی استفاده نمی شود، این یک جاوا اسکریپت است! ;)

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 تمام عناصر تبدیل شده را تسریع می کند (بدون توجه به دو بعدی یا سه بعدی بودن)، ایجاد یک درخواست رسانه و حذف شتاب در آن سطح منطقی است. توجه داشته باشید که ترفندهای شتاب سخت افزاری هیچ گونه بهبود سرعتی را تحت 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) استفاده می‌کنیم.

آن را در عمل مشاهده کنید 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 ease-out برای تلنگر صفحه استفاده می کنیم، element.offsetLeft معمولی.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 بازی کنم تا بهترین حس بومی را به انتقال ها بدهم، اما سهولت کار این کار را انجام داد.

در نهایت، برای انجام ناوبری، باید متدهای slideTo() تعریف شده قبلی را که در آخرین نسخه آزمایشی استفاده کردیم فراخوانی کنیم.

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

در حال چرخش

در مرحله بعد، بیایید نگاهی به انیمیشن چرخشی استفاده شده در این نسخه نمایشی بیاندازیم. در هر زمان، می‌توانید صفحه‌ای را که در حال مشاهده آن هستید 180 درجه بچرخانید تا با ضربه زدن روی گزینه منوی «تماس»، سمت عقب آن نمایان شود. مجدداً، این فقط به چند خط CSS و مقداری جاوا اسکریپت نیاز دارد تا یک کلاس انتقال onclick را اختصاص دهد. توجه: انتقال چرخش در اکثر نسخه‌های Android به درستی ارائه نمی‌شود، زیرا فاقد قابلیت تبدیل CSS سه بعدی است. متأسفانه، اندروید به جای نادیده گرفتن تلنگر، با چرخش به جای ورق زدن، صفحه را «چرخ چرخ» می کند. ما توصیه می‌کنیم تا زمانی که پشتیبانی بهبود نیابد، از این انتقال استفاده کنید.

نشانه گذاری (مفهوم اصلی جلو و عقب):

<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
    <div id="contact-page" class="page">
        <h1>Contact Page</h1>
    </div>
</div>

جاوا اسکریپت:

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

اشکال زدایی شتاب سخت افزاری

اکنون که انتقال‌های اساسی خود را پوشش داده‌ایم، بیایید نگاهی به مکانیک نحوه کار و ترکیب آنها بیندازیم.

برای انجام این جلسه اشکال زدایی جادویی، اجازه دهید چند مرورگر و IDE انتخابی شما را فعال کنیم. ابتدا سافاری را از خط فرمان شروع کنید تا از برخی متغیرهای محیط اشکال زدایی استفاده کنید. من در مک هستم، بنابراین ممکن است دستورات بر اساس سیستم عامل شما متفاوت باشد. ترمینال را باز کنید و عبارت زیر را تایپ کنید:

  • $> صادرات CA_COLOR_OPAQUE=1
  • $> صادرات CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

این کار سافاری را با چند کمک رفع اشکال شروع می کند. CA_COLOR_OPAQUE به ما نشان می دهد که کدام عناصر در واقع ترکیب یا شتاب یافته اند. CA_LOG_MEMORY_USAGE به ما نشان می دهد که هنگام ارسال عملیات ترسیم خود به فروشگاه پشتیبان از چه مقدار حافظه استفاده می کنیم. این به شما می‌گوید دقیقاً چه مقدار فشار به دستگاه تلفن همراه وارد می‌کنید و احتمالاً نکاتی را در مورد اینکه چگونه استفاده از GPU شما ممکن است باتری دستگاه مورد نظر را تخلیه کند، ارائه می‌دهد.

حالا بیایید Chrome را فعال کنیم تا بتوانیم اطلاعات فریم در ثانیه (FPS) خوب را ببینیم:

  1. مرورگر وب گوگل کروم را باز کنید.
  2. در نوار URL، عبارت about:flags را تایپ کنید.
  3. چند مورد را به پایین اسکرول کنید و روی «فعال کردن» برای FPS Counter کلیک کنید.

اگر این صفحه را در نسخه جدید Chrome خود مشاهده کنید، شمارنده FPS قرمز رنگ را در گوشه سمت چپ بالا خواهید دید.

کروم FPS

اینگونه می دانیم که شتاب سخت افزاری روشن است. همچنین به ما ایده ای در مورد نحوه اجرای انیمیشن و اینکه آیا شما نشت می دهید (انیمیشن های در حال اجرا مداوم که باید متوقف شوند) به ما می دهد.

راه دیگر برای تجسم واقعی شتاب سخت افزاری این است که همان صفحه را در سافاری باز کنید (با متغیرهای محیطی که در بالا ذکر کردم). هر عنصر DOM تسریع شده دارای رنگ قرمز است. این دقیقاً به ما نشان می دهد که چه چیزی توسط لایه ترکیب می شود. توجه داشته باشید که پیمایش سفید قرمز نیست زیرا شتاب ندارد.

تماس ترکیبی

تنظیم مشابهی برای کروم نیز در about:flags "مرزهای لایه رندر ترکیبی" موجود است.

یکی دیگر از راه‌های عالی برای دیدن لایه‌های ترکیبی، مشاهده نسخه نمایشی برگ‌های در حال سقوط WebKit در حین اعمال این مد است.

برگ های ترکیبی

و در نهایت، برای درک واقعی عملکرد سخت افزار گرافیکی برنامه ما، بیایید نگاهی به نحوه مصرف حافظه بیندازیم. در اینجا می بینیم که 1.38 مگابایت دستورالعمل ترسیم را به بافرهای CoreAnimation در سیستم عامل مک فشار می دهیم. بافرهای حافظه Core Animation بین OpenGL ES و GPU به اشتراک گذاشته می شوند تا پیکسل های نهایی را که روی صفحه می بینید ایجاد کنند.

Coreanimation 1

هنگامی که ما به سادگی اندازه پنجره مرورگر را تغییر می دهیم یا بزرگ می کنیم، می بینیم که حافظه نیز گسترش می یابد.

Coreanimation 2

تنها در صورتی که اندازه مرورگر را به ابعاد صحیح تغییر اندازه دهید، این به شما ایده می‌دهد که چگونه حافظه در دستگاه تلفن همراه شما مصرف می‌شود. اگر در حال رفع اشکال یا آزمایش برای محیط های آیفون بودید، اندازه آن را به 480 پیکسل در 320 پیکسل تغییر دهید. اکنون می‌دانیم که شتاب‌دهی سخت‌افزار چگونه کار می‌کند و اشکال‌زدایی به چه چیزی نیاز دارد. خواندن در مورد آن یک چیز است، اما واقعاً دیدن بافرهای حافظه GPU که به صورت بصری کار می کنند، واقعاً همه چیز را به چشم می اندازد.

پشت صحنه: واکشی و ذخیره سازی

اکنون زمان آن رسیده است که ذخیره صفحه و منابع خود را به سطح بعدی ببریم. بسیار شبیه رویکردی که JQuery Mobile و فریمورک‌های مشابه استفاده می‌کنند، ما می‌خواهیم صفحات خود را با تماس‌های همزمان AJAX از قبل واکشی و کش کنیم.

بیایید به چند مشکل اصلی وب موبایل و دلایلی که چرا باید این کار را انجام دهیم، بپردازیم:

  • واکشی: واکشی اولیه صفحات ما به کاربران امکان می دهد برنامه را آفلاین کنند و همچنین امکان عدم انتظار بین اقدامات ناوبری را فراهم می کند. البته، ما نمی‌خواهیم وقتی دستگاه آنلاین می‌شود، پهنای باند دستگاه را خفه کنیم، بنابراین باید از این ویژگی کم استفاده کنیم.
  • ذخیره سازی: در مرحله بعد، ما یک رویکرد همزمان یا ناهمزمان را هنگام واکشی و کش کردن این صفحات می خواهیم. ما همچنین نیاز به استفاده از localStorage داریم (از آنجایی که در بین دستگاه ها به خوبی پشتیبانی می شود) که متأسفانه ناهمزمان نیست.
  • AJAX و تجزیه پاسخ: استفاده از innerHTML() برای درج پاسخ AJAX در DOM خطرناک است (و غیرقابل اعتماد ؟). ما در عوض از یک مکانیسم قابل اعتماد برای درج پاسخ AJAX و رسیدگی به تماس‌های همزمان استفاده می‌کنیم. ما همچنین از برخی ویژگی‌های جدید HTML5 برای تجزیه xhr.responseText استفاده می‌کنیم.

با تکیه بر کدهای اسلاید، تلنگر و چرخش نسخه ی نمایشی ، با افزودن چند صفحه ثانویه و پیوند دادن به آنها شروع می کنیم. سپس پیوندها را تجزیه می‌کنیم و انتقال‌ها را در لحظه ایجاد می‌کنیم.

صفحه اصلی آیفون

نسخه ی نمایشی Fetch and Cache را در اینجا مشاهده کنید.

همانطور که می بینید، ما در اینجا از نشانه گذاری معنایی استفاده می کنیم. فقط یک لینک به یک صفحه دیگر. صفحه فرزند از ساختار گره/کلاس مشابهی پیروی می کند. ما می‌توانیم این را یک قدم جلوتر برداریم و از ویژگی data-* برای گره‌های "page" و غیره استفاده کنیم. و در اینجا صفحه جزئیات (فرزند) در یک فایل 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>

حال اجازه دهید نگاهی به جاوا اسکریپت بیندازیم. برای سادگی، من هر کمکی یا بهینه‌سازی را از کد خارج می‌کنم. تمام کاری که ما در اینجا انجام می دهیم این است که از طریق یک آرایه مشخص از گره های 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 در Working Off the Grid with HTML5 Offline وجود دارد. در این مثال، استفاده اساسی از کش کردن در هر درخواست و ارائه اشیای ذخیره شده در حافظه پنهان را هنگامی که سرور چیزی جز پاسخ موفقیت آمیز (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 خود دریافت می کنیم، داریم. تعداد زیادی تجزیه کننده جاوا اسکریپت 3000 خطی و عبارات منظم وجود دارد که تگ های اسکریپت و غیره را حذف می کند. اما چرا به مرورگر اجازه نمی دهیم کاری را که به بهترین شکل انجام می دهد انجام دهد؟ در این مثال، ما می‌خواهیم responseText در یک iframe مخفی موقت بنویسیم. ما از ویژگی HTML5 "sandbox" استفاده می کنیم که اسکریپت ها را غیرفعال می کند و بسیاری از ویژگی های امنیتی را ارائه می دهد.

از مشخصات: ویژگی 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?
    }
  }
};

سافاری به درستی از انتقال ضمنی یک گره از یک سند به سند دیگر خودداری می کند. اگر گره فرزند جدید در سند دیگری ایجاد شده باشد، خطا ایجاد می شود. بنابراین در اینجا ما از adoptNode استفاده می کنیم و همه چیز خوب است.

پس چرا iframe؟ چرا فقط از innerHTML استفاده نمی کنید؟ حتی با وجود اینکه innerHTML اکنون بخشی از مشخصات HTML5 است، درج پاسخ از یک سرور (شر یا خوب) در یک منطقه کنترل نشده، عمل خطرناکی است. در طول نوشتن این مقاله، من نتوانستم کسی را پیدا کنم که از چیزی جز innerHTML استفاده کند. من می‌دانم که JQuery از آن در هسته خود با یک ضمیمه بازگشتی فقط در استثنا استفاده می‌کند. و JQuery Mobile نیز از آن استفاده می کند. با این حال، من هیچ آزمایش سنگینی در رابطه با innerHTML انجام نداده‌ام که «به‌طور تصادفی کار نمی‌کند» ، اما دیدن همه پلتفرم‌هایی که این کار را تحت تأثیر قرار می‌دهد بسیار جالب خواهد بود. همچنین جالب است که ببینیم کدام رویکرد کارآمدتر است... من ادعاهایی از هر دو طرف در این مورد نیز شنیده ام.

تشخیص نوع شبکه، مدیریت، و پروفایل

اکنون که توانایی بافر کردن (یا حافظه پنهان پیش‌بینی کننده) برنامه وب خود را داریم، باید ویژگی‌های تشخیص اتصال مناسب را ارائه دهیم که برنامه ما را هوشمندتر می‌کند. اینجاست که توسعه اپلیکیشن موبایل به حالت های آنلاین/آفلاین و سرعت اتصال بسیار حساس می شود. وارد 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);
  }
}

تنظیمات متعددی وجود دارد که می‌توانیم در فرآیند fetchAndCache خود انجام دهیم، اما تنها کاری که من در اینجا انجام دادم این بود که به آن گفتم منابع را به صورت ناهمزمان (درست) یا همزمان (نادرست) برای یک اتصال مشخص واکشی کند.

خط زمانی درخواست لبه (همگام).

همگام سازی لبه

جدول زمانی درخواست WIFI (ناهمزمان).

WIFI Async

این حداقل برخی از روش‌های تنظیم تجربه کاربر را بر اساس اتصالات کند یا سریع امکان‌پذیر می‌کند. این به هیچ وجه یک راه حل نهایی و همه چیز نیست. یکی دیگر از کارها این است که وقتی روی یک پیوند کلیک می شود (در اتصالات کند) یک مدال بارگذاری را پرتاب کنید در حالی که ممکن است برنامه همچنان صفحه آن پیوند را در پس زمینه واکشی کند. نکته مهم در اینجا کاهش تأخیر و در عین حال استفاده از قابلیت های کامل اتصال کاربر با جدیدترین و بهترین HTML5 است. نسخه نمایشی تشخیص شبکه را در اینجا مشاهده کنید .

نتیجه

سفر در مسیر برنامه های موبایل HTML5 تازه شروع شده است. اکنون زیربنای بسیار ساده و اساسی یک "چارچوب" تلفن همراه را می بینید که صرفاً بر اساس HTML5 ساخته شده است و از فناوری های پشتیبانی می کند. من فکر می‌کنم برای توسعه‌دهندگان مهم است که با این ویژگی‌ها در هسته خود کار کنند و به آن‌ها بپردازند و توسط یک پوشش پنهان نشوند.