JS в мобильных устройствахНа примере одного проекта
Проект
• Задача o R&D / Поиск истины– Пригоден ли html5 для использования на
мобильных устройствах– UI фреймворк для сайтов заказчикаo Сделать демо новостного сайта
• Требованияo Поддержка наибольшего числа устройствo Простотаo Гибкость
Все уже сделано, но не нами
*источник http://aspirelondon.com/blog/articles/financial-times-app/ft-ipad-app/
http://ft.com
Чем пользуются в mobile web?
• Мобильный телефон
• Планшетный компьютер
• Читалка
• Плеер
*источник: http://en.wikipedia.org/wiki/Mobile_operating_system
Через что смотрят mobile web?
Webkit браузеры доминируют
Типичные характеристики
• Устройствоo Слабая батарейкаo Не всегда много памятиo Дорогой интернет (спорно)o Размер экрана в среднем меньше десктопа
• Браузер o Ограничен в памятиo Получает системные события не первыйo Частично поддерживает новые стандарты
Мобильный браузер
• Dom API
• Хранилище на стороне клиента
• Анимация
• Аудио, Видео
• Canvas
• SVG
• WebSocket
• WebWorkers
• ...
Проверка возможностей
http://html5test.com/http://haz.io/http://caniuse.com/
Требования к демо сайту
• Мокапыo Много картинок в интерфейсе (20 x 10)o Много видеоo Все скролится (10 скролов)o Кругом одни слайдшоу (20 слайд шоу)
• Дополнительноo Оффлайн режимo Бекэнд постоянно падаетo Закладываться на новые устройства
Выбор каркаса фреймворка
• jquery / jquery mobileo Компоненты – плагиныo Нет каркасности
• sencha touch / kendoo Есть все (почти)o Лицензия
• Backboneo Только MVCo Ничего не навязываетo Нет UI компонентов
Слайдеры и скролеры
• iScroll
• TouchScroll
• Wipetouch
• Touchswipe
• Scrollability
• touch-scroll
• Zynga Scroller
• -webkit-overflow-scrolling:touch;
Пишем свой фремворк
• Быстрый
• Потребляет мало памяти
• Кросс браузерный
• Гибкий
• Слабо связанный
Что мы хотели
JS рутина
• DOMo Селекторы
• Array o delete arr[0]o Фильтрация
• Objecto Итерированиеo Наследование
• События
• Логирование
DOM
• Jqueryo Большойo Известныйo Кросбраузерный
• Zeptoo Легкийo Ориентирован на mobile
DOM
• css3 селекторыo querySelector()o querySelectorAll()
• fragmentsvar div = document.getElementsByTagName("div");
var fragment = document.createDocumentFragment();
for ( var e = 0; e < elems.length; e++ ) {
fragment.appendChild( elems[e] );
}
for ( var i = 0; i < div.length; i++ ) {
div[i].appendChild( fragment.cloneNode(true) );
}
Утилиты
• Underscoreo Легкийo Но только хелперы
• Backbone– Коллекции– Диспетчер событий– Модели– Представления– Контроллеры
Необходимо невелировать среду
• Поддержка ECMA5
• CSS
• Анимация
• Системные события
Патерны: Singleton
var instance = (function () { var privateVar; function privateMethod() { // ... } return { // public interface publicMethod: function () { // private members can be accessed here } };})();
Паттерны: Mixin, Inheritance
• Прототипo Динамическое изменение поведения всех
объектовo Конструктор
• Примесьo Копирование поведения
Определяем среду исполнения
var ua = navigator.userAgent.toLowerCase().replace(/\(.*?\)/g, "");var devices = { "android": /android\s+([\d.]+)/, "opera" : /opera.+(presto\/[^.]+\.[^.]+)/, "ipad" : /ipad.*os\s([\d_]+)/, "kindle" : /kindle|silk/};
for(i in devices) { if (devices.hasOwnProperty(i)) { var matches = devices[i].test(ua); // .. }}
Touch события
var hasTouch = 'ontouchstart' in window;
var Events = { hasTouch : hasTouch, resizeEvent : ('onorientationchange' in window ) ? 'orientationchange' : 'resize', clickEvent : hasTouch ? 'tap' : 'click', // синтетическое startEvent : hasTouch ? 'touchstart' : 'mousedown', // один раз moveEvent : hasTouch ? 'touchmove' : 'mousemove', // постоянно endEvent : hasTouch ? 'touchend' : 'mouseup', // один раз cancelEvent : hasTouch ? 'touchcancel' : 'mouseup',};
// webkitEvents.transitionEndEvent = 'webkitTransitionEnd';
Паттерны: Abstract Factory
var device = 'ipad'; // 'android'
var Environment = (function(device) { var environment;
switch(device) { case 'ipad' : environment = ... break; ... } environment = _({}).defaults(environment); return { factory : function() { return environment; } }})(device);
var environment = Environment.factory();
Дебаг и логирование
• "use strict" o Подсветка в IDE
• Профилирование важноo Замена дебагеруo Поиск узких мест
• console.log()o В ipad консоль не раскрывает объектыo Снижает производительностьo Не так удобно как хотелось бы
Профайлер
• Идея log4jo Каждый компонент пишет в своем namespaceo Уровни логов (debug, info, warn)
• Общий пул сообщенийo warn в одном месте может показать состояние
всей системы o Альтернатива стек трейсу
• Возможность выключить все логи
• Возможность смотреть логи в production
События приложения
• Приложение event-driven
• Компоненты знают мало друг о друге
• Многие вещи происходят асинхронноo Ajaxo Анимацияo DOM изменения
• События управляют объектами
Диспетчер событий
function EventDispatcher() { this._events = {};}
EventDispatcher.prototype = { on: function (name, handler, scope) { this._events[name] = this._events[name] || []; this._events[name].push({ handler: handler, scope: scope || this }); },
fire: function (name) { var args = arguments.toArray().slice(1), events = this._events[name]; if (events) { events.forEach(function (event) { event.handler.apply(event.scope, args); }); } }};
Анимация
• translate
• translate3d
• scale
• rotate
• Хардварная
• CSS префикс
• Скорость может отличаться
Анимация в деталях
• CSS .container { -webkit-transform: translate3d(0px, -700px, 0px); -webkit-transition: all 10s cubic-bezier(0, 0, 0.1, 1); -webkit-transform-style: preserve-3d;
}
• Jquery/Zepto$('.container').animate({ 'translate3d': 'translate3d(0px, -700px, 0px)',}, 10);
Скорость анимации
• Не грузите контент
• Не меняйте DOM
• Избегайте любых действий во время анимации
• translate3d работает только на видимых обхектах
• Меняйте DOM только после остановки анимации
• Много анимации может привести к падению браузера
Скрол
• Хуки в процесс анимации
• Управление touch событиями
• Управление через его API
• Синхронизация с другими компонентами
• Ускорение
• Обработка смены ориентации экрана
• Отзывчивый интерфейс
Слайдшоу
• Ленивое построение слайдов
• Экономия памяти
• Хуки в процесс анимации
• Автослайдинг
• Переход на произвольный слайд
• Отзывчивый итерфейс
Особенности на устройствах
• Ipad баг: событие окончания анимации пропускается в ~20% случаевo использовать планировщик
• Android при разворотах кидает и "resize" и "orientationchange"o Отлавливать оба события
• Android работает медленнее с translate3d при включенном "OpenGL acceleration"o Android WebView
Особенности на устройствах
• Контейнер не отпускает "move” при выпадении из поля видимости– Прокси для touch событий
• Изменение ориентации приводит к перерисовке интерфейса – Быть готовым и обрабатывать корректно
• Синхронизация анимации при новых касания – Уметь останавливать анимацию и начинать
новую
Смена ориентации
window.addEventListener('orientationchange', function() { var d = Math.abs(window.orientation);
if (d == 90) {
// пейзаж } else { // портрет }}, false);
• На Android событие 'resize'
Планировщик
var Scheduler = function () { this.callbacks = []; this.timers = [];};
Scheduler.prototype = { schedule: function (callback, delay) { var self = this; self.callbacks.push(callback); var timer = setTimeout(function () { self.execute(); }, delay); self.timers.push(timer); },
execute: function () { var callbacks = this.callbacks; if (callbacks && callbacks.length) { var cb; while (cb = callbacks.shift()) { cb(); } } }};
Синхронизация анимации
• Автослайдинг
Visible Screen
Scrollable YSlide 1
Scrollable Y Slide 2
Proxy контейнер для делегирования событий на активный слайд
Синхронизация анимации
• Текущее положение анимируемого контейнера
var computedStyle = window.getComputedStyle(node)['-webkit-transform']; var parsedMatrix = new WebKitCSSMatrix(computedStyle); var x = parsedMatrix.e; var y = parsedMatrix.f;
• При смене ориентации необходимо пересчитывать размеры
Навигация в приложении
• Url fragment #o http://app.example.com#accounto http://app.example.com#news
• Подписка событий на изменение urlwindow.addEventListener("hashchange", function () { var router = window.location.hash; // ...});
function navigate(route) { window.location.hash = route;}
• Изменение hash медленная операция
Хранилище на стороне клиента
• до 50 мб (требует подтверждения пользователя)
• Local storage
• App cache
• Indexed DB
• Websql (не является стандартом)
• Cookies
Хранилище на стороне клиента
• local storagelocalStorage.key = 'value'
• app cache<html manifest="app.appcache">
#app.appcache
CACHE MANIFEST
index.html
css/main.css
js/app.js
Обновления
• Обновление файлов из манифестаvar cache = window["applicationCache"];cache.addEventListener('updateready', function () { window.location.reload();
});
• Чистка локального хранилищаlocalStorage.clear();
• Получение свежих файловo запросить http://app.example.com/file?
{timestamp}
Аудио, Видео
• тег video <video controls autoplay preload src="video.mp4"></video>
• тег audio <audio controls autoplay preload loop src="audio.wav">
• Формат у всех свой
• Процессор у всех разный
• Спеки лучший друг
• Готовим контент на сервере
Шаблоны
• Обрабатываются на клиенте
• Удобные для верстки
• Понятные для браузера
• Быстрые в обработке
• XSS
Шаблонизаторы
• Mustache – язык шаблонов
• Реализация• dust.js
• hogan.js
• Серверный компиляторo node.js
Компиляция шаблонов
• Сервер<ul>
{#messages}
<li data-id="{id}">{text}</li>
{/messages}
</ul>
o Распарсить шаблонo Скомпилировать в JS представление
• Клиентo Вставить данныеo Обновить html
• Внутренне представление шаблонаo Конкатенация JS строк
Отрисовка HTML
• Работа с DOM может быть дорогойo Reflow – пересчет параметров дереваo Repaint – перерисовка старницы
• Как избежатьo Fragmentso Большие части лучше менять innerHtmlo Маленькие изменения напрямую через DOM
Собираем все вместе
• Браузер загружает файлы асинхронно
• Велика вероятность конфликта зависимостей
• Глобальная область видимости
• Модульная архитектура
• Выход естьo Инъекция зависиммостейo Компиляция всего приложения
Require.js
• Поддерживает минификациюo JSo CSS
• Умеет грузить файлы по порядку
• Помогает избегать конфликтов имен
• Работает с node.js
Require.js
<!DOCTYPE html><html manifest="app.minifest"> <head></head> <body> <script src="js/require.js" data-main="js/application"></script> </body></html>
Require.js
// spec.jsrequire.config({ paths: { "zepto": "vendor/zepto", "logger": "lib/logger", "environment": "lib/environment", ... }});
Require.js
// application.jsdefine([ "order!zepto", // соблюдает порядок "logger"], function ($, Logger) { var logger = new Logger; // ...});
Производительность
• Профилирование
• Быстрые операцииo Математические расчетыo Запросы к DOMo Оперирование объектами
• Медленные операцииo Изменения в DOMo Отрисовка
• Компиляция шаблонов
• Минификация CSS, JS
• Освобождение ресурсов
Память
• Потребители памяти– Картинки– DOM дерево– Анимация
• Если память кончится, браузер упадет• Меры борьбы– Прятать все, что не видно пользователю– Используем бекграундные картинки вместо <img>– Избегать reflow/repaint, особенно при анимации– Избавляться от тяжелых библиотек
Интеграция с бекэндом
• Минимизируем число http запросовo Запрос всех картинок одним запросомo Кодирование картинок в base 64
• JSON/ GZIP
• Подготовка контентаo Изменение размеров картинок под устройство
• Application cache
• Чем меньше логики на клиенте - тем лучше
Делаем свою Карусель
Требования
• Карусель картинок
• Работоспособность в ipad, chrome
• Поддержка событий мышки
• Режим слайдшоу
• Плавная анимация
Контейнеры
• Прокрутка осуществляется на контейнере, содержащем слайды
• Показываем только видимые элементы• Контейнеры вне зоны видимости скрываем для
экономии ресурсов
Следим за браузером
• touchStart - один раз
• touchMove - много раз
• touchEnd - один раз
• transitionEnd – по окончании анимации
• resize/orientationchange - смена ориентации
• Не забываем про разницу chrome и ipado В chrome перенос мышки на новую позицию
вызывает "move"
API
• setSlideShowState(state)
• moveNext()
• movePrevious()
• reset()
Запрещаем zoom
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,
minimum-scale=1, user-scalable=no"/>
Конструктор
• Параметры по умолчанию
• Навешивает DOM события
• Делает все расчеты позиционированияo Окна просмотраo Слайдов
• Сохраняет ссылки на слайды
Обработчик браузерных событий
Scroll.prototype = { handleEvent: function (e) { switch (e.type) { case startEvent: this._startTouch(e); break; case moveEvent: this._moveTouch(e); break; case endEvent: this._endTouch(e); break; case resizeEvent: this._resize(e); break; case transitionEndEvent: this._transitionEnd(e); break; } return false; }}
Алгоритм слайдинга
• Вылавливае "start"
• Запоминаем начальную точку
• Следим за событием "move"
• Передвигаем контейнер слайдов
• По событию "end" проверяем нужно ли осуществить полный слайд или вернуть в исходное положение
• Блокируем объект до окончания анимации
Алгоритм слайдшоу
• Создаем вызов для слайдинга
• В момент исполнения вызова проверяем состояние слайдшоу (true/false)o true - moveNext()o false - ничегоo В момент анимации блокируем объект
• При достижении границы переходим на первый слайд
Узкие места
• Потеря состояния начала движения
• Потеря события "transitionEnd" (ipad bug)
• Некорректная обработка смены ориентации
Контакты
Юрий Хрусталев, инженер в [email protected]://about.me/ykhrustalev
Top Related