Javascript for mobile devices

Preview:

DESCRIPTION

 

Citation preview

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

Представлюсь

Юрий Хрусталев, инженер в DataArtyuri.khrustalev@dataart.comhttp://about.me/ykhrustalev

http://dataart.com

Проект

• Задача 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)

• Некорректная обработка смены ориентации

Контакты

Юрий Хрусталев, инженер в DataArtyuri.khrustalev@dataart.comhttp://about.me/ykhrustalev

Recommended