Задумываясь о разработке html5 приложения, многим сразу на ум приходит jQuery, или точнее jQueryMobile. И попробовав написать даже самое простенькое приложение используя jQueryMobile, очень легко разочароваться, так как производительность и отзывчивость получившегося html5 приложения куда ниже ожидаемого, и уж совсем его не сравнить с нативными приложениями.



Соответственно, если продолжить идти по пути html5, вы постепенно узнаёте, что тормоза и плохая отзывчивость из-за того, что есть множество тонкостей, например, что jquery далеко не самый быстрый вариант для приложения или по-умолчанию любое нажатие пользователя срабатывает с 300мс задержкой, что ухудшает отзывчивость.

Также важным моментом является есть ли набор готовых типичных компонентов, таких как список, кнопки, боковое меню итд, время на повторную разработку которых нет смысла трат��ть. Можно добавить еще к этому списку то, что как-то всё таки нужно решить проблему производительности, чтобы конкурировать с нативными приложениями.

Framework7


И мы, наконец, подходим к самому главному, чтобы не забивать себе голову решением таких проблем, а получить сразу готовый инструмент для разработки быстрых, отзывчивых и функциональных html5 приложений, существует Framework7. Сам я с ним столкнулся совершенно случайно, и удивился тому, насколько он прост в использовании без необходимости искать решения в разных местах, достаточно просто подключить framework7.js.

Стоит отметить, что для работы с Framework7 не нужно использовать какие-то сторонние модули или библиотеки, так как он всё уже содержит:
  • High-performance Animation — хочется выделить это отдельно, так как анимации и правда очень быстрые
  • fast-clicks — решает проблему задержки 300мс без сторонних библиотек
  • Template7 — движок шаблонов, синтаксис как у Handlebars, но меньше весит и скорость до 3х раз быстрее
  • Dom7 — более быстрая и прозрачная замена jQuery
  • Navigation / Router — множество вариантов переходов и их контроля
  • Поддержка стилей, включая частичную поддержку material design
  • Огромный список встроенных компонентов, таких как forms, buttons, list view, pull to refresh, infinite scroll, slide menu итд
  • Свайпы — встроенные свайпы на все случаи жизни: вызвать меню свайпом, возврат к предыдущему экрану, удалить элемент итд
  • И множество других полезных вещей

На сайте есть огромное кол-во примеров (работают в браузере), инструкций и обучающих статей (например, работа Framework7 совместно с AngularJS)
www.idangero.us/framework7

Пример приложения




Чтобы далеко не ходить, создадим небольшое приложение-пример, где можно будет посмотреть, как объединить отдельные компоненты фреймворка в единое приложение. Будем использовать slide menu, pull to refresh, infinite scroll, смену material/ios style на лету и огромным списком на 8000 элементов, который не тормозит (virtual list).

Структура приложения довольно простая:
  • index.html — дизайн приложения
  • about.html — дизайн другого view
  • app.js — файл инициализации и настроек приложения
  • css/app.css — этот файл в общем-то не нужен, так как в framework7.css есть уже все настройки дизайна, но можно, например, сменить цвет фона бокового меню
  • lib/framework7.js — сам фреймво��к
  • lib/framework7.css — дизайн всех элементов и компонентов в стиле ios
  • lib/framework7.material.css — дизайн всех элементов и компонентов в стиле material

Больше ничего не потребуется, другие библиотеки или фреймворки для создания приложения не нужны. Но при желании можно использовать require.js, angular.js, matreshka.js итд.

index.html
Для начала рассмотрим каркас index.html:
<!DOCTYPE html>
<html>
  <head>
    <!-- Добавляем необходимые meta-тэги -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <title>My App</title>
    <!-- подключаем framework7 стили, и добавляем id, чтобы можно было менять стиль на лету -->
    <link id="pagestyle" rel="stylesheet" href="lib/framework7.css">
    <!-- подключаем наши кастомные стили -->
    <link rel="stylesheet" href="css/app.css">
  </head>
  <body>
    <!-- statusbar-overlay для правильной поддержки полноэкранного режима -->
    <div class="statusbar-overlay"></div>
    <!-- тоже самое для panel -->
    <div class="panel-overlay"></div>

    <!-- если нужно левое боковое меню с reveal эффектом -->
    <div class="panel panel-left panel-reveal">
    </div>

    <!-- самое главное, добавляем views -->
    <div class="views">
    <!-- главный экран, должен содержать класс view-main -->
      <div class="view view-main">

    <!-- если требуется верхняя панель, так же указываем что у нее будет белая тема -->
        <div class="navbar theme-white">
        </div>

    <!-- собственно содержимое страницы, указываем что navbar и toolbar будет зафиксированы -->
        <div class="pages navbar-through toolbar-through">
    <!-- в data-page указываем название страницы, которое желательно для всех страниц быть разным -->
          <div data-page="index" class="page">
            <div class="page-content">
            </div>
          </div>
        </div>

    <!-- если нужная нижняя панель -->
        <div class="toolbar">
          <div class="toolbar-inner">
          </div>
        </div>
      </div>

    </div>
    <!-- подключаем framework7 и наш файл инициализации -->
    <script type="text/javascript" src="lib/framework7.js"></script>
    <script type="text/javascript" src="app.js"></script>
  </body>
</html>            

Добавление компонентов очень просто реализовано (для каждого компонента есть инструкция на сайте), например, чтобы добавить pull-to-refresh, достаточно добавить в
<div class="page-content">
</div>

класс pull-to-refresh-content, и дизайн стрелочки, которая будет показываться при обновлении:
<div class="page-content pull-to-refresh-content">
  <div class="pull-to-refresh-layer">
      <div class="preloader"></div>
      <div class="pull-to-refresh-arrow"></div>
  </div>
</div>

Теперь надо добавить table view (list view), в котором будет список наших элементов. Для создания коротких списков подойдет List View (Media List итд, список можно создавать статически или динамически):
<div class="list-block media-list"><ul>тут данные</ul></div>

Но для очень длинных списков требуется выгрузка элементов, которые сейчас не видны на экране, это необходимо для того, чтобы ничего не тормозило, и приложение работало как и нативное. Такий список называется Virtual List, и создается так же просто:
<div class="list-block virtual-list media-list">тут оставить пусто</div>

В дальнейшем список надо будет проинициализировать и заполнить в app.js

Сюда же добавим infinite-scroll, который добавляет так же, добавлением одноименного класса, дизайна крутилки и указанием начиная с какого расстояния вызывать infinite-scroll:
<div class="page-content pull-to-refresh-content infinite-scroll" data-distance="100">
		<div class="pull-to-refresh-layer">
		    <div class="preloader"></div>
		    <div class="pull-to-refresh-arrow"></div>
		</div>

		<div class="list-block virtual-list media-list"></div>

		<div class="infinite-scroll-preloader">
		  <div class="preloader"></div>
		</div>
</div>


Чтобы сменить ios стиль на material стиль и наоборот, добавим 2 кнопки, например, в левом меню:
<div class="list-block left-menu">
         	<div class="row theme-white" style="width:90%;margin-left: auto;margin-right: auto;">
		  <div class="col-50">
		    <a href="#" class="button changestyle" rel="lib/framework7.css">iOS style</a>
		  </div>
		  <div class="col-50">
		    <a href="#" class="button changestyle" rel="lib/framework7.material.css">Android style</a>
		  </div>
		</div>    
</div>

Позже на класс changestyle повесим обработчик, чтобы реагировал на нажатие и менял css файл на указанный в rel.

app.js
Теперь рассмотрим файл app.js, в котором производится настройка приложения, инициализация списков итд.
// Инициализируем движок фреймворка
var myApp = new Framework7({
    animateNavBackIcon:true, 
    pushState: true, //при переходе между экранами, чтобы работала кнопка back на android
    modalTitle: "MyApp",
    modalButtonCancel: "Отмена", //текст Cancel кнопки
    swipePanel: 'left', //включаем левого меню свайпом
});

// если запускается на ios, то кнопки back нету, и поэтому можно отключить
if (Framework7.prototype.device.os == "ios") 
	myApp.params.pushState = false; 
 
// вместо jQuery используем встроенный Dom7, переменную можно назвать $, чтобы было привычнее, но если оставить $$, то в дальнейшем можно будет легко подключить jQuery, если понадобится
var $$ = Dom7;

// по клику меняем css файл, меняя ios и material стиль. Выбор запоминаем в localStorage, чтобы при перезапуске приложения загружался нужный css
$$(".changestyle").click(function() { 
	$$("#pagestyle").attr("href",$$(this).attr('rel'));
	localStorage.setItem("css", $$(this).attr('rel'));
	return false;
});

// при первой загрузке загружаем запомненный css
if(localStorage.getItem("css")) {
	$$("#pagestyle").attr("href",localStorage.getItem("css"));
}

// инициализируем главную вьюху
var mainView = myApp.addView('.view-main', {
  dynamicNavbar: true, 
  domCache: true, //чтобы навигация работала без сбоев и с запоминанием scroll position в длинных списках
});

// инициализируем и заполняем вирутальный список используя шаблон (один из вариантов использования Template7)
var myList = myApp.virtualList('.list-block.virtual-list', {
    items: [
        {
	    id: 1,
            title: 'Item 1',
            picture: 'http://lorempixel.com/88/88/abstract/1'
        },
        {
	    id: 2,
            title: 'Item 2',
            picture: 'http://lorempixel.com/88/88/abstract/2'
        }
    ],
    height:44,
	template: 
		'<li class="contact-item" data-id="{{id}}" >' +
			  '<a href="about.html" class="item-link">' + 
				  '<div class="item-content">' +
			                  '<div class="item-media"><img src="{{picture}}" width="22"></div>' +
			                  '<div class="item-inner">' +
			                      '<div class="item-title">{{title}}</div>' +
			                  '</div>' +
	        	          '</div>' +
		 	  '</a>' + 
               '</li>'
});  

// пример функции, которая будет обновлять содержимое виртуального списка
function reloadTable(table, array)
{
	table.items = array;
	table.update();
}

// заполним виртуальный лист 20 новыми элементами
var itemsArray = [];
function firstInitList(text, count)
{
	itemsArray = [];
	for (var i = 0; i < count; i++ ) 
	{
		itemsArray.push({ id: i, title: text + ' ' + i, picture: 'http://lorempixel.com/88/88/abstract/' + i });
	}
}

firstInitList("Item", 20);
reloadTable(myList, itemsArray);

// инициализируем pull-to-refresh
var ptrContent = $$('.pull-to-refresh-content');
 
ptrContent.on('refresh', function (e) {
    // Эмулируем 0.5секундную задержку
    setTimeout(function () {
	refreshIt();
    }, 500);
});

// просто случайные данные для генерации виртуального листа
var authors = ['Beatles', 'Queen', 'Michael Jackson', 'Red Hot Chili Peppers'];

function refreshIt()
{
        // первые 10 элементов создадим с надписью Refresh
	firstInitList("Refresh", 10);
	myList.deleteAllItems();
	myList.appendItems(itemsArray);

        // остальные 8000 случайным образом
	var temparr = [];
	for(var i = 0; i<8000; i++) 
	{
            var picURL = 'http://lorempixel.com/88/88/abstract/' + Math.round(Math.random() * 10);
            var author = authors[Math.floor(Math.random() * authors.length)];
	    temparr.push({ id: i, title: author, picture: picURL });
	}
        myList.appendItems(temparr);
        myApp.pullToRefreshDone();
}

// инициализируем infinite-scroll
$$('.infinite-scroll').on('infinite', function () { 
  loadMore(); 
});          

// флаг
var loading = false;

//прячем крутилку infinite-scroll'а
$$('.infinite-scroll-preloader').hide();

function loadMore()
{
  // если уже загружаем данные, то ничего не делаем
  if (loading) return;
  $$('.infinite-scroll-preloader').show();
 
  // Эмулируем 1 секундную задержку
  setTimeout(function () {
    for (var i = lastIndex + 1; i <= lastIndex + itemsPerLoad; i++) {
	itemsArray.push({ id: i, title: 'Item ' + i, picture: 'http://lorempixel.com/88/88/abstract/1' });
    }
    reloadTable(myList, itemsArray);
    // сбрасываем флаг, после загрузки данных
    loading = true; 
  }, 1000);
}


app.css
// дизайн крутилки для infinite-scroll
.infinite-scroll-preloader {
  margin-top:-20px;
  margin-bottom: 10px;
  text-align: center;
}
.infinite-scroll-preloader .preloader {
  width:34px;
  height:34px;
}  
// сменим цвет фона navbar
.navbar {
  border-bottom: none;
  background: #2196f3;
  color: #ffffff;
}
.navbar a.link {
  color: #ffffff;
}
// сменить цвет фона левого меню
.panel, .left-menu .list-button {
  background-color: #3f4041;
}
.left-menu .item-link.list-button {
  text-align: left;
}


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

Ускоряем html5 приложения на старых версия android. Crosswalk


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

Дело в том, что на версиях андроид до 4.4 используется очень тормозной webview (убедиться в этом можно, например, если на 4.1 андроид поставить chrome beta и запустить приложение в нём, а потом сравнить с тем, что есть во встроенном браузере, то разница в скорости будет очень заметна). Поэтому, если просто запаковать html5 приложение в apk, оно будет использовать именно встроенный тормозной webview.

Только начиная с 5.x андроида, webview обновляется отдельно и базируется на свежем chromium, благодаря чему html5 приложение будет работать быстро и плавно.

Проще говоря, если нужна хорошая производительность от html5 приложения на любых устройствах и любых версиях андроид, нужно чтобы оно работало на chromium движке. Проект, который позволяет это сделать, называется crosswalk и вместо встроенного webview используется свой собственный, который работает на последней версии chromium.



Самый простой способ проверить как ваше html5 приложение будет работать с использованием crosswalk, это установить Intel XDK, создать пустой «Standart HTML5» проект, и заменить в созданном проекте папку www на вашу.

После этого выбрать Build и нажать на Crosswalk for Android. Приложение будет скомпилировано на удаленном сервере, и спустя несколько минут вы получите ссылку на apk (версия для arm и x86). К минусам можно отнести тот факт, то размер приложения увеличится на ~19мб.

Сайт framework7: www.idangero.us/framework7
Онлайн пример готового приложения: comedian-ant-73047.bitballoon.com (так как онлайн, то немного тормозит загрузка стилей)
Исходники: yadi.sk/d/Quu2VfApgcGXA
Готовые apk файлы, созданные через Intel XDK: yadi.sk/d/marrZA5-gcGuQ

Онлайн пример большинства возможностей (kitchen-sink): framework7.io/kitchen-sink-ios или framework7.io/kitchen-sink-material
Тоже самое в material дизайне: poacher-bear-12003.bitballoon.com/kitchen-sink-material/index.html