Улучшение пользовательского потока через переходы между страницами
Опубликовано: 2022-03-10Переходы между страницами могут улучшить впечатление, сохраняя (или даже улучшая) контекст пользователя, удерживая его внимание и обеспечивая визуальную непрерывность и положительную обратную связь. В то же время переходы между страницами могут быть эстетически приятными и забавными, а также усиливать брендинг, если они выполнены правильно.

В этой статье мы шаг за шагом создадим переход между страницами. Мы также поговорим о плюсах и минусах этой техники и о том, как довести ее до предела.
Примеры
Многие мобильные приложения хорошо используют переходы между представлениями. В приведенном ниже примере, который следует рекомендациям Google по дизайну материалов, мы видим, как анимация передает иерархические и пространственные отношения между страницами.
Почему бы нам не использовать тот же подход с нашими веб-сайтами? Почему мы согласны с тем, что пользователь чувствует, что его телепортирует каждый раз, когда меняется страница?
Как переходить между веб-страницами
SPA-фреймворки
Прежде чем запачкать руки, я должен сказать кое-что о фреймворках одностраничных приложений (SPA). Если вы используете платформу SPA (такую как AngularJS, Backbone.js или Ember), то создание переходов между страницами будет намного проще, поскольку вся маршрутизация уже обрабатывается JavaScript. Пожалуйста, обратитесь к соответствующей документации, чтобы узнать, как переходить страницы с помощью выбранной вами платформы, потому что, вероятно, есть несколько хороших примеров и руководств.
Неправильный путь
Моя первая попытка создать переход между страницами выглядела примерно так:
document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });
Концепция проста: используйте одну анимацию, когда пользователь покидает страницу, и другую анимацию, когда загружается новая страница.
Однако вскоре я обнаружил, что это решение имеет некоторые ограничения:
- Мы не знаем, сколько времени потребуется для загрузки следующей страницы, поэтому анимация может выглядеть не плавной.
- Мы не можем создавать переходы, объединяющие содержимое предыдущей и следующей страниц.
На самом деле единственный способ добиться плавного и плавного перехода — полностью контролировать процесс смены страницы и, следовательно, вообще не менять страницу . Таким образом, мы должны изменить наш подход к проблеме.
Правильный путь
Давайте рассмотрим шаги, необходимые для правильного создания простого плавного перехода между страницами. Это включает в себя так называемую pushState
AJAX (или PJAX), которая, по сути, превратит наш веб-сайт в своего рода одностраничный веб-сайт.
Эта техника не только обеспечивает плавные и приятные переходы, но мы также извлекаем пользу из других преимуществ, которые мы подробно рассмотрим позже в этой статье.
Предотвращение поведения ссылки по умолчанию
Первый шаг — создать прослушиватель событий click
для всех ссылок, чтобы браузер не выполнял поведение по умолчанию и настраивал способ обработки изменений страницы.
// Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });
Этот метод добавления прослушивателя событий к родительскому элементу вместо добавления его к каждому конкретному узлу называется делегированием событий, и это возможно благодаря характеру всплывающих событий HTML DOM API.
Получить страницу
Теперь, когда мы прервали работу браузера при попытке изменить страницу, мы можем вручную получить эту страницу с помощью Fetch API. Давайте посмотрим на следующую функцию, которая извлекает HTML-контент страницы по ее URL-адресу.
function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }
Для браузеров, не поддерживающих Fetch API, рассмотрите возможность добавления полифилла или использования старого доброго XMLHttpRequest
.
Изменить текущий URL-адрес
HTML5 имеет фантастический API под названием pushState
, который позволяет веб-сайтам получать доступ и изменять историю браузера без загрузки каких-либо страниц. Ниже мы используем его, чтобы изменить текущий URL-адрес на URL-адрес следующей страницы. Обратите внимание, что это модификация нашего ранее объявленного обработчика события щелчка привязки.
if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }
Как вы могли заметить, мы также добавили вызов функции с именем changePage
, которую мы вскоре подробно рассмотрим. Та же функция будет вызываться в событии popstate
, которое запускается при изменении активной записи истории браузера (например, когда пользователь нажимает кнопку «Назад» в своем браузере):
window.addEventListener('popstate', changePage);
При всем этом мы по сути строим очень примитивную систему маршрутизации, в которой у нас есть активный и пассивный режимы.
Наш активный режим используется, когда пользователь нажимает на ссылку, и мы меняем URL-адрес с помощью pushState
, в то время как пассивный режим используется, когда URL-адрес изменяется, и мы получаем уведомление о событии popstate
. В любом случае мы собираемся вызвать changePage
, который позаботится о чтении нового URL-адреса и загрузке соответствующей страницы.
Разберите и добавьте новый контент
Как правило, страницы, по которым осуществляется навигация, имеют общие элементы, такие как верхний и footer
header
Предположим, мы используем следующую структуру DOM на всех наших страницах (которая на самом деле является структурой самого Smashing Magazine):
var main = document.querySelector('main'); function changePage() { // Note, the URL has already been changed var url = window.location.href; loadPage(url).then(function(responseText) { var wrapper = document.createElement('div'); wrapper.innerHTML = responseText; var oldContent = document.querySelector('.cc'); var newContent = wrapper.querySelector('.cc'); main.appendChild(newContent); animate(oldContent, newContent); }); }
Анимация!
Когда пользователь щелкает ссылку, функция changePage
извлекает HTML-код этой страницы, затем извлекает контейнер cc
и добавляет его к main
элементу. На данный момент у нас есть два контейнера cc
на нашей странице, первый принадлежит предыдущей странице, а второй — следующей странице.

Следующая функция, animate
, заботится о перекрестном затухании двух контейнеров, накладывая их друг на друга, затухая старый, затухая новый и удаляя старый контейнер. В этом примере я использую API веб-анимации для создания анимации затухания, но, конечно, вы можете использовать любую технику или библиотеку по своему усмотрению.
function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }
Окончательный код доступен на GitHub.
И это основы перехода веб-страниц!
Предостережения и ограничения
Маленький пример, который мы только что создали, далек от совершенства. На самом деле, мы до сих пор не учли несколько вещей:
- Убедитесь, что мы влияем на правильные ссылки.
Прежде чем изменить поведение ссылки, мы должны добавить проверку, чтобы убедиться, что она должна быть изменена. Например, мы должны игнорировать все ссылки сtarget="_blank"
(что открывает страницу в новой вкладке), все ссылки на внешние домены и некоторые другие особые случаи, такие какControl/Command + click
(что также открывает страницу в новой вкладке). новую вкладку). - Обновите элементы за пределами основного контейнера содержимого.
В настоящее время при изменении страницы все элементы за пределами контейнераcc
остаются прежними. Однако некоторые из этих элементов нужно будет изменить (что теперь можно было сделать только вручную), включаяtitle
документа, элемент меню сactive
классом и, возможно, многие другие в зависимости от веб-сайта. - Управляйте жизненным циклом JavaScript.
Наша страница теперь ведет себя как SPA, в котором браузер сам не меняет страницы. Итак, нам нужно вручную позаботиться о жизненном цикле JavaScript — например, привязать и отвязать определенные события, переоценить плагины и включить полифиллы и сторонний код.
Поддержка браузера
Единственным требованием для этого режима навигации, который мы реализуем, является pushState
API, который доступен во всех современных браузерах. Эта техника полностью работает как прогрессивное улучшение . Страницы по-прежнему обслуживаются и доступны в обычном режиме, и веб-сайт будет продолжать нормально работать при отключенном JavaScript.
Если вы используете инфраструктуру SPA, рассмотрите вместо этого использование навигации PJAX, просто чтобы обеспечить быструю навигацию. При этом вы получаете устаревшую поддержку и создаете более оптимизированный для SEO веб-сайт.
Идем еще дальше
Мы можем продолжать раздвигать границы этой техники, оптимизируя некоторые ее аспекты. Следующие несколько приемов ускорят навигацию, значительно улучшая пользовательский опыт.
Использование кэша
Немного изменив нашу функцию loadPage
, мы можем добавить простой кеш, который гарантирует, что уже посещенные страницы не будут перезагружены.
var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }
Как вы могли догадаться, мы можем использовать более постоянный кеш с Cache API или другой кеш постоянного хранилища на стороне клиента (например, IndexedDB).
Анимация текущей страницы
Наш эффект плавного затухания требует, чтобы следующая страница была загружена и готова до завершения перехода. С другим эффектом мы можем захотеть начать анимацию старой страницы, как только пользователь щелкнет ссылку, что даст пользователю немедленную обратную связь, что значительно повысит воспринимаемую производительность.
Используя промисы, справиться с такой ситуацией становится очень просто. Метод .all
создает новое обещание, которое разрешается, как только разрешаются все обещания, включенные в качестве аргументов.
// As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …
Предварительная загрузка следующей страницы
Используя только навигацию PJAX, изменения страницы обычно почти в два раза быстрее , чем навигация по умолчанию, потому что браузеру не нужно анализировать и оценивать какие-либо сценарии или стили на новой странице.
Однако мы можем пойти еще дальше, начав предварительно загружать следующую страницу, когда пользователь наводит курсор или начинает касаться ссылки.
Как видите, задержка обычно составляет от 200 до 300 миллисекунд при наведении курсора и щелчке мышью. Это мертвое время , и его обычно достаточно для загрузки следующей страницы.
При этом делайте предварительную выборку с умом, потому что она может легко стать узким местом. Например, если у вас есть длинный список ссылок, и пользователь прокручивает его, этот метод будет выполнять предварительную выборку всех страниц, поскольку ссылки проходят под мышью.
Еще один фактор, который мы можем обнаружить и принять во внимание при принятии решения о предварительной выборке, — это скорость подключения пользователя. (Возможно, в будущем это станет возможным благодаря API сетевой информации.)
Частичный вывод
В нашей функции loadPage
мы извлекаем весь HTML-документ, но на самом деле нам нужен только контейнер cc
. Если мы используем серверный язык, мы можем определить, исходит ли запрос от определенного пользовательского вызова AJAX, и, если это так, вывести только тот контейнер, который ему нужен. Используя Headers API, мы можем отправить собственный HTTP-заголовок в нашем запросе на выборку.
function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }
Затем на стороне сервера (в данном случае с использованием PHP) мы можем определить, существует ли наш собственный заголовок, прежде чем выводить только необходимый контейнер:
if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }
Это уменьшит размер HTTP-сообщения, а также снизит нагрузку на сервер.
Подведение итогов
После реализации этой техники в нескольких проектах я понял, что повторно используемая библиотека будет очень полезна. Это сэкономило бы мне время на его реализацию каждый раз, освободив меня, чтобы сосредоточиться на самих эффектах перехода.
Так родилась Barba.js, крошечная библиотека (минифицированная по 4 КБ и сжатая в формате gZip), которая абстрагируется от всей этой сложности и предоставляет хороший, чистый и простой API для использования разработчиками. Он также учитывает представления и поставляется с многократно используемыми переходами, кэшированием, предварительной выборкой и событиями. Он с открытым исходным кодом и доступен на GitHub.
Заключение
Теперь мы увидели, как создать эффект плавного затухания, а также плюсы и минусы использования навигации PJAX для эффективного преобразования нашего веб-сайта в SPA. Помимо преимуществ самого перехода, мы также увидели, как реализовать простые механизмы кэширования и предварительной выборки для ускорения загрузки новых страниц.
Вся эта статья основана на моем личном опыте и на том, чему я научился при реализации переходов между страницами в проектах, над которыми я работал. Если у вас есть какие-либо вопросы, не стесняйтесь оставлять комментарии или обращаться ко мне в Твиттере — моя информация ниже!
Дальнейшее чтение на SmashingMag:
- Умные переходы в дизайне взаимодействия с пользователем
- Проектирование при переходе к миру с несколькими устройствами
- Предоставление собственного опыта работы с веб-технологиями