Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы создаём интерактивную галерею с модальным просмотром и навигацией с помощью клавиатуры.
Вместо булева значения (true/false) мы храним индекс изображения. Значение null означает "модалка закрыта", число (0, 1, 2...) указывает, какое изображение открыто в массиве. Это позволяет одновременно контролировать видимость модалки и знать, какой контент показывать.
При клике на миниатюру мы сохраняем её индекс из map. Индекс - это позиция элемента в массиве (0 для первого, 1 для второго и т.д.). Он позволяет потом обратиться к конкретному изображению через IMAGES[index].
Перед переходом на следующее изображение проверяем, что мы не на последнем. IMAGES.length - 1 - это индекс последнего элемента (массивы начинаются с 0). Если сейчас индекс 8, а длина массива 9, последний элемент имеет индекс 8, и условие 8 < 8 ложно - переход не произойдёт.
Аналогично для движения назад - проверяем, что индекс больше 0 (первого элемента). Если индекс 0, уменьшать его нельзя, иначе получим -1, что вызовет ошибку при обращении к IMAGES[-1].
Если модалка закрыта (selectedImageIndex === null), обработчики клавиш не нужны. Ранний return предотвращает добавление слушателей событий. Это экономит ресурсы и предотвращает нежелательное поведение - например, закрытие несуществующей модалки по Escape.
Объект события e содержит свойство key с названием нажатой клавиши. 'Escape' - клавиша Esc, 'ArrowRight' и 'ArrowLeft' - стрелки. Мы проверяем каждую клавишу и вызываем соответствующую функцию. Это делает навигацию удобнее - не нужно использовать мышь.
Метод addEventListener добавляет обработчик события на объект window (всё окно браузера). Событие 'keydown' срабатывает при нажатии любой клавиши. Это глобальный слушатель - он работает независимо от того, на каком элементе фокус.
Cleanup функция удаляет слушатель событий. Это критически важно - без cleanup при каждом изменении selectedImageIndex создавался бы новый слушатель, а старые продолжали работать. Накопление сотен обработчиков привело бы к утечке памяти и странному поведению (каждое нажатие выполнялось бы многократно).
Эффект перезапускается при изменении selectedImageIndex. Это важно, потому что функции goToNext и goToPrev внутри handleKeyPress используют текущее значение selectedImageIndex. Когда индекс меняется, старый обработчик удаляется, и создаётся новый с актуальным значением индекса.
Мы вычисляем текущее изображение из массива по индексу. Если индекс null (модалка закрыта), selectedImage тоже null. Если индекс число, selectedImage содержит объект с данными изображения. Это вычисляемое значение, а не состояние - оно автоматически обновляется при изменении selectedImageIndex.
Второй параметр функции в map - это индекс элемента. Мы передаём его в openModal через стрелочную функцию. Без стрелочной функции (onClick={openModal(index)}) функция вызвалась бы сразу при рендере. Стрелочная функция создаёт замыкание, которое вызовется при клике.
Модалка рендерится только когда selectedImage не null. Оператор && проверяет условие - если ложно, React ничего не рендерит. Если истинно, рендерит JSX справа от &&. Это короткая запись для "показать только если есть выбранное изображение".
Метод stopPropagation() останавливает распространение события вверх по DOM-дереву. Без него клик по контенту модалки дошёл бы до modal-overlay, и модалка закрылась бы. С stopPropagation клик по контенту не вызывает закрытие - закрывается только при клике на затемнённый фон.
Атрибут disabled отключает кнопку "Назад" на первом изображении. Проверка selectedImageIndex === 0 даёт true для первого элемента. Отключенная кнопка серая и некликабельная - это визуальная подсказка, что дальше идти некуда.
Мы добавляем 1 к индексу, потому что для пользователя счёт начинается с 1, а не с 0. Если индекс 0, показываем "1 / 9". Если индекс 8, показываем "9 / 9". Это интуитивнее, чем "0 / 9" и "8 / 9".
Внизу модалки показываем все изображения в виде маленьких кнопок. Текущее изображение получает класс active для визуального выделения. При клике на миниатюру мы сразу устанавливаем её индекс - это альтернативный способ навигации помимо стрелок и клавиатуры.
Атрибут role="img" говорит программам чтения с экрана, что это изображение. aria-label даёт текстовое описание для незрячих пользователей. Эмодзи не всегда корректно озвучиваются, поэтому мы добавляем понятное описание. Это делает галерею доступной для всех.