ссылка на живой пример того, что мы будем кодировать!

Https://shapirodaniel.github.io/single-page-nav/

ссылка на репозиторий GitHub и исходные файлы



Статические одностраничные прокручиваемые сайты представляют собой интересную и очень интересную задачу навигации! Если вы создали одностраничные приложения с помощью React, скорее всего, вы использовали React Router для рендеринга своих компонентов по определенным URI - и если вы создали одностраничный прокручиваемый сайт, где все компоненты отображаются с одним и тем же URI, вы уже знаете, в чем состоит складка: а именно, как нам направить пользователя в конкретную область сайта и как мы можем отразить текущее положение пользователя на нашем сайте в навигации бар?

Как пользователь,

  1. Я хочу щелкнуть элемент навигации и прокрутить этот элемент в мое окно просмотра, и
  2. Я хочу прокрутить и увидеть свое текущее положение на сайте, отображаемое на панели навигации.

Пользовательская история №1 кажется простой: мы можем повесить containerRef на любой элемент, который нам нравится, и использовать метод scrollIntoView интерфейса Element для перемещения этого контейнера в область просмотра при щелчке по соответствующему элементу навигации. Пользовательская история №2 немного сложнее - как наши элементы навигации узнают, какой контейнер в настоящее время находится в области просмотра, и как мы будем передавать эту информацию вверх по пищевой цепочке, чтобы стили навигации могли отображаться соответствующим образом?

Если бы от нас не требовалось следить за текущим местонахождением пользователя, мы могли бы просто применить стиль activeClass вместе с нашим scrollIntoView вызовом, сохранив navLinkId в локальном состоянии Nav компонента.

Ура! Мы выполнили пользовательскую историю №1 - переходим к следующему этапу.

Чувак, а где мой пользователь?

Отслеживать местонахождение пользователя не должно быть слишком сложно: у нас есть обязательный дескриптор (containerRef) для каждой из наших навигационных целей, и мы можем использовать его, чтобы определить, виден ли контейнер ref. Это отличный вариант использования веб-APIIntersectionObserver, который позволит нам создать наблюдателя, чтобы уведомлять нас, когда - и, что важно, в какой степени - его наблюдаемая цель пересекается с нашим полем обзора.

Экземпляр IntersectionObserver принимает функцию обратного вызова и объект параметров, состоящий из:

  1. элемент root, который позволит нам сравнить положение нашего возможно пересекающегося containerRef с элементом или, если не указано, с окном просмотра браузера;
  2. rootMargin (записанное как строковое свойство поля CSS), которое позволит нам расширить или сузить размер ограничивающего прямоугольника нашего root элемента перед вычислением пересечений; и,
  3. значение threshold (или массив значений) в диапазоне 0,0–1,0, которое поможет нам откалибровать чувствительность нашего наблюдателя, установив точки останова в зависимости от того, насколько общая площадь наблюдаемого элемента пересекает root.

Поскольку нам нужно только определить, вошел ли вертикально сложенный контейнер в видимую для пользователя область страницы, мы оставим root неуказанным и оставим его по умолчанию для нашего окна просмотра браузера. Аналогичным образом, rootMargin значение по умолчанию 0 пикселей выполнит свою работу, но в конечном итоге нам может потребоваться калибровка нашего threshold (подробнее об этом позже).

Наш экземпляр observer получает обратный вызов, который ожидает два аргумента - массив entries и сам экземпляр observer. Массив entries - это набор IntersectionObserverEntry экземпляров, которые подключаются к различным свойствам взаимосвязи между пересекающимся элементом и нашим root. Мы будем использовать логическое значение entry.isIntersecting, чтобы отслеживать каждый containerRef, когда он входит и покидает область просмотра.

Здорово! Мы на шаг ближе к реализации пользовательской истории №2: когда наш пользователь прокручивает страницу, у нас будет способ узнать, где они находятся в нашем одностраничном макете. Давайте создадим настраиваемую ловушку, которая позволит нам предоставить containerRef, присоединить observer и вернуть логическое значение, указывающее, отображается ли элемент на экране. Вся заслуга принадлежит пользователю StackOverflow Creaforge, который создал специальный хук именно для этой цели!

Давайте создадим useOnScreen для создания пользовательского хука, useNav, который позволит нам генерировать и возвращать наблюдаемое containerRef, ref.current.id которого будет зарегистрировано на NavProvider, доступ к которому осуществляется через useContext!

Кстати: если вы немного не уверены в своем подходе к React Context API, ознакомьтесь с моей статьей Восстановление игры с императивным кодом с нуля в React, где я излагаю хорошую общую стратегию для реализации React Context API. - легкая альтернатива Redux, которая помогает разделять и управлять частями состояния.

Наш NavContext состоит из React.createContext() экземпляра и NavProvider, чьи значения провайдера activeNavLinkId и setActiveNavLinkId - локальное состояние, ранее обрабатываемое нашим Nav component, - будут доступны нашему ловушке useNav и использоваться для обновления нашего пользовательского интерфейса навигации. Но зачем изобретать велосипед? У нас уже есть управление на местном уровне, и все, что нам нужно сделать, это изменить наш useNav хук, чтобы он принимал setActiveNavLinkId, передав его каждому компоненту, который мы хотим зарегистрировать для наблюдения.

В данном случае я решил использовать NavProvider, поскольку он позволяет нам инкапсулировать всю нашу логику контекста в useNav: после того, как ловушка установлена ​​и наши Nav и Компоненты NavLink построены, для назначения функциональности навигации навигационной цели потребуется только одно объявление ловушки и массив из navLink объектов, содержащих navLinkId и scrollToId, каждый из которых указывает на экземпляр NavLink и идентификатор containerRef.

Собираем все вместе

Теперь, чтобы развернуть наш кастомный хук и немного по навигации! Начнем с того, что обернем содержимое нашего App компонента верхнего уровня в наш NavProvider.

Наш Nav компонент становится немного тоньше: поскольку мы передали управление нашим activeNavLinkId NavContext, мы можем удалить наш локальный useState обработчик. Давайте также реорганизуем наш NavLink компонент в отдельный файл для лучшей модульности.

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

useNav слепо относится к его реализации - повесив ref на любой понравившийся элемент, мы можем создать прокручиваемую цель навигации, которая будет:

  1. реагировать на прямую навигацию с помощью кликов,
  2. регистрировать пассивную навигацию всякий раз, когда прокручиваемая пользователем цель появляется в окне просмотра нашего браузера и синхронизируется со значениями threshold нашего IntersectionObserver экземпляра, и
  3. отражать текущее положение пользователя в нашем Nav компоненте!

Инкапсулируя всю логику, которую мы используем для информирования нашего Nav компонента в useNav, мы пришли к готовому к использованию решению для одностраничной навигации, интерфейс которого имеет минимальную площадь поверхности, что позволяет нам, чтобы легко добавлять, удалять и перемешивать элементы в нашей иерархии компонентов с возможностью навигации. И наш NavContext на самом деле является просто конкретной реализацией более общего IntersectionContext, который может быть использован для удовлетворения других требований пользовательских историй, связанных с пересечением, таких как отложенная загрузка контента.

Отмена ложных подсказок навигации

Есть еще одна интересная ошибка, которую нужно исправить - как предотвратить появление ложных подсказок для пользователя при переходе по щелчку мыши к удаленному (т. Е. Несмежному) компоненту относительно своего текущего положения?

CSS спешит на помощь! Мы назначим transition-delay маске NavLink экземпляров, которые получают activeClass, поскольку наш scrollToSection метод прокручивает пользователя мимо других компонентов на пути к месту назначения. Это нивелирует эффект нашего useNav хука для всех ссылок, которые получили бы activeClass стиль, что позволяет нам улучшить UX - в конце концов, щелкнуть ссылку и увидеть, как загораются другие ссылки, довольно неприятно. !

Настройка порога наблюдения

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

Готово!

И это все! У нас есть расширяемая одностраничная система навигации, поддерживаемая React, React Context и React Custom Hooks. Помните: пользовательские хуки - это просто функции, которые составляют стандартные хуки для инкапсуляции логики и предоставляют чистый, минимальный интерфейс для работы с нашими компонентами. Всякий раз, когда вы обнаруживаете, что пишете повторяющиеся последовательности хуков в своих компонентах, посмотрите, сможете ли вы абстрагироваться от деталей и предоставить вам и вашей команде оптимизированный API, такой как useNav!

ссылка на живой пример того, что мы построили!

Https://shapirodaniel.github.io/single-page-nav/

ссылка на репозиторий GitHub и исходные файлы



Дэниел Шапиро - инженер-программист полного цикла и выпускник Fullstack Academy, где в настоящее время работает преподавателем. Когда он не занимается созданием крутых вещей, вы обычно можете увидеть, как он занимается своей прежней карьерой пекаря в многочисленных ремесленных пекарнях в Чикаго, выпекая партию английских кексов, бегая по пляжу со своей смесью биглей Лили или каякивая по озеру Мичиган. Свяжитесь с Дэниелом по адресу [email protected] или подключитесь к LinkedIn и обязательно посетите Note-ary, пакет для управления проектами с досками в стиле Канбан и общением в реальном времени, поддерживаемым socket.io!