Передняя часть Средиземья

Пошаговое руководство по разработке для нескольких устройств

В нашей первой статье о разработке Chrome Experiment A Journey Through Middle-earth мы сосредоточились на разработке WebGL для мобильных устройств. В этой статье мы обсуждаем проблемы, проблемы и решения, с которыми мы столкнулись при создании остальной части интерфейса HTML5.

Три версии одного и того же сайта

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

Весь проект основан на очень «кинематографическом» стиле, в котором мы с точки зрения дизайна хотели сохранить впечатление в фиксированном пейзажно-ориентированном кадре, чтобы сохранить волшебство фильма. Поскольку значительная часть проекта состоит из интерактивных мини-игр, было бы неразумно позволять им выходить за пределы кадра.

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

Иглз только что высадили нас на целевой странице.
Иглз только что высадили нас на целевой странице.

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

Мы используем данные пользовательского агента для обнаружения мобильных устройств и тест размера области просмотра, чтобы ориентироваться на планшеты среди них (645 пикселей и выше). Фактически каждый отдельный режим может отображать все разрешения, поскольку макет основан на медиа-запросах или относительном/процентном позиционировании с помощью JavaScript.

Поскольку дизайн в этом случае не основан на сетках или правилах и совершенно уникален в разных разделах, то, какие точки останова или стили использовать, действительно зависит от конкретного элемента и сценария. Не раз случалось, что мы настроили идеальный макет с красивыми sass-миксинами и медиа-запросами, а затем нам нужно было добавить эффект, основанный на положении мыши или динамических объектах, и в итоге нам приходилось переписывать все на JavaScript.

Мы также добавляем класс с текущим режимом в тег head, чтобы мы могли использовать эту информацию в наших стилях, как в этом примере (в SCSS):

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

Мы поддерживаем все размеры вплоть до 360x320, что было довольно сложно при создании захватывающего веб-интерфейса. На настольных компьютерах у нас есть минимальный размер, прежде чем мы будем показывать полосы прокрутки, потому что мы хотим, чтобы вы, если возможно, просматривали сайт в большем окне просмотра. На мобильных устройствах мы решили разрешить как альбомный, так и портретный режим, вплоть до интерактивных возможностей, когда мы просим вас перевернуть устройство в альбомный режим. Аргументом против этого было то, что портрет не так захватывает, как пейзаж; но сайт довольно хорошо масштабировался, поэтому мы сохранили его.

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

Во время разработки мы часто использовали инструмент эмулятора в DevTools, особенно в Chrome Canary, который имеет новые улучшенные функции и множество предустановок. Это хороший способ быстрой проверки проекта. Нам по-прежнему нужно было регулярно тестировать на реальных устройствах. Одна из причин заключалась в том, что сайт адаптируется к полноэкранному режиму. Страницы с вертикальной прокруткой в ​​большинстве случаев скрывают пользовательский интерфейс браузера при прокрутке (в настоящее время у Safari на iOS7 есть проблемы с этим), но нам пришлось подогнать все, независимо от этого. Мы также использовали предустановку в эмуляторе и изменили настройку размера экрана, чтобы имитировать потерю доступного пространства. Тестирование на реальных устройствах также важно для мониторинга потребления памяти и производительности.

Управление состоянием

После целевой страницы мы попадаем на карту Средиземья. Вы заметили, что URL-адрес изменился? Сайт представляет собой одностраничное приложение, использующее History API для управления маршрутизацией .

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

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

Демонстрация локаций

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

Зал Трандуила
Хронология Зала Трандуила

График времени

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

Модули и компоненты поведения

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

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

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

Содержимое текстового модуля поддерживается перетаскиванием с помощью плагина TweenMax Draggable . Для вертикальной прокрутки также можно использовать колесо прокрутки или пролистывание двумя пальцами. Обратите внимание на плагин throw-props-plug , который добавляет физику в стиле броска, когда вы проводите пальцем по экрану и отпускаете его.

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

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

Последовательности изображений

Самым сложным из модулей с точки зрения производительности и размера загрузки является последовательность изображений. На эту тему есть что почитать. На мобильных устройствах и планшетах мы заменяем это изображение неподвижным изображением. Слишком много данных для декодирования и хранения в памяти, если мы хотим достойного качества на мобильных устройствах. Мы попробовали несколько альтернативных решений; сначала использовал фоновое изображение и таблицу спрайтов, но это приводило к проблемам с памятью и задержкам, когда графическому процессору приходилось переключаться между таблицами спрайтов. Затем мы попробовали поменять местами элементы img, но это тоже было слишком медленно. Рисование кадра из спрайт-листа на холст оказалось наиболее производительным, поэтому мы начали его оптимизировать. Чтобы сэкономить время вычислений для каждого кадра, данные изображения, записываемые на холст, предварительно обрабатываются через временный холст и сохраняются с помощью putImageData() в массив, декодируются и готовы к использованию. Исходный спрайт-лист затем можно утилизировать, и мы сохраняем в памяти только минимально необходимый объем данных. Возможно, на самом деле хранить некодированные изображения меньше, но мы получаем лучшую производительность при очистке последовательности таким образом. Рамки довольно маленькие, всего 640х400, но они будут видны только во время прокрутки. Когда вы останавливаетесь, изображение в высоком разрешении загружается и быстро исчезает.

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

Спрайт-листы генерируются с помощью Imagemagick . Вот простой пример на GitHub , который показывает, как создать таблицу спрайтов всех изображений внутри папки.

Анимация модулей

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

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

Производительность страницы

Переход от работающего прототипа к релизной версии без ошибок означает переход от догадок к знанию того, что происходит в браузере. Здесь Chrome DevTools — ваш лучший друг.

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

Мне нравится использовать TweenMax от Greensock для анимации свойств, преобразований и CSS. Думайте контейнерами, визуализируйте свою структуру по мере добавления новых слоев. Имейте в виду, что существующие преобразования могут быть перезаписаны новыми преобразованиями. TranslateZ(0), который вызывал аппаратное ускорение в вашем классе CSS, заменяется 2D-матрицей, если вы анимируете только 2D-значения. Чтобы в таких случаях сохранить слой в режиме ускорения, используйте свойство «force3D:true» в анимации движения, чтобы создать 3D-матрицу вместо 2D-матрицы. Об этом легко забыть, когда вы комбинируете анимацию CSS и JavaScript для установки стилей.

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

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

Выход из раздела с неработающей функцией удаления.
Выход из раздела с неработающей функцией удаления.
Намного лучше!
Намного лучше!

Чтобы найти утечку, в DevTools был довольно простой рабочий процесс: запись временной шкалы и создание снимков кучи. Проще, если есть определенные объекты, например трехмерная геометрия или определенная библиотека, которые можно отфильтровать. В приведенном выше примере оказалось, что 3D-сцена все еще существует, а массив, в котором хранится геометрия, не был очищен. Если вам сложно определить местонахождение объекта, есть полезная функция, позволяющая просмотреть это, называемое сохранением путей . Просто щелкните объект, который хотите проверить, на снимке кучи, и вы получите информацию на панели ниже. Использование хорошей структуры с меньшими объектами помогает при поиске ссылок.

На сцену была ссылка в EffectComposer.
На сцену была ссылка в EffectComposer.

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

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

Полноэкранный

Если доступно, у вас есть возможность перевести сайт в полноэкранный режим в меню через полноэкранный API. Но на устройствах также есть решение браузеров перевести его в полноэкранный режим. Раньше в Safari на iOS был хак, позволяющий вам это контролировать, но он больше недоступен, поэтому вам нужно подготовить свой дизайн для работы без него при создании страницы без прокрутки. Вероятно, мы можем ожидать обновлений по этому поводу в будущих обновлениях, поскольку из-за этого сломалось множество веб-приложений.

Ресурсы

Анимированные инструкции к опытам.
Анимированные инструкции к опытам.

На сайте имеется множество различных типов ресурсов: мы используем изображения (PNG и JPEG), SVG (встроенные и фоновые), таблицы спрайтов (PNG), пользовательские шрифты значков и анимацию Adobe Edge. Мы используем PNG для ресурсов и анимации (спрайт-таблиц), где элемент не может быть векторным, в противном случае мы стараемся использовать SVG как можно чаще.

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

  • Маленький размер файла.
  • Мы можем анимировать каждую часть отдельно (идеально подходит для продвинутой анимации). В качестве примера мы скрываем «подзаголовок» логотипа Хоббита (пустошь Смауга), когда он уменьшен.
  • Его можно внедрить как HTML-тег SVG или использовать в качестве фонового изображения без дополнительной загрузки (оно загружается одновременно с HTML-страницей).

Шрифты-иконки имеют те же преимущества, что и SVG, когда дело касается масштабируемости, и используются вместо SVG для небольших элементов, таких как значки, для которых нам нужно только иметь возможность изменять цвет (при наведении, активный и т. д.). Значки также очень легко использовать повторно, вам просто нужно установить свойство CSS «content» элемента.

Анимации

В некоторых случаях анимация SVG-элементов с помощью кода может занять очень много времени, особенно если анимацию необходимо сильно изменить в процессе проектирования. Чтобы улучшить рабочий процесс между дизайнерами и разработчиками, мы используем Adobe Edge для некоторых анимаций (инструкции перед играми). Рабочий процесс анимации очень близок к Flash, и это помогло команде, но есть несколько недостатков, особенно при интеграции анимации Edge в наш процесс загрузки ресурсов, поскольку он поставляется со своими собственными загрузчиками и логикой реализации.

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

Заключение

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