Руководство по Intersection Observer

Недавно мне пришлось конкретно поработать с IntersectionObserver и, как обычно, решил рассказать об этом на блоге.

Основная суть IntersectionObserver в том, что он следит за пересечением одного элемента другим. Или за пересечением элемента и областью видимостью документа (viewport). А также в целом значительно улучшает производительность страницы, потому что позволяет отказаться от «дорогих» scroll-событий.

В качестве хороших примеров использования Intersection Observer API можно привести ленивую загрузку изображений, создание содержания для контента или даже бесконечную подгрузку постов.

Также, чтобы вам стало совсем всё понятно, рекомендую взглянуть на эту картинку:

Тут мы видим, что страница скроллится, root – это может быть какой-то определённый элемент, либо по умолчанию – viewport страницы (подробнее тут). и когда нам целевой элемент (зелёный квадрат) попадает в эту область, мы выполняем какие-то определённые действия.

Вас ждёт довольно интересный урок, но я предполагаю, что базово вы уже знакомы с JavaScript, если же нет, то рекомендую посмотреть мой видеокурс по основам JS и React.

И конечно же, видео:

Как работать с Intersection Observer API?

Взаимодействовать с API Intersection Observer довольно несложно, но в то же время есть несколько критических для понимания моментов. Попробуем разбираться со всеми ими по порядку.

Для начала нам нужно обратиться к конструктору IntersectionObserver и передать в него какие-либо параметры и callback-функцию.

// какие-либо параметры
const options = {
	// root: document.querySelector( '#viewport' ), // я закомментил строку, чтобы использовать значение по умолчанию
	rootMargin: '0px',
	threshold: [ 0, 0.5 ]
};
 
const observer = new IntersectionObserver( trueCallback, options );
 
const target = document.querySelector( '#target' );
observer.observe( target ); // запускаем "слежку" за элементом(ами) в константе target
 
// callback-функция (возвратная функция)
const trueCallback = function(entries, observer) {
	entries.forEach((entry) => {
		// делаем что-либо для каждого переданного элемента (в нашем случае он один)
		console.log( 'сработало' );
		// например можно добавить какой-либо CSS-класс элементу
		entry.target.classList.add( 'some-class' );
	});
}

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

root

Правильнее всего этот параметр можно описать, как область наблюдения. По умолчанию это viewport (видимая часть страницы сайта в окне браузера), но это может быть какой-то определённый HTML-элемент на странице.

rootMargin

В этот параметр мы можем передать значения, которыми можно увеличить или уменьшить область root-элемента. Значения передаются точно в таком же формате, в котором они передаются при работе с обычным CSS-свойством margin. То есть вы можете передать какое-то одно значение, которое будет применяться со всех сторон, например 25px, либо передать несколько значений в одну строку, для каждой стороны '10px 11% -10px 25px (в таком порядке – сверху справа внизу слева).

threshold

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

Значение threshold (от 0 до 1)Что будет происходить
0 (значение по умолчанию)В этом случае возвратная функция сработает два раза – как только первый пиксель элемента попадёт в область наблюдения, и как только последний пиксель покинет область наблюдения.
0.5В данном случае возвратная функция сработает, когда центр (50%) элемента будет пересекать область наблюдения в любом направлении.
[0, 0.2, 0.5, 1]Да, мы можем передать массив значений. И для каждого из них будет срабатывать возвратная функция. Ну и в таком ситуация возвратная функция срабатывает при: 1) первый пиксель элемента попадает в область наблюдения, либо последний пиксель выходит из области наблюдения 2) 20% элемента внутри области наблюдения (напоминаю, что направление не имеет значения) 3) то же самое для 50% 4) и когда элемент полностью в области наблюдения.

На видео представлен пример кода из самого начала этой главы. Там кстати, напоминаю, значение threshold равно [ 0, 0.5 ]. Обратите внимание, когда именно срабатывает возвратная функция.

Callback-функция

Сейчас я бы хотел немного подробнее остановиться на callback-функции, а точнее на доступном в ней объекте IntersectionObserverEntry и его свойствах.

Проще всего их распаковать вот таким образом:

const { 
	time,
	rootBounds,
	boundingClientRect,
	intersectionRect,
	target, 
	isIntersecting, 
	isVisible,
	intersectionRatio 
} = entry;
// в итоге мы получим доступ к каждому свойству в константе с его названием
// например target.classList.add( 'hello' ); // добавляем CSS-класс к наблюдаемому элементу

Давайте поговорим о некоторых из них.

target

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

const { target } = entry;
target.classList.add( 'hello' );

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

isIntersecting

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

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

// callback-функция (возвратная функция)
const trueCallback = function(entries, observer) {
	entries.forEach((entry) => {
		// получаем свойства, которые доступны в объекте entry
		const { target, isIntersecting } = entry;
 
		if( isIntersecting ) {
			// добавляем класс, когда элемент входит в область наблюдения
			target.classList.add( 'some-class' );
		} else {
			// удаляем класс, когда элемент из неё выходит  
			target.classList.remove( 'some-class' );
		}
 
	});
}

Все значения options в этом примере можно оставить равными значениям по умолчанию.

intersectionRatio

Это свойство обычно используется в качестве дополнительного условия внутри callback-функции. И оно капец как зависит от параметра threshold. Если вы не разобрались, как работает threshold, то про intersectionRatio можете даже не начинать читать.

Суть этого свойства в том, что оно содержит информацию о том, на сколько процентов наблюдаемый элемент пересекает наблюдаемую область (находится в ней). Хотя сама эта информация не в непосредственно в процентах, а в значениях от 0 до 1. То есть например значение intersectionRatio равное 0.25 будет означать, что элемент пересекает область на 25%. Ну в целом, вы и так наверное всё это знаете.

Скриншот ниже поможет вам понять этот параметр.

Но есть некоторые ситуации, когда условия со свойством intersectionRatio никогда не сработают.

Например, если вы установили параметр threshold равным 1, и пытаетесь добавить условие intersectionRatio == 0.5, то оно не выполнится никогда (или только при загрузке страницы с нахождением элемента в целевой области), потому что обратная функция не будет срабатывать на моменте, когда элемент находится наполовину в наблюдаемой области. Если вы установили threshold в 0.5, то шанс его выполнения тоже близок к нулю, потому что intersectionRatio будет близким к 0.5, типо 0.5073958039283752, а в таком случае условие ведь тоже не выполняется!! Конечно можно попробовать округлить, но не проще ли использовать знаки < или > для таких условий. Но ещё один момент, если у вас threshold равен 1 и вы делаете проверку intersectionRatio == 1, то она будет выполняться, когда элемент входит в область, а когда выходит – не будет, потому что максимальное значение свойства 1, а в обратную сторону будет что-то типо 0.9973958039283752! Вот такое огромное количество разных моментиков, про которые лучше бы помнить.

Ну а теперь уже можно перейти к полноценным примерам, как считаете?

Примеры использования IntersectionObserver

В целом, если вы читали урок, а не сразу перешли к примерам, вы уже знаете, как работать с Intersection Observer API, но чтобы понимать его ещё лучше, я рекомендую вам изучить примеры ниже. Я решил вставить их с Codepen, чтобы вам было удобнее посмотреть код и поиграть с ним при необходимости.

Кстати, если вам кажется, что не хватает знаний JavaScript, то обратите внимание на мой курс.

1. Квадраты

В этом примере, если вы скроллите вниз, появляются квадраты, которые выполняют различные действия. По сути всё, что делает IntersectionObserver в этом примере, это добавляет CSS-класс к квадрату, когда он попадает в область отслеживания (когда становится полностью видимым на экране). И именно CSS-класс и осуществляет анимацию квадрата. И несмотря на то, что добавляем CSS-класс один и тот же для каждого квадрата, анимации различаются, потому что сами квадраты имеют разные CSS-классы!

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

Ещё мы использовали параметр threshold равным 1.0, и условие со свойством intersectionRatio entry.intersectionRatio == 1, это нужно именно для того, чтобы анимация квадрата срабатывала только тогда, когда квадрат целиком попадает в область отслеживания.

2. Ленивая загрузка изображений

Ленивая загрузка изображений при скролле – это одна из тех вещей, для реализации которых идеально использовать Intersection Observer.

Взгляните на пример ниже:

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

В этом примере вы могли заметить отметку в «200px от нижней границы экрана браузера» – она нужна именно для того, чтобы показать вам, как происходит ленивая загрузка. Потому что так бы вы даже и не заметили её. Но есть одно «но» – в Codepen эта история не работает, поэтому вы можете прочекать её по этой ссылке.

А реализовал я её при помощи параметра rootMargin. По умолчанию бы он был равен 0 и изображения начинали подгружаться, как только первый пиксель области изображения коснулся нижней границы viewport. На реальных примерах этот отступ и вовсе лучше увеличивать в обратную сторону, например rootMargin: "0px 0px 200px 0px", тогда загрузка изображений начнётся заранее.

Мы также использовали условие if(!!window.IntersectionObserver){ , чтобы проверить, что функционал IntersectionObserver в принципе поддерживается используемым браузером. Кстати, это также можно глянуть на сайте caniuse.com.

3. Навигация-содержание с выделением текущего элемента в ней

Предупреждаю, тут будет даже два примера в одном.

Начнём с лёгкого – когда мы используем высоту наблюдаемых секций равную высоте окна браузера (viewport). Тут нужно лишь решить, на скольки процентах попадания секции в экран мы будем переключать активный элемент меню. В примере ниже это 55%. Поэтому мы устанавливаем параметр threshold равным 0.55 и также в возвратной функции используем условие if( intersectionRatio >= 0.55 ). В целом, если вы вдумчиво читали урок, то ничего сложного.

Однако, если вы например зайдёте в редактирование этого кода на codepen и попробуете в CSS выставить высоту секции равную скажем 200vh, то код у вас больше уже никогда не заработает. А не заработает он потому что 55% секции это 110vh, а такая высота никогда не уместится на вашем экране! Что же делать в такой ситуации?!

Чекайте второй пример:

Давайте я теперь расскажу вам, как это работает.

В этом примере для удобства я создал объект TableOfContents, со своими свойствами и методами, об этом кстати тоже есть урок в моём видеокурсе по JavaScript и React.

В качестве отслеживаемых элементов мы используем заголовки с атрибутами id, которые по сути являются якорями.

Прежде всего нам нужно учитывать, что одновременно на странице могут находиться два и более заголовков! Поэтому на все соответствующие заголовкам пункты меню мы добавляем класс is-visible, но только на первый из них мы добавляем класс is-active, которые и подсвечивает ссылку в меню.

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

Миша

Впервые познакомился с WordPress в 2009 году. Организатор и спикер на конференциях WordCamp. Преподаватель в школе Нетология.

Пишите, если нужна помощь с сайтом или разработка с нуля.

Комментарии — 1

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

Миша Рудрастых и WordPress

Полезности из мира WordPress и жизни студии.

Мой телеграм-канал