Статьи

Мобильные веб-приложения: загрузка страниц

Это четвертая выдержка из книги Эрла Кастледина , Майлса Эфтоса и Макса Уилера « Создание мобильных веб-сайтов и приложений для интеллектуальных устройств ». BuildMobile эксклюзивно публикует полную главу из книги, главу о мобильных веб-приложениях, и этот раздел называется «Загрузка страниц».

4. Загрузка страниц

Теперь, когда мы изучили основы работы с сенсорными экранами и набрали пару быстрых побед за счет оптимизации наших ссылок и форм, пришло время закатать рукава и приступить к работе над самым крупным компонентом практически любого мобильного веб-приложения. , Если ваше приложение не очень простое, скорее всего, вам понадобится больше, чем одна страница, и, следовательно, вам нужно подумать о том, как переключаться между страницами. Как мы уже говорили ранее, наш клиент вряд ли будет впечатлен перезагрузками веб-страниц, которые в настоящее время существуют в нашем приложении, поэтому нам нужен способ скрыть цикл запроса / ответа от пользователя. Есть три основных способа сделать это:

  1. Размещение всего на одной странице, а затем скрытие и отображение разделов по мере необходимости
  2. Загрузка новых страниц через Ajax
  3. Включая только полный скелет приложения, а затем вводя данные по мере необходимости

Подход, который вы выберете, будет сильно зависеть от приложения. Мы начнем с рассмотрения первого (и самого простого) подхода и загрузим весь наш контент заранее. Это позволит нам взглянуть на то, как мы можем обрабатывать переходы между различными состояниями. Конечно, для реального приложения, которое для получения актуальной информации зависело от местоположения пользователя, вы должны выбрать один из двух других методов; мы рассмотрим их в конце этой главы.

4.1. Обмен страницами

Если весь наш контент загружен на одной HTML-странице, «страница» с точки зрения нашего приложения больше не является полноценным HTML-документом; это просто DOM-узел, который мы используем в качестве контейнера. Нам нужно выбрать подходящий контейнер и подходящий способ группировки наших страниц, чтобы наши скрипты могли им последовательно управлять.

Мы начнем с создания контейнера div (называемого pages ), который содержит несколько дочерних элементов div которые являются фактическими страницами. За один раз может быть видна только одна страница, поэтому мы дадим этому элементу class current . Этот class будет передан любой активной странице:

 <div id="pages"> <div id="page-spots" class="current"> <!-- Spots Index --> </div> <div id="page-spot"> <!-- Spot Detail --> </div> <div id="page-sightings"> <!-- Add Sighting Form --> </div> <div id="page-stars"> <!-- Stars Index --> </div> <div id="page-star"> <!-- Star Detail --> </div> </div> 

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

 <ul id="tab-bar"> <li> <a href="#spots">Spots</a> </li> <li> <a href="#sightings">Add a sighting</a> </li> <li> <a href="#stars">Stars</a> </li> </ul> 

После этого нам понадобится пара стилей для скрытия и отображения страниц. В нашей разметке каждая страница является дочерним элементом первого уровня основного контейнера #pages , поэтому мы можем опираться на этот факт и использовать дочерний селектор ( > ). Сначала мы скроем все страницы; тогда мы раскроем страницу с current class :

Пример 4.15. stylesheets/transitions.css (отрывок)

 #pages > div { display: none; } #pages > div.current { display: block; } 

Чтобы на самом деле выбрать несколько страниц, нам нужно перехватить щелчки в меню навигации. Мы будем использовать код, который мы написали ранее, для захвата события и предотвращения перехода браузера по ссылке:

Пример 4.16. javascripts/ch4/07-swap.js (отрывок)

 $("#tab-bar a").bind('click', function(e) { e.preventDefault(); // Swap pages! }); 

А вот и хитрость: ссылки указывают на элементы нашей страницы с помощью синтаксиса привязки хеш-символа ( # ), за которым следует идентификатор фрагмента. По совпадению случается, что jQuery использует тот же синтаксис для выбора элементов по id , поэтому мы можем направить свойство hash события click непосредственно в jQuery, чтобы выбрать целевую страницу. Очень подлый

Пример 4.17. javascripts/ch4/07-swap.js (отрывок)

 $("#tab-bar a").bind('click', function(e) { e.preventDefault(); var nextPage = $(e.target.hash); $("#pages .current").removeclass("current"); nextPage.addclass("current"); }); 

Получив целевую страницу, мы можем скрыть текущую страницу, удалив current class и передав его на целевую страницу. Переключение между страницами теперь работает должным образом, но есть небольшая проблема: выбранный значок на панели вкладок не изменяется при переходе на другую страницу. Оглядываясь назад на наш CSS, вы помните, что внешний вид панели вкладок связан с class установленным в содержащем элементе ul ; это class который совпадает с id элемента div текущей страницы. Поэтому все, что нам нужно сделать, это вырезать хеш-символ из нашей строки (используя slice(1) чтобы удалить первый символ), и установить его в качестве класса ul :

Пример 4.18. javascripts/ch4/07-swap.js (отрывок)

 $("#tab-bar a").bind('click', function(e) { e.preventDefault(); var nextPage = $(e.target.hash); $("#pages .current").removeclass("current"); nextPage.addclass("current"); $("#tab-bar").attr("className", e.target.hash.slice(1)); }); 

4.2. Исчезновение с анимацией WebKit

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

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

Прежде чем беспокоиться о переходах, мы должны заложить некоторые основы. Чтобы перевернуть DOM-элементы, мы должны иметь возможность отображать, скрывать и размещать их по желанию:

Пример 4.19. stylesheets/transitions.css (отрывок)

 #pages { position: relative; } #pages > div { display:none; position: absolute; top: 0; left: 0; width: 100%; } 

Позиционируя элементы абсолютно, мы переместили каждую страницу в верхний левый угол, дав нам аккуратную стопку невидимых карт, которые мы теперь можем перемешивать и анимировать. Они не все невидимы, хотя помните, что в нашем HTML мы дали нашей странице по умолчанию class current который устанавливает свойство display для block .

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

  • Настройте анимацию CSS.
  • Запустите анимацию, установив соответствующие классы на страницах.
  • Удалите ненужные классы, когда анимация закончится, и вернитесь в неанимационное состояние.

Давайте начнем с CSS. Есть много подходов, которые вы можете использовать для решения проблемы эмуляции собственных переходов страниц. Мы примем гибкий метод, адаптированный из библиотеки jQTouch. Это модульный подход, в котором мы контролируем переходы, применяя и удаляя соответствующие части анимации для каждой страницы.

Прежде чем мы углубимся в это, кратко ознакомимся с CSS3-анимацией. В настоящее время они поддерживаются только в браузерах WebKit с префиксами -webkit- vendor. Анимация CSS3 состоит из серии ключевых кадров, сгруппированных в виде именованной анимации, созданной с использованием @-webkit-keyframes . Затем мы применяем эту анимацию к элементу, используя свойство -webkit-animation-name. Мы также можем контролировать длительность и замедление анимации с помощью свойств -webkit-animation-duration и -webkit-animation-timer-function соответственно. Если вы новичок в анимации, это, вероятно, звучит более чем немного запутанно для вас сейчас; не берите в голову, как только вы увидите это на практике, это будет намного яснее.

Итак, давайте применим анимацию к нашим элементам. Сначала мы установим функцию синхронизации и продолжительность для наших анимаций. Они определяют, сколько времени займет переход, и как страницы освобождаются от начальной до конечной точки:

Пример 4.20. stylesheets/transitions.css (отрывок)

 .in, .out { -webkit-animation-timing-function: ease-in-out; -webkit-animation-duration: 300ms; } 

Мы поместили эти свойства в общие классы, чтобы их можно было использовать в любой будущей анимации, которую мы создадим.

Далее нам нужно создать наши ключевые кадры. Для начала, давайте просто исчезнем с новой страницы в:

Пример 4.21. stylesheets/transitions.css (отрывок)

 @-webkit-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } 

В вышеприведенном правиле fade-in — это имя анимации, к которому мы будем обращаться всякий раз, когда мы хотим анимировать элемент, используя эти ключевые кадры. Ключевые слова from и to позволяют нам объявлять начальную и конечную точки анимации, и они могут включать любое количество свойств CSS, которые вы хотите анимировать. Если вам нужно больше ключевых кадров между началом и концом, вы можете объявить их в процентах, например:

Пример 4.22. stylesheets/transitions.css (отрывок)

 @-webkit-keyframes fade-in-out { from { opacity: 0; } 50% { opacity: 1; } to { opacity: 0; } } 

С объявленными нашими ключевыми кадрами мы можем объединить их с предыдущими классами направления для создания конечного эффекта. Для нашего затухания мы будем использовать анимацию, которую мы определили выше, а также перевернем z-index на страницах, чтобы убедиться, что правильная страница находится впереди:

Пример 4.23. stylesheets/transitions.css (отрывок)

 .fade.in { -webkit-animation-name: fade-in; z-index: 10; } .fade.out { z-index: 0; } 

Объявляя -webkit-animation-name, мы сообщаем браузеру, что как только элемент соответствует этому селектору, он должен начать именованную анимацию.

С этим CSS мы можем перейти ко второму шагу. Мы начнем с применения нашей анимации к одному элементу навигации, а затем расширим ее, чтобы она работала для всей панели вкладок.

На странице, к которой мы #sightings ( #sightings ), нужно добавить три разных класса: текущий, чтобы сделать страницу видимым, исчезнуть, чтобы добавить нашу анимацию, и применить нашу функцию синхронизации и продолжительность. Страница, с которой мы #spots ( #spots ), видима, поэтому у нее уже будет текущий класс; нам нужно только добавить классы fade и out :

 var fromPage = $("#spots"), toPage = $("#sightings"); $("#tab-sighting a").click(function(){ toPage.addclass("current fade in"); fromPage.addclass("fade out"); }); 

Это дает нам хороший эффект затухания, когда мы нажимаем на вкладку «Добавить прицеливание», но теперь страницы наложены друг на друга. Это потому, что эти имена class все еще там, поэтому страницы теперь имеют current и они оба видимы. Время их удалить! Мы сделаем это путем привязки к событию webkitAnimationEnd , которое срабатывает после завершения перехода. Когда происходит это событие, мы можем удалить все три класса с исходной страницы, а fade и классы с новой страницы. Кроме того, мы должны помнить, что необходимо webkitAnimationEnd события webkitAnimationEnd чтобы не добавлять дополнительные обработчики при следующем исчезновении со страницы:

 var fromPage = $("#spots"), toPage = $("#sightings"); $("#tab-sighting a").click(function(){ toPage .addclass("current fade in") .bind("webkitAnimationEnd", function(){ // More to do, once the animation is done. fromPage.removeclass("current fade out"); toPage .removeclass("fade in") .unbind("webkitAnimationEnd"); }); fromPage.addclass("fade out"); }); 

Вот и мы. Наша страница теперь красиво исчезает; Тем не менее, есть несколько проблем с нашим кодом. Первый структурный. Будет ужасно, если нам придется копировать один и тот же обработчик кликов для каждого набора страниц, на который мы хотим перейти! Чтобы исправить это, мы сделаем функцию с именем transition() которая будет принимать селектор страниц и переходить с текущей страницы на новую.

Пока мы на этом, мы можем заменить наши вызовы bind() и unbind() one() методом jQuery one() . Этот метод выполняет ту же задачу: он связывает событие, а затем отменяет привязку при первом запуске, но выглядит намного чище:

Пример 4.24. javascripts/ch4/08-fade.js (отрывок)

 function transition(toPage) { var toPage = $(toPage), fromPage = $("#pages .current"); toPage .addclass("current fade in") .one("webkitAnimationEnd", function(){ fromPage.removeclass("current fade out"); toPage.removeclass("fade in") }); fromPage.addclass("fade out"); } 

Обобщающие функции

Вы можете шпионить, что мы жестко закодировали текущий селектор страниц внутри нашей функции. Это делает наш код меньше, но уменьшает возможность повторного использования функции. Если вы создаете более крупный каркас, предназначенный для более общего использования, вы, вероятно, также захотите принять fromPage в качестве параметра.

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

Пример 4.25. javascripts/ch4/08-fade.js (отрывок)

 $("#tab-bar a").click(function(e) { e.preventDefault(); var nextPage = $(e.target.hash); transition(nextPage); $("#tab-bar").attr("className", e.target.hash.slice(1)); }); 

Тем не менее, есть еще одна серьезная проблема, и вы ее заметите, если попытаетесь протестировать этот код в браузере, в котором отсутствует поддержка анимации, например в Firefox. Поскольку мы полагаемся на событие webkitAnimationEnd для удаления current class со старой страницы, браузеры, которые не поддерживают анимации и, следовательно, никогда не запускают это событие, никогда не будут скрывать исходную страницу.

Тестирование браузера

Эта ошибка, которая делает приложение полностью непригодным для использования в браузерах, отличных от WebKit, подчеркивает важность тестирования вашего кода в максимально возможном количестве браузеров. Хотя легко предположить, что каждый мобильный браузер содержит обновленную версию WebKit (особенно если у вас есть iPhone или Android), реальный мобильный ландшафт гораздо более разнообразен.

Эту проблему достаточно легко решить. В конце нашей функции transition() мы добавим некоторый код обнаружения объектов, который будет обрабатывать упрощенный обмен страниц при отсутствии анимации:

Пример 4.26. javascripts/ch4/08-fade.js (отрывок)

 function transition(toPage) { ... // For non-animatey browsers if(!("WebKitTransitionEvent" in window)){ toPage.addclass("current"); fromPage.removeclass("current"); return; } } 

С этим кодом наше приложение теперь производит прекрасный переход с постепенным исчезновением в браузерах WebKit, но все же эффективно заменяет страницы в других браузерах.

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

От этих случаев относительно легко защититься. Нам просто нужно убедиться, что наш toPage отличается от нашего fromPage и что в нем еще нет current class . Эта мера защиты идет после объявления переменной и перед любыми манипуляциями с классами:

Пример 4.27. javascripts/ch4/08-fade.js (отрывок)

 function transition(toPage) { var toPage = $(toPage), fromPage = $("#pages .current"); if(toPage.hasclass("current") || toPage === fromPage) { return; }; ... 

4,3. скольжение

Потрясающая работа — наш первый анимационный переход запущен! Давайте перейдем к следующему: чрезвычайно распространенное мобильное взаимодействие для страниц с мастер-деталями, таких как наши списки Spots и Stars. На мобильных устройствах обычная страница подробностей скользит с правой стороны экрана, как если бы она скрывалась там все время. Когда пользователь возвращается на главную страницу, переход работает в обратном порядке, предоставляя пользователю четкую визуальную модель информационной иерархии приложения.

Создать слайд или «толкать» переход очень легко, теперь, когда мы научились исчезать, нам просто нужно обновить наши CSS-анимации. Чтобы выполнить толчок, нам нужно анимировать текущую страницу с экрана влево, одновременно перемещая новую страницу справа. Это немного сложнее, чем просто исчезать, но только просто.

Сначала мы создадим анимацию для удаления текущего экрана. Мы будем использовать свойство -webkit-transform для анимации горизонтального расположения элемента div с помощью команды translateX .

CSS трансформации

Если вы не знакомы с преобразованиями CSS3, не беспокойтесь. Это просто простой способ воздействовать на положение и форму элемента при его отображении. В дополнение к преобразованию translateX, которое мы здесь используем, у вас также есть доступ к функциям translateY (что неудивительно), translate (для перевода по осям X и Y), а также к функциям поворота, перекоса и масштабирования. Полный список и примеры их использования можно найти в модуле CSS 2D-преобразований W3C .

Экран начинается с X = 0, а затем исчезает из поля зрения, переводя на –100%. Это отличный трюк. Если мы переведем на отрицательную величину, экран переместится влево. Переместившись на 100% влево, экран исчез. После того как мы определили ключевые кадры, мы можем затем назначить анимацию селектору .push.out (помните, что наш существующий out class обеспечивает для наших анимаций функцию продолжительности и времени по умолчанию):

Пример 4.28. stylesheets/transitions.css (отрывок)

 /* Screen pushes out to left */ @-webkit-keyframes outToLeft { from { -webkit-transform: translateX(0); } to { -webkit-transform: translateX(-100%); } } .push.out { -webkit-animation-name: outToLeft; } 

Точно так же мы определим анимацию для нового экрана, чтобы вылетать справа, вместо старого:

Пример 4.29. stylesheets/transitions.css (отрывок)

 /* Screen pushes in from the right */ @-webkit-keyframes inFromRight { from { -webkit-transform: translateX(100%); } to { -webkit-transform: translateX(0); } } .push.in { -webkit-animation-name: inFromRight; } 

Для JavaScript мы могли бы перезапустить функцию перехода, которую мы сделали для затухания, вызвав ее, например, transition_push() . Тогда нам просто нужно изменить все случаи fade . И затем снова для flip когда мы хотим реализовать переворачивания, и так далее. Если подумать, было бы лучше передать тип перехода в качестве параметра нашей функции transition() :

Пример 4.30. javascripts/ch4/09-slide.js (отрывок)

 function transition(toPage, type) { ... toPage .addclass("current " + type + " in") .one("webkitAnimationEnd", function(){ fromPage.removeclass("current " + type + " out"); toPage.removeclass(type + " in"); }); fromPage.addclass(type + " out"); } 

Теперь, когда мы создаем CSS-анимацию для нового перехода, они автоматически будут доступны для использования в нашем сценарии. Мы просто передаем новое имя в:

 transition(nextPage, "push"); 

Нам бы хотелось, чтобы push-переход использовался для перехода от списка к странице сведений, поэтому нам нужно добавить несколько новых обработчиков щелчков для элементов списка:

Пример 4.31. javascripts/ch4/09-slide.js (отрывок)

 $("#spots-list li").click(function(e){ e.preventDefault(); transition("#page-spot", "push"); }); $("#stars-list li").click(function(e){ e.preventDefault(); transition("#page-star", "push"); }); 

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

4.4. Возвращаясь назад

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

Но это забегает вперед; Во-первых, нам нужна кнопка Назад. Мы предоставили один из них в заголовке каждой страницы в виде элемента, стилизованного под все кнопки:

Пример 4.32. ch4/10-back.html (отрывок)

 <div class="header"> <h1>Spots</h1> <a href="#" class="back">Back</a> </div> 

И, конечно, у нас должен быть обработчик для выполнения действия при нажатии кнопки:

Пример 4.33. javascripts/ch4/10-back.js (отрывок)

 $("#spot-details .back").click(function(){ // Do something when clicked ... }); 

Далее нам нужно воссоздать все наши CSS-анимации, но в обратном порядке. Мы уже создали inFromRight и outFromLeft ; нам нужно добавить еще два, чтобы дополнить их: inFromLeft и outToRight . Как только они определены, они должны быть присоединены к нашим элементам с помощью селекторов CSS. Мы продолжим модульный подход и будем использовать комбинацию селекторов классов, чтобы использовать наши существующие свойства:

Пример 4.34. stylesheets/transitions.css (отрывок)

 @-webkit-keyframes inFromLeft { from { -webkit-transform: translateX(-100%); } to { -webkit-transform: translateX(0); } } .push.in.reverse { -webkit-animation-name: inFromLeft; } @-webkit-keyframes outToRight { from { -webkit-transform: translateX(0); } to { -webkit-transform: translateX(100%); } } .push.out.reverse { -webkit-animation-name: outToRight; } 

Следующим шагом является добавление нового class в нашу функцию transition() . Мы добавим третий параметр reverse , который принимает логическое значение. Если значение равно false или оно вообще не предоставлено, мы сделаем прямую версию перехода. Если значение равно true , мы добавим reverse class ко всем операциям с классами:

Пример 4.35. javascripts/ch4/10-back.js (отрывок)

 function transition(toPage, type, reverse){ var toPage = $(toPage), fromPage = $("#pages .current"), reverse = reverse ? "reverse" : ""; if(toPage.hasclass("current") || toPage === fromPage) { return; }; toPage .addclass("current " + type + " in " + reverse) .one("webkitAnimationEnd", function(){ fromPage.removeclass("current " + type + " out " + reverse); toPage.removeclass(type + " in " + reverse); }); fromPage.addclass(type + " out " + reverse); } 

Если мы сейчас передадим значение true , новой странице будет назначен атрибут class push in reverse , а старой странице будет назначен push out reverse что вызовет нашу новую обратную анимацию. Чтобы увидеть это в действии, мы добавим вызов для transition() в нашей кнопке Back:

Пример 4.36. javascripts/ch4/10-back.js (отрывок)

 $("#page-spot .back").click(function(e){ e.preventDefault(); transition("#page-spots", "push", true); }); 

4.4.1. Управление историей

Кнопка «Назад» работает, но на данный момент она немного «ручная». Для каждой страницы в нашем приложении нам нужно подключить отдельный обработчик, чтобы вернуться назад. Хуже того, некоторые страницы могут быть доступны по нескольким различным маршрутам, но наше текущее решение возвращает нас только к фиксированной странице. Чтобы справиться с этими проблемами, мы создадим нашу собственную систему истории, которая будет отслеживать каждую страницу, которую посещают пользователи, чтобы, когда они нажимали кнопку «Назад», мы знали, куда их отправлять.

Для начала мы создадим объект visits , который будет содержать массив истории и некоторые методы для управления им:

Пример 4.37. javascripts/ch4/11-history.js (отрывок)

 var visits = { history: [], add: function(page) { this.history.push(page); } }; 

Наш объект visits будет поддерживать стек посещенных страниц в массиве истории. Метод add() берет страницу и добавляет ее в стек (с помощью функции JavaScript push() , которая добавляет элемент в конец массива). Мы будем вызывать этот метод из нашей функции transition() , чтобы каждая страница была добавлена ​​до того, как она будет показана:

Пример 4.38. javascripts/ch4/11-history.js (отрывок)

 function transition(toPage, type, reverse) { var toPage = $(toPage), fromPage = $("#pages .current"), reverse = reverse ? "reverse" : ""; visits.add(toPage); ... } 

Централизация изменений страницы

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

Следующий пункт, о котором нужно подумать — это кнопка «Назад». Мы хотим, чтобы он отображался только при наличии элемента истории, к которому можно вернуться. Мы добавим вспомогательный метод к объекту visits чтобы проверить нас. Поскольку первая страница в истории будет текущей страницей, мы должны проверить, что есть как минимум две страницы:

Пример 4.39. javascripts/ch4/11-history.js (отрывок)

 var visits = { ... hasBack: function() { return this.history.length > 1; } } 

Теперь, когда у нас есть этот помощник, мы можем использовать его в нашем коде перехода, чтобы соответственно отобразить или скрыть кнопку «Назад». Функция jQuery toggle() здесь очень полезна; он принимает логическое значение и либо показывает, либо скрывает элемент на основе этого значения:

Пример 4.40. javascripts/ch4/11-history.js (отрывок)

 function transition(toPage, type, reverse) { var toPage = $(toPage), fromPage = $("#pages .current"); reverse = reverse ? "reverse" : ""; visits.add(toPage); toPage.find(".back").toggle(visits.hasBack()); ... 

Хорошо! Теперь нам нужна некоторая логика в нашем объекте visits для обработки события back. Если есть история, мы вытолкнем первый элемент (текущую страницу) с вершины стека. Нам на самом деле не нужна эта страница, но мы должны удалить ее, чтобы перейти к следующему элементу. Этот элемент является предыдущей страницей, и мы возвращаем ее:

Пример 4.41. javascripts/ch4/11-history.js (отрывок)

 var visits = { ... back: function() { if(!this.hasBack()){ return; } var curPage = this.history.pop(); return this.history.pop(); } } 

Push и Pop

Методы push() и pop() добавляют или удаляют элемент из конца массива соответственно. Оба метода изменяют исходный массив на месте. Метод pop() возвращает удаленный элемент (в нашем примере мы используем его для получения предыдущей страницы), а метод push() возвращает новую длину массива.

Наконец, мы можем подключить все кнопки Назад нашего приложения. Когда выдается запрос на возврат, мы берем предыдущую страницу и, если она существует, мы возвращаемся к ней. Мы просто заменим наш жестко закодированный обработчик кликов универсальным:

Пример 4.42. javascripts/ch4/11-history.js (отрывок)

 $(".back").live("click",function(){ var lastPage = visits.back(); if(lastPage) { transition(lastPage, "push", true); } }); 

Тем не менее, есть проблема: мы никогда не добавляем начальную страницу в стек истории, поэтому нет способа вернуться к ней. Это достаточно легко исправить — мы просто удалим current class из начального div и вызовем нашу функцию перехода, чтобы показать первую страницу при загрузке документа:

Пример 4.43. javascripts/ch4/11-history.js (отрывок)

 $(document).ready(function() { ... transition($("#page-spots"), "show"); }); 

Чтобы подключить этот переход «show», мы будем использовать нашу анимацию затухания, но с очень короткой продолжительностью:

Пример 4.44. stylesheets/transitions.css (отрывок)

 .show.in { -webkit-animation-name: fade-in; -webkit-animation-duration: 10ms; } 

Многие нативные приложения отслеживают историю только между главной страницей и страницей сведений; в нашем случае, например, список звездочек ведет к деталям звезды, а кнопка «Назад» позволяет вам вернуться к списку. Если вы измените области приложения (например, нажав на одну из основных навигационных ссылок), история будет сброшена. Мы можем имитировать это поведение, добавив метод clear() :

Пример 4.45. javascripts/ch4/11-history.js (отрывок)

 var visits = { ... clear: function() { this.history = []; } } 

Это просто стирает наш стек истории. Мы будем вызывать этот метод всякий раз, когда пользователь переходит в новый раздел:

Пример 4.46. javascripts/ch4/11-history.js (отрывок)

 $("#tab-bar a").click(function(e){ // Clear visit history visits.clear(); ... }); 

Это вызывает ощущение «приложения», и в качестве дополнительного бонуса нам не нужно подключать так много событий «Назад»!

4.4.2. Вернуться с аппаратными кнопками

Наша нынешняя система кнопок «Назад» хороша, но она не принимает во внимание тот факт, что мобильное устройство часто будет иметь собственную кнопку «Назад» — либо в виде физической кнопки, либо программной кнопки в браузере. На самом деле, если пользователь нажимает кнопку «Назад» на своем устройстве после нажатия нескольких внутренних ссылок в нашем приложении, браузер просто переходит на последнюю загруженную HTML-страницу или полностью завершает работу. Это определенно разрушит иллюзию наших пользователей о нашем сайте как о полноценном приложении, поэтому давайте посмотрим, сможем ли мы найти решение этой проблемы.

Что нам действительно нужно, так это уметь слушать и изменять встроенную историю браузера, а не наш собственный стек страниц. Чтобы добиться этого, API истории HTML5 здесь, чтобы помочь нам.

History API позволяет нам добавлять страницы в стек истории, а также перемещаться вперед и назад между страницами в стеке. Для добавления страниц мы используем метод window.history.pushState() . Этот метод аналогичен нашему visits.add() методу visits.add() , но принимает три параметра: любые произвольные данные, которые мы хотим запомнить о странице; заголовок страницы (если применимо); и URL-адрес страницы.

Мы собираемся создать метод changePage() который сочетает в себе как добавление страницы с использованием API истории, так и обычный переход. Мы будем отслеживать переход внутри истории, поэтому, когда пользователь нажимает назад, мы можем посмотреть на переход и сделать обратное. Это лучше, чем в нашей предыдущей версии, где для обратного перехода мы бы только делали обратный слайд.

Вот первый шаг при написании этого нового метода:

Пример 4.47. javascripts/ch4/12-hardware-back.js (отрывок)

 function changePage(page, type, reverse) { window.history.pushState({ page: page, transition: type, reverse: !!reverse }, "", page); // Do the real transition transition(page, type, reverse) } 

Первый параметр для pushState() называется объектом состояния . Вы можете использовать его для передачи любого объема данных между страницами в вашем приложении в виде объекта JavaScript. В нашем случае мы передаем страницу, тип перехода, и является ли это обратным переходом.

Чтобы использовать эту новую функцию в нашем коде, мы просто меняем все вхождения transition() на changePage() , например:

 changePage("#page-spots", "show"); 

Теперь, когда пользователь перемещается по нашему приложению, история сохраняется. Если они нажимают физическую кнопку «Назад», вы можете увидеть историю страниц в строке URL, но ничего особенного не происходит. Этого и следовало ожидать: мы только что поместили ряд строк в стек истории, но мы не сказали приложению, как вернуться к ним.

Событие window.onPopState вызывается всякий раз, когда происходит событие реальной загрузки страницы или когда пользователь нажимает кнопку Назад или Вперед. Событие получает объект с именем state, который содержит объект состояния, который мы помещаем туда с помощью pushStack() (если состояние не определено, это означает, что событие было pushStack() загрузкой страницы, а не изменением истории, так что это не имеет значения) , Давайте создадим обработчик для этого события:

Пример 4.48. javascripts/ch4/12-hardware-back.js (отрывок)

 window.addEventListener("popstate", function(event) { if(!event.state){ return; } // Transition back - but in reverse. transition( event.state.page, event.state.transition, !event.state.reverse ); }, false); 

Где jQuery?

Для этого примера мы просто использовали стандартный приемник событий DOM, а не метод jQuery bind() . Это только для ясности для события popstate . Если мы связываем его, используя $(window).bind("popstate", ...) , объект события, переданный $(window).bind("popstate", ...) вызову, будет объектом события jQuery, а не родным событием popstate браузера. Обычно это то, что нам нужно, но оболочка событий jQuery не включает в себя свойства из History API, поэтому нам нужно вызвать event.originalEvent чтобы получить событие браузера. В этом нет ничего плохого — вы можете свободно использовать любой подход, который вам кажется наиболее простым.

Фантастический! Кажется, что анимации работают в обратном порядке, когда мы нажимаем кнопку «Назад» в браузере … или они? Если вы посмотрите внимательно, вы можете заметить что-то странное. Иногда мы видим «скользящие» переходы, которые должны быть простыми «показывать» переходы, и наоборот. В чем дело?

На самом деле, здесь у нас возникает ошибка: при движении назад мы не хотим использовать переход страницы, на которую мы переходим, а страницу, с которой мы переходим. К сожалению, это означает, что нам нужно вызвать pushState() при следующем переходе. Но мы не можем видеть будущее … как мы можем знать, какой переход произойдет дальше?

К счастью, History API предоставляет нам другой метод replaceState (). Он почти идентичен pushState (), но вместо добавления в стек он заменяет текущую (самую верхнюю) страницу в стеке. Чтобы решить нашу проблему, мы будем использовать детали предыдущего pushState() ; затем, прежде чем добавить следующий элемент, мы будем использовать replaceState() чтобы обновить страницу с помощью перехода «следующий»:

Пример 4.49. javascripts/ch4/12-hardware-back.js (отрывок)

 var pageState = {}; function changePage(page, type, reverse) { // Store the transition with the state if(pageState.url){ // Update the previous transition to be the NEXT transition pageState.state.transition = type; window.history.replaceState( pageState.state, pageState.title, pageState.url); } // Keep the state details for next time! pageState = { state: { page: page, transition: type, reverse: reverse }, title: "", url: page } window.history.pushState(pageState.state, pageState.title, pageState.url); // Do the real transition transition(page, type, reverse) } 

Мы также должны обновить нашу переменную pageState когда пользователь возвращается; в противном случае он не синхронизировался бы с историей браузера, и наши replaceState() итоге вставили бы поддельные записи в историю:

Пример 4.50. javascripts/ch4/12-hardware-back.js (отрывок)

 window.addEventListener("popstate", function(event) { if(!event.state){ return; } // Transition back - but in reverse. transition( event.state.page, event.state.transition, !event.state.reverse ); pageState = { state: { page: event.state.page, transition: event.state.transition, reverse: event.state.reverse }, title: "", url: event.state.page } }, false); 

Вот и мы. Физическая кнопка Назад теперь работает прекрасно. Но как насчет нашего пользовательского приложения Кнопка «Назад»? Мы можем связать это с тем, чтобы вызвать событие истории, и, следовательно, связать весь этот джаз History API, который мы только что написали, используя быстрый вызов history.back() :

Пример 4.51. javascripts/ch4/12-hardware-back.js (отрывок)

 $(".back").live("click",function(e){ window.history.back(); }); 

Теперь кнопка «Назад» нашего приложения работает точно так же, как браузер или физическая кнопка «Назад». Вы также можете подключить кнопку «Вперед» и вызвать ее с помощью history.forward() или перейти к определенной странице в стеке с помощью history.go(-3) . Возможно, вы заметили, что мы немного потихоньку относились к обработке кнопок «Вперед». Для этого есть две причины: во-первых, в большинстве мобильных браузеров отсутствует кнопка «Вперед», а во-вторых, невозможно узнать, произошло ли событие popstate из-за кнопки «Назад» или «Вперед».

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

Сборка мобильной книги

Вы можете приобрести книгу « Создание мобильных веб-сайтов и приложений для интеллектуальных устройств » в Sitepoint. Прочитайте всю главу 4. Мобильные веб-приложения , бесплатно здесь, в BuildMobile, бесплатно в следующих разделах.