Skip to content

Scroll Spy ​

createScrollSpy(targets, options, onChange) — отслеживает, какой из списка целевых элементов сейчас находится в «активной» полосе рядом с вертикальным центром viewport, и сообщает его индекс — стандартный приём подсветки навигации (сайдбар документации, оглавление) без ручной арифметики скролла. Построен целиком поверх пулящегося движка видимости, поэтому все цели с одинаковыми rootMargin/threshold/root (обычный случай — все они приходят из одного вызова) делят один нативный IntersectionObserver.

ts
function createScrollSpy(
  targets: Array<Element | null | undefined>,
  options: ScrollSpyOptions | undefined,
  onChange: (activeIndex: number | null) => void,
): ScrollSpy

interface ScrollSpy {
  destroy(): void
}
ts
import { createScrollSpy } from '@macrulez/inview-core'

const sections = document.querySelectorAll('section[id]')
const spy = createScrollSpy([...sections], undefined, (activeIndex) => {
  const active = activeIndex === null ? null : sections[activeIndex]
  navLinks.forEach((link, i) => link.classList.toggle('active', i === activeIndex))
})

// позже
spy.destroy()

Опции ​

ScrollSpyOptions ​

ПолеТипПо умолчанию
rootMarginstring'-45% 0px -45% 0px' — тонкая полоса у вертикального центра; какая граница её пересекает, тот элемент и становится активным
thresholdnumber | number[]—
rootElement | nullnull
poolObserverPoolобщий пул по умолчанию

Настраивайте rootMargin (и threshold) под конкретную вёрстку — странице с очень короткими секциями может понадобиться более узкая полоса, чем по умолчанию.

onChange(activeIndex) ​

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

Возвращаемое значение ​

destroy() ​

() => void

Снимает наблюдение со всех целей и уничтожает движок под капотом.

Реактивная версия: для React — см. React-хуки; Vue composable — ниже.

useScrollSpy (Vue) ​

ts
function useScrollSpy(
  targets: MaybeRefOrGetter<Array<HTMLElement | null | undefined>>,
  options?: UseScrollSpyOptions,
): Ref<number | null>

UseScrollSpyOptions имеет ту же форму, что ScrollSpyOptions выше. Переподписывается при каждом изменении самого targets (как ref/геттера); options захватываются в этот момент — та же одноразовая модель, что у useRevealController, а не реактивный ref/геттер сам по себе.

vue
<script setup lang="ts">
import { useTemplateRefs } from 'vue' // или собирайте рефы вручную в v-for
import { useScrollSpy } from '@macrulez/inview-vue'

const sections = ref<HTMLElement[]>([])
const activeIndex = useScrollSpy(sections)
</script>

<template>
  <nav>
    <a v-for="(item, i) in items" :key="item.id" :class="{ active: i === activeIndex }">
      {{ item.title }}
    </a>
  </nav>
</template>