События привязки прокрутки JavaScript для анимации, запускаемой прокруткой

Пользователи Chrome версии 129 и выше теперь могут получить доступ к новым событиям привязки прокрутки - scrollsnapchange и scrollsnapchanging. Эти новые события предоставляют пользователям уникальный и динамический контроль над функцией привязки прокрутки CSS.

переключение прокрутки

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

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

scrollsnapchange - смена прокрутки

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

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

Как реализуются события привязки прокрутки?

События привязки прокрутки работают в сочетании со свойствами привязки прокрутки CSS. Эти события обычно назначаются родительскому контейнеру, который содержит объекты привязки прокрутки.

Оба этих события привязки прокрутки JavaScript совместно используют объект SnapEvent, который включает в себя два важных свойства, используемых в этих событиях:

    snapTargetBlock - Предоставляет ссылку на элемент, который был привязан в направлении блока при запуске события. Если привязка происходит только во встроенном направлении, возвращается значение null, поскольку в направлении блока привязка к элементу не выполняется. snapTargetInline Предоставляет ссылку на элемент, привязанный во встроенном направлении, при запуске события. Если привязка происходит только в направлении блока, она возвращает значение null, поскольку ни к одному элементу не привязана привязка во встроенном направлении

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

Давайте взглянем на краткий пример того, как использовать эти события:

Посмотрите, как работает ручка С помощью scrollSnapChange и демонстрации scrollSnapChanging от coded_fae (@coded_fae) на CodePen.

В этом примере показан контейнер для прокрутки с привязкой к вертикальной прокрутке, применяемой к группе блоков сетки. Эти блоки, представленные в виде элементов div, служат объектами привязки к прокрутке.

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

Вот как реализуются события. В комментариях к коду приведено пошаговое описание процесса:

Для изменения прокрутки:

scrollContainer.addEventListener("scrollsnapchanging", (event) => {
// This adds an event listener to the scroll container for the "scrollsnapchanging" event 
// This event fires during scrolling, predicting potential snap targets
  const previousPending = document.querySelector(".snap-incoming");
  // Find any existing element with the "snap-incoming" class

  if (previousPending) {
    previousPending.classList.remove("snap-incoming");
    // If such an element exists, remove the "snap-incoming" class
    // This ensures only one element has this active state at a time
  }
  event.snapTargetBlock.classList.add("snap-incoming");
  // Add the "snap-incoming" class to the new snap target

  updateEventLog("Snap Changing");
  // Update the event log to show that the snapchanging event has occured
});

Для scrollsnapchange:

scrollContainer.addEventListener("scrollsnapchange", (event) => {
  // This adds an event listener to the scroll container for the "scrollsnapchange" event
  // The event fires when a scroll snap has been completed and a new target has been selected

  const currentlySnapped = document.querySelector(".snap-active");
  // Finds any existing element with the "snap-active" class

  if (currentlySnapped) {
    currentlySnapped.classList.remove("snap-active");
    // If such an element exists, remove the "snap-active" class
  }

  event.snapTargetBlock.classList.add("snap-active");
  // Adds the "snap-active" class to the new snap target

  updateEventLog("Snap Changed");
  // Update the event log to show that the snapchange event has occured
});

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

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

Карусель с точным управлением защелкой

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

Эта реализация осуществляется с помощью события scrollsnapchange для динамического применения класса стилей snap-active непосредственно к текущему слайду карусели. Это плавно добавляет анимацию к карусели, тем самым создавая более привлекательную презентацию.

Посмотрите на ручку Карусель с точным управлением привязкой с помощью coded_fae (@coded_fae) на CodePen.

Вот пошаговый процесс использования события scrollsnapchange:

// Select the carousel container
const carousel = document.getElementById("carousel");
// Get all slides within the carousel
const slides = carousel.querySelectorAll(".carousel-slide");

// This adds an event listener to the scroll container for the "scrollsnapchange" event 
carousel.addEventListener("scrollsnapchange", (event) => {
  // Get the target slide that snapped into place (horizontally scrolled)
  const snapTarget = event.snapTargetInline;

  // This part updates the current slide for the navigation buttons
  const slideWidth = carousel.clientWidth;
  currentSlide = Math.round(carousel.scrollLeft / slideWidth);

  // Remove 'snap-active' class from previously active slides
  const currentlySnapping = document.querySelector(".snap-active");
  if (currentlySnapping) {
    currentlySnapping.classList.remove("snap-active");
  }
  // Add 'snap-active' class to newly snapped slide to trigger animations
  snapTarget.classList.add("snap-active");
});

В CSS класс snap-active объединяется с именем существующего класса элемента. Когда запускается событие scrollsnapchange и этот класс добавляется в список классов элемента, немедленно применяются соответствующие правила CSS, которые динамически обновляют внешний вид слайда и анимируют его содержимое в режиме реального времени:

.carousel-slide.snap-active {
  opacity: 1;
  scale: 1;
}
.carousel-slide.snap-active .content {
  opacity: 1;
  transform: translateY(0);
}

Автоматическое воспроизведение видео-трейлеров на snap

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

Используя события scrollsnapchanging и scrollsnapchange одновременно, реализация точно контролирует воспроизведение видео, гарантируя, что воспроизводится только видимое в данный момент видео, в то время как другие остаются приостановленными.

Смотрите видео с авторучкой Трейлеры к видео автоматически воспроизводятся на Snap by coded_fae (@coded_fae) на CodePen.

Вот как были использованы эти события:

const snapContainer = document.querySelector(".snap-container");
const trailers = document.querySelectorAll("video");

snapContainer.addEventListener("scrollsnapchange", (event) => {
  // Get the video element of the currently snapped item
  const visibleTrailer = event.snapTargetBlock.children[0];
  if (visibleTrailer) {
    // Play the visible trailer when it snaps into view
    visibleTrailer.play();
  }
});

snapContainer.addEventListener("scrollsnapchanging", (event) => {
  const visibleTrailer = event.snapTargetBlock.children[0];
  if (visibleTrailer) {
    // Pause the currently visible trailer when transitioning to another snap
    visibleTrailer.pause();
  }
});

В данном случае элемент video напрямую не вложен в snapTargetBlock. Поэтому для доступа к элементу video необходимо получить доступ к дочерним элементам snapTargetBlock:

const visibleTrailer = event.snapTargetBlock.children[0];

Динамический скроллинг

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

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

Смотрите запись с помощью пера Автор: abiolaesther_ (@coded_fae) в CodePen.

Как работает эта логика:

const scrollContainer = document.querySelector(".container");

// Handle the scrollsnapchange event
scrollContainer.addEventListener("scrollsnapchange", (event) => {
  // Get the current snap target block
  const snapTarget = event.snapTargetBlock;

  // Add the "active" class to the first child element (if it exists)
  if (snapTarget.children[0]) {
    snapTarget.children[0].classList.add("active");
  }

  // Add the "active" class to the second child element (if it exists)
  if (snapTarget.children[1]) {
    snapTarget.children[1].classList.add("active");
  }
});

scrollContainer.addEventListener("scrollsnapchanging", (event) => {
  // Get the current snap target block
  const snapTarget = event.snapTargetBlock;

  // Remove the "active" class from the first child element (if it exists)
  if (snapTarget.children[0]) {
    snapTarget.children[0].classList.remove("active");
  }

  // Remove the "active" class from the second child element (if it exists)
  if (snapTarget.children[1]) {
    snapTarget.children[1].classList.remove("active");
  }
});

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

Теперь давайте взглянем на CSS для раздела портфолио:

@keyframes slideDown {
  0% {
    opacity: 0;
    transform: translateY(-100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideUp {
  0% {
    opacity: 0;
    transform: translateY(100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.portfolio-content.active {
  animation: slideDown 1s ease-out forwards;
}

.portfolio-grid.active img {
  animation: slideUp 1s ease-out forwards;
}

.portfolio-grid.active img:nth-child(1) {
  animation-delay: 0.2s;
}
.portfolio-grid.active img:nth-child(2) {
  animation-delay: 0.4s;
}
.portfolio-grid.active img:nth-child(3) {
  animation-delay: 0.6s;
}

Мы создали две разные анимации: slideup и slidedown. Эти анимации были добавлены в активный класс вместе с названием соответствующего класса элемента.

Чтобы настроить таргетинг на отдельные дочерние элементы, мы использовали псевдокласс:nth-child(). Такой подход позволил нам создать плавный поток анимации.

Устранение разрыва между привязкой на основе CSS и JS

Еще в 2022 году привязка прокрутки зависела исключительно от CSS для определения точек привязки и стилизации целевых объектов привязки. Хотя CSS предоставляет простой и декларативный способ включения привязки с помощью таких свойств, как scroll-snap-type и scroll-snap-align, ему не хватает динамического контроля над поведением привязки и стилем оформления.

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

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

Например, вы можете использовать scroll-snap-type для управления поведением привязки и scroll-snap-align для определения соответствующих свойств, возвращаемых объектом SnapEvent:

    ось блока - snapTargetBlock ссылается на встроенную ось привязанного элемента - snapTargetInline ссылается на обе оси привязанного элемента - Оба свойства возвращают соответствующие привязанные элементы

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

Browser compatibility

Современные браузеры начинают поддерживать эти новые события, но в настоящее время они доступны только для браузеров с Chrome версии 129 и выше и Edge.

Â' 

Сравнение с Intersection Observer API

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

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

Вот краткое сравнение событий привязки прокрутки и API Intersection Observer, в котором описаны их ключевые различия и преимущества:

Заключение и последующие шаги

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

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

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

Я надеюсь, что это руководство было полезным для вас! Не стесняйтесь обращаться ко мне по электронной почте, если у вас возникнут какие-либо вопросы. Приятного программирования!