Методы HTML5 для оптимизации производительности мобильных устройств

Введение

Вращающиеся обновления, прерывистые переходы страниц и периодические задержки в событиях касания — это лишь некоторые из головных болей в современных мобильных веб-средах. Разработчики стараются максимально приблизиться к нативному коду, но их часто сбивают с пути хаки, перезагрузки и жесткие рамки.

В этой статье мы обсудим минимум того, что необходимо для создания мобильного веб-приложения HTML5. Основная задача — раскрыть скрытые сложности, которые современные мобильные платформы пытаются скрыть. Вы увидите минималистичный подход (с использованием основных API-интерфейсов HTML5) и базовые основы, которые дадут вам возможность написать собственную структуру или внести свой вклад в ту, которую вы используете в настоящее время.

Аппаратное ускорение

Обычно графические процессоры обрабатывают детальное 3D-моделирование или диаграммы САПР, но в этом случае мы хотим, чтобы наши примитивные рисунки (разделы, фон, текст с тенями, изображения и т. д.) выглядели гладкими и плавно анимировались с помощью графического процессора. К сожалению, большинство разработчиков интерфейса передают этот процесс анимации сторонним фреймворкам, не заботясь о семантике, но следует ли маскировать эти основные функции 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 ускоряет все преобразованные элементы (независимо от того, двухмерные они или трехмерные), имеет смысл создать медиа-запрос и исключить ускорение на этом уровне. Обратите внимание, что приемы аппаратного ускорения не обеспечивают повышения скорости под 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 градусов, чтобы показать обратную сторону, нажав на пункт меню «Контакты». Опять же, для назначения класса перехода onclick требуется всего несколько строк CSS и немного JavaScript. ПРИМЕЧАНИЕ. Переход поворота отображается неправильно в большинстве версий Android, поскольку в нем отсутствуют возможности преобразования 3D 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);
}

Отладка аппаратного ускорения

Теперь, когда мы рассмотрели основные переходы, давайте посмотрим на механику их работы и компоновки.

Чтобы этот волшебный сеанс отладки состоялся, давайте запустим пару браузеров и выбранную вами IDE. Сначала запустите 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, вы увидите красный счетчик FPS в верхнем левом углу.

Хром ФПС

Вот как мы узнаем, что аппаратное ускорение включено. Это также дает нам представление о том, как работает анимация и есть ли какие-либо утечки (непрерывная анимация, которую следует остановить).

Еще один способ визуализировать аппаратное ускорение — открыть ту же страницу в Safari (с упомянутыми выше переменными среды). Каждый ускоренный элемент DOM имеет красный оттенок. Это показывает нам, что именно комбинируется по слоям. Обратите внимание, что белая навигация не красная, потому что она не ускорена.

Составной контакт

Аналогичная настройка для Chrome также доступна в разделе about:flags «Границы составного слоя рендеринга».

Еще один отличный способ увидеть составные слои — просмотреть демонстрацию падающих листьев WebKit во время применения этого мода.

составные листья

И, наконец, чтобы по-настоящему понять производительность графического оборудования нашего приложения, давайте посмотрим, как используется память. Здесь мы видим, что мы помещаем 1,38 МБ инструкций рисования в буферы CoreAnimation в Mac OS. Буферы памяти Core Animation совместно используются OpenGL ES и графическим процессором для создания окончательных пикселей, которые вы видите на экране.

Коранимация 1

Когда мы просто изменяем размер или разворачиваем окно браузера, мы также видим расширение памяти.

Коранимация 2

Это дает вам представление о том, как используется память на вашем мобильном устройстве, только если вы измените размер браузера до правильных размеров. Если вы занимались отладкой или тестированием среды iPhone, измените размер до 480 на 320 пикселей. Теперь мы точно понимаем, как работает аппаратное ускорение и что нужно для отладки. Одно дело читать об этом, но визуальное наблюдение за работой буферов памяти графического процессора действительно позволяет взглянуть на вещи в перспективе.

За кулисами: выборка и кэширование

Теперь пришло время поднять кэширование наших страниц и ресурсов на новый уровень. Подобно подходу, который используют JQuery Mobile и подобные платформы, мы собираемся предварительно получать и кэшировать наши страницы с помощью одновременных вызовов AJAX.

Давайте рассмотрим несколько основных проблем мобильного Интернета и причины, по которым нам нужно это сделать:

  • Извлечение: предварительная загрузка наших страниц позволяет пользователям отключить приложение, а также не требует ожидания между действиями навигации. Конечно, мы не хотим ограничивать пропускную способность устройства, когда оно подключается к сети, поэтому нам нужно использовать эту функцию экономно.
  • Кэширование. Далее нам нужен параллельный или асинхронный подход при извлечении и кэшировании этих страниц. Нам также необходимо использовать localStorage (поскольку он хорошо поддерживается среди устройств), который, к сожалению, не является асинхронным.
  • AJAX и синтаксический анализ ответа. Использование метода InternalHTML() для вставки ответа AJAX в DOM опасно (и ненадежно ?). Вместо этого мы используем надежный механизм для вставки ответов AJAX и обработки одновременных вызовов. Мы также используем некоторые новые возможности HTML5 для анализа xhr.responseText .

Опираясь на код из демонстрации Slide, Flip и Rotate , мы начнем с добавления дополнительных страниц и ссылок на них. Затем мы разберем ссылки и создадим переходы на лету.

iPhone для дома

Посмотреть демо-версию Fetch and Cache можно здесь.

Как видите, здесь мы используем семантическую разметку. Просто ссылка на другую страницу. Дочерняя страница имеет ту же структуру узлов/классов, что и ее родительская страница. Мы могли бы пойти еще дальше и использовать атрибут 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. Существует множество 3000-строчных парсеров JavaScript и регулярных выражений, которые удаляют теги скриптов и т. д. Но почему бы не позволить браузеру делать то, что он умеет лучше всего? В этом примере мы собираемся записать 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 также использует его. Однако я не проводил никаких серьезных тестов на предмет того, что InternalHTML «перестает работать случайным образом» , но было бы очень интересно увидеть все платформы, на которые это влияет. Также было бы интересно посмотреть, какой подход более эффективен… Я тоже слышал утверждения по этому поводу с обеих сторон.

Обнаружение типа сети, обработка и профилирование

Теперь, когда у нас есть возможность буферизовать (или прогнозировать кеширование) наше веб-приложение, мы должны предоставить правильные функции обнаружения соединений, которые сделают наше приложение более интеллектуальным. Именно здесь разработка мобильных приложений становится чрезвычайно чувствительной к онлайн/оффлайн режимам и скорости соединения. Войдите в 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);

В приведенных выше прослушивателях событий мы должны сообщить нашему коду, вызывается ли он из события или фактического запроса или обновления страницы. Основная причина заключается в том, что событие 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 (асинхронного)

Wi-Fi асинхронный

Это позволяет использовать по крайней мере какой-то метод настройки пользовательского опыта на основе медленных или быстрых соединений. Это ни в коем случае не является универсальным решением. Еще одна задача — вызвать модальное окно загрузки при нажатии ссылки (при медленных соединениях), в то время как приложение все еще может получать страницу этой ссылки в фоновом режиме. Важным моментом здесь является сокращение задержек при одновременном использовании всех возможностей соединения пользователя с новейшими и лучшими возможностями HTML5. Посмотреть демо-версию обнаружения сети можно здесь .

Заключение

Путешествие по пути развития мобильных HTML5-приложений только начинается. Теперь вы видите очень простую и базовую основу мобильной «структуры», построенной исключительно на HTML5 и поддерживающих его технологиях. Я думаю, что разработчикам важно работать с этими функциями и реализовывать их в своей основе, а не замаскировать оболочкой.