Do you build things with AMP? Fill out the AMP Developer Survey!
AMP

Создание схемы посадочных мест

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

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

Требуемые AMP-компоненты

Для начала ознакомимся со списком необходимых нам компонентов:

amp-pan-zoom

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

amp-list

amp-list динамически загружает контент из конечной точки CORS JSON и производит его рендеринг с использованием заданного шаблона. Используется для загрузки данных о доступности мест в реальном времени, чтобы пользователи всегда видели актуальную информацию.

amp-bind

amp-bind добавляет на страницу интерактивные возможности. Нужен для отслеживания количества выбранных мест.

amp-selector

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

rect[selected].seat {
  fill: var(--orange-theme);
}

Требования

  1. Чтобы отрисовать схему посадочных мест в виде SVG-изображения, где каждому месту соответствует элемент rect необходима информация о каждом месте: координаты x и y, размеры width и height и, опционально, rx и ry для скругления углов прямоугольников.
  2. Уникальные идентификаторы для каждого места, необходимые для их бронирования.
  3. Размер (ширина и высота) всей схемы посадочных мест для использования в качестве значения атрибута viewbox.

Отрисовка схемы посадочных мест

Для рендеринга схемы используются компоненты amp-list и amp-mustache. После получения данных при помощи вызова amp-list их можно использовать для обхода списка мест:

<svg preserveAspectRatio="xMidYMin slice" viewBox="0 0 {{width}} {{height}}">
{{#seats}}
<rect option="{{id}}" role="button" tabindex="0" class="seat {{unavailable}}" x="{{x}}" y="{{y}}" width="{{width}}" height="{{height}}" rx="{{rx}}" ry="{{ry}}"/>
{{/seats}}
</svg>

Стилизация недоступных мест

В вышеприведенном примере на место {{unavailable}} подставляется значение поля, возвращаемого конечной точкой JSON, благодаря чему к недоступным местам применяется соответствующий стиль. Такой подход не позволяет убирать атрибуты, такие как option="", в случае, если место недоступно, поскольку шаблон нельзя применить к корневому элементу <html> страницы.

Другой, более развернутый подход состоит в том, чтобы продублировать теги:

{{#available }}<rect option="" role="button" tabindex="0" class="seat" x="" y="" width="" height="" rx="" ry=""/>{{/available }}
{{^available}}<rect role="button" tabindex="0" class="seat unavailable" x="" y="" width="" height="" rx="" ry=""/>{{/available }}

Определение размера схемы посадочных мест

Если схема посадочных мест не имеет фиксированного размера, определение размеров компонента amp-list, содержащего схему, — нетривиальная задача. Для работы компонента amp-list требуется либо задать ему фиксированные размеры, либо использовать layout="fill" (чтобы использовать все доступное пространство родительского контейнера). Решить эту проблему можно двумя способами:

  1. Рассчитать доступное пространство на странице с учетом уже известных данных о том, сколько пространства используют другие компоненты, такие как верхний и нижний колонтитулы. Сделать это можно посредством CSS, вычислив значение при помощи выражения calc и присвоив его атрибуту min-height элемента div, внутри которого расположен компонент amp-list .
  2. Использовать flex-макет, если известна высота макета страницы.

Стилизация компонента amp-pan-zoom

При использовании подхода, описанного в предыдущем разделе, для компонента amp-pan-zoom также необходимо использовать layout="fill".

СОВЕТ. Для того чтобы оставить вокруг схемы посадочных мест пустое пространство и сделать его частью области, внутри которой работают жесты изменения масштаба:

  • Оберните SVG-изображение в элемент div
  • Добавьте внутренний отступ

Если вместо того, чтобы обернуть SVG-изображение в элемент div, вы просто добавите к изображению внешний отступ, тогда этот отступ не станет частью области, внутри которой работают жесты изменения масштаба.

Хранение состояния

Идентификаторы (id) мест, выбранных пользователем, можно хранить в переменной при помощи компонента amp-state, используя один из следующих способов:

  • Путем добавления к каждому месту выражения amp-bind, которое будет добавлять места, выбираемые пользователем, в список
  • Путем использования компонента amp-selector с действием on="select:AMP.setState({selectedSeats: event.selectedOptions})", чтобы все выбираемые места добавлялись в список

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

Второй способ также позволяет избежать дублирования выражений amp-bind для каждого места, отображаемого при помощи шаблона.

Окончательная структура HTML

Ниже можно ознакомиться с окончательным HTML-кодом схемы посадочных мест:

<div class="seatmap-container">
  <amp-list layout="fill" src="/json/seats.json" binding="no" items="." single-item noloading>
    <template type="amp-mustache">
      <amp-pan-zoom layout="fill" class="seatmap">
        <amp-selector multiple on="select:AMP.setState({
          selectedSeats: event.selectedOptions
        })" layout="fill">
          <div class="svg-container">
            <svg preserveAspectRatio="xMidYMin slice" viewBox="0 0 {{width}} {{height}}">
            {{#seats}}
              <rect option="{{id}}" role="button"
               tabindex="0" class="seat {{unavailable}}"
              x="{{x}}" y="{{y}}"
              width="{{width}}" height="{{height}}"
              rx="{{rx}}" ry="{{ry}}"/>
            {{/seats}}
            </svg>
          </div>
        </amp-selector>
      </amp-pan-zoom>
    </template>
  </amp-list>
</div>