JavaScript
HTML
Development for Android
May 2015 14

Быстрое кроссплатформенное HTML5 приложение на Framework7

Задумываясь о разработке 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
+42
68.9k 547
Comments 20