Этап 4: Модальное окно

Free Preview
Продолжительность:

Поиск и фильтрация работают. Теперь добавим модальное окно — при клике на карточку пользователь видит полную информацию о месте.

Требования

HTML-структура модального окна

  • Добавьте в index.html один элемент модального окна. Он должен существовать в DOM с самого начала, но быть скрыт.
  • Структура внутри: крупное изображение, бейдж категории, название, адрес, рейтинг, полное описание, кнопка закрытия.
  • За пределами внутреннего блока — полупрозрачный backdrop, клик по которому закрывает модалку.

Рекомендуемая HTML-структура. Внешний элемент — сам backdrop (полупрозрачный оверлей на весь экран). Внутри него — блок с контентом. Это удобно: клик вне блока с контентом будет кликом по backdrop:

<div class="modal" id="modal">
  <div class="modal__content">
    <button class="modal__close" id="modalClose">×</button>
    <img class="modal__image" id="modalImage" src="" alt="">
    <div class="modal__body">
      <span class="modal__category" id="modalCategory"></span>
      <h2 class="modal__title" id="modalTitle"></h2>
      <p class="modal__address" id="modalAddress"></p>
      <div class="modal__rating" id="modalRating"></div>
      <p class="modal__description" id="modalDescription"></p>
    </div>
  </div>
</div>
html

CSS для модалки: position: fixed; inset: 0 — фиксирует оверлей на весь экран. Скрытое состояние удобнее всего реализовать через opacity: 0; pointer-events: none, а видимое — через класс modal--open с opacity: 1; pointer-events: auto. Это позволяет анимировать через transition: opacity.

Открытие и закрытие

  • Клик по карточке открывает модалку с данными именно этой достопримечательности.
  • Используйте делегирование: один обработчик на контейнере сетки, а не по обработчику на каждой карточке.
  • Кнопка закрытия (×) закрывает модалку.
  • Клик по backdrop закрывает модалку. Клик по содержимому модалки — нет.
  • Нажатие Escape закрывает модалку.
  • Пока модалка открыта, страница под ней не прокручивается (document.body получает нужный стиль).
  • После закрытия прокрутка восстанавливается.

Как найти нужный объект при делегировании. Карточка имеет data-id. При клике найдите ближайшую карточку через closest, возьмите id и найдите объект в state.attractions:

grid.addEventListener('click', (e) => {
  const card = e.target.closest('.card');
  if (!card) return;

  const id = Number(card.dataset.id);
  const attraction = state.attractions.find(a => a.id === id);
  openModal(attraction);
});
js

Как отличить клик по backdrop от клика по контенту. Слушайте клик на самом модальном оверлее и проверяйте e.target:

modal.addEventListener('click', (e) => {
  if (e.target === modal) closeModal(); // кликнули именно по backdrop, не по содержимому
});
js

Блокировка прокрутки. Стандартный приём — добавить overflow: hidden на body:

function openModal(attraction) {
  document.body.style.overflow = 'hidden';
  modal.classList.add('modal--open');
  // заполнить поля...
}

function closeModal() {
  document.body.style.overflow = '';
  modal.classList.remove('modal--open');
}
js

Анимация

  • Открытие и закрытие должны быть анимированы: плавное появление backdrop и самого окна.
  • Реализуйте через CSS-классы и transition — не через JS-анимацию.

CSS-подход к анимации. Базовое состояние — модалка невидима и некликабельна. Класс modal--open делает её видимой. Переход прописывается в базовом состоянии:

.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal--open {
  opacity: 1;
  pointer-events: auto;
}

.modal__content {
  transform: translateY(20px);
  transition: transform 0.25s ease;
}

.modal--open .modal__content {
  transform: translateY(0);
}
css

Наполнение данными

  • При открытии модалки заполняйте её содержимое данными из объекта достопримечательности.
  • Храните ссылку на текущий открытый объект в state — она пригодится в следующем этапе (для кнопки избранного внутри модалки).

Заполнение полей модалки. Прямое обращение по id удобнее querySelector внутри функции:

function fillModal(attraction) {
  document.getElementById('modalImage').src = attraction.image;
  document.getElementById('modalImage').alt = attraction.name;
  document.getElementById('modalTitle').textContent = attraction.name;
  document.getElementById('modalCategory').textContent = attraction.category;
  document.getElementById('modalAddress').textContent = attraction.address;
  document.getElementById('modalRating').textContent = `★ ${attraction.rating}`;
  document.getElementById('modalDescription').textContent = attraction.description;
}
js

Продвинутое задание — URL hash

Реализуйте навигацию через хэш URL: когда модалка открывается, добавляйте в адресную строку #attraction-1 (где 1 — id места). При закрытии убирайте хэш. При загрузке страницы проверяйте хэш и если он есть — автоматически открывайте нужную модалку.

Это позволяет делиться прямой ссылкой на конкретное место. Изучите:

Ожидаемый результат

  • клик по карточке открывает модалку с данными этой достопримечательности
  • в модалке есть изображение, категория, название, адрес, рейтинг, описание
  • модалка закрывается по кнопке ×, клику по backdrop и клавише Escape
  • пока модалка открыта, страница не прокручивается
  • открытие и закрытие анимированы
  • клик внутри модалки не закрывает её