Реализация Intersection Observer для отслеживания видимости элементов на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Intersection Observer для отслеживания видимости элементов на сайте
Простая
~1 рабочий день
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Intersection Observer для отслеживания видимости элементов на сайте

Intersection Observer — браузерный API для асинхронного наблюдения за тем, входит ли элемент в область видимости viewport или другого контейнера. Работает в отдельном потоке, не блокирует main thread, не вызывает layout thrashing — в отличие от старого подхода с getBoundingClientRect() в обработчике scroll.

Применения: ленивая загрузка изображений, анимации при появлении, бесконечная прокрутка, отслеживание прочитанного контента, рекламные показы.

Базовая настройка

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      // entry.isIntersecting — виден ли элемент
      // entry.intersectionRatio — доля видимой части (0 до 1)
      // entry.boundingClientRect — размеры и позиция элемента
      // entry.time — временная метка
    })
  },
  {
    root: null,          // null = viewport
    rootMargin: '0px',   // отступы (как CSS margin)
    threshold: 0.1,      // сработать при 10% видимости
    // threshold: [0, 0.25, 0.5, 0.75, 1] — несколько порогов
  }
)

observer.observe(element)
observer.unobserve(element)
observer.disconnect() // остановить все наблюдения

Анимации при появлении

Паттерн без лишних библиотек:

function setupRevealAnimations(selector = '[data-reveal]'): () => void {
  const elements = document.querySelectorAll<HTMLElement>(selector)
  if (!elements.length) return () => {}

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const el = entry.target as HTMLElement
          el.classList.add('is-revealed')
          observer.unobserve(el)
        }
      })
    },
    { threshold: 0.15, rootMargin: '0px 0px -50px 0px' }
  )

  elements.forEach((el) => observer.observe(el))
  return () => observer.disconnect()
}

CSS:

[data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 500ms ease, transform 500ms ease;
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

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

function lazyLoadImages(selector = 'img[data-src]'): void {
  const images = document.querySelectorAll<HTMLImageElement>(selector)

  // Нативный lazy loading как основной метод
  if ('loading' in HTMLImageElement.prototype) {
    images.forEach((img) => {
      img.src = img.dataset.src!
      img.removeAttribute('data-src')
    })
    return
  }

  // Intersection Observer как fallback
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (!entry.isIntersecting) return
        const img = entry.target as HTMLImageElement
        img.src = img.dataset.src!
        img.removeAttribute('data-src')
        observer.unobserve(img)
      })
    },
    { rootMargin: '200px 0px' } // загружать за 200px до появления
  )

  images.forEach((img) => observer.observe(img))
}

Бесконечная прокрутка

function createInfiniteScroll(
  sentinel: HTMLElement,
  onLoadMore: () => Promise<boolean> // возвращает false, когда данные кончились
): () => void {
  let loading = false

  const observer = new IntersectionObserver(
    async (entries) => {
      const entry = entries[0]
      if (!entry.isIntersecting || loading) return

      loading = true
      const hasMore = await onLoadMore()
      loading = false

      if (!hasMore) observer.disconnect()
    },
    { rootMargin: '400px 0px' }
  )

  observer.observe(sentinel)
  return () => observer.disconnect()
}

Отслеживание прочтения контента (аналитика)

function trackReadDepth(
  article: HTMLElement,
  onMilestone: (percent: number) => void
): () => void {
  const thresholds = [0.25, 0.5, 0.75, 1.0]
  const reported = new Set<number>()

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        const ratio = entry.intersectionRatio
        for (const t of thresholds) {
          if (ratio >= t && !reported.has(t)) {
            reported.add(t)
            onMilestone(t * 100)
          }
        }
      })
    },
    { threshold: thresholds }
  )

  observer.observe(article)
  return () => observer.disconnect()
}

// Использование:
trackReadDepth(articleEl, (percent) => {
  analytics.track('article_read_depth', { percent })
})

React-хук

function useIntersectionObserver(
  options: IntersectionObserverInit = {}
): [RefObject<HTMLElement | null>, boolean, IntersectionObserverEntry | null] {
  const ref = useRef<HTMLElement>(null)
  const [isVisible, setIsVisible] = useState(false)
  const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null)

  useEffect(() => {
    const el = ref.current
    if (!el) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting)
        setEntry(entry)
      },
      options
    )

    observer.observe(el)
    return () => observer.disconnect()
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [options.threshold, options.rootMargin])

  return [ref, isVisible, entry]
}

// Использование:
function AnimatedCard() {
  const [ref, isVisible] = useIntersectionObserver({ threshold: 0.2 })

  return (
    <div
      ref={ref as React.RefObject<HTMLDivElement>}
      className={isVisible ? 'card card--visible' : 'card'}
    >
      ...
    </div>
  )
}

Что входит в работу

Настройка наблюдателей для нужных сценариев — анимации, ленивая загрузка, бесконечный скролл, аналитика прочтения. React-хуки, корректная очистка observers при размонтировании, prefers-reduced-motion для анимаций.

Срок: 0.5 дня на базовые сценарии, 1 день при нескольких паттернах.