Skip to content

Отслеживание видимости

useVisibilityTracker() — отслеживание «вошёл во вьюпорт» / «покинул вьюпорт» по ключу на основе настоящего IntersectionObserver, а не сравнения visibleRange — поэтому точность сохраняется даже при большом буфере overscan или порогах частичной видимости. Не зависит от структуры данных: вы сами решаете, какие элементы observe(), под любым удобным ключом (id строки, индекс, что угодно).

Типичное применение: подсветить пункт навигации/миникарты, элемент оглавления или кнопку «перейти к» на панели управления, пока соответствующая строка реально видна на экране в виртуализированном списке или таблице — мгновенно выключая подсветку, как только строка уходит за пределы экрана.

ts
function useVisibilityTracker(options: UseVisibilityTrackerOptions): UseVisibilityTrackerReturn

Опции

root

() => HTMLElement | null

Возвращает контейнер прокрутки для пересечения. Опустите для использования вьюпорта браузера.

rootMargin

string · по умолчанию: '0px'

rootMargin IntersectionObserver — расширяет/сжимает эффективные границы root (например, срабатывать чуть раньше).

threshold

number | number[] · по умолчанию: 0

Доля элемента, которая должна быть видна, чтобы считаться «видимой».

onVisible

(key: string | number) => void

Вызывается, когда отслеживаемый ключ становится видимым.

onHidden

(key: string | number) => void

Вызывается, когда отслеживаемый ключ становится скрытым (в том числе через unobserve, пока был видим).

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

visibleKeys

Readonly<Ref<Set<string | number>>>

Ключи, в данный момент пересекающиеся с root.

isVisible

(key: string | number) => boolean

Виден ли key в данный момент.

observe

(el: Element | null, key: string | number) => void

Начать отслеживать элемент под key — привязывайте через callback template-ref.

unobserve

(key: string | number) => void

Прекратить отслеживать key (например, при размонтировании строки).

root может разрешиться уже после того, как запустится собственная настройка этого composable (например, template-ref соседнего VirtualList) — он опрашивается несколько кадров и автоматически перестраивается, если разрешённый элемент root когда-либо меняется (например, после принудительного :key-перемонтирования).

Пример

Отслеживание конкретных строк и отражение их видимости в боковой панели — тот же паттерн, что используется в «Watchlist» демо VirtualList:

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VirtualList, useVisibilityTracker } from 'vue-virtual-scroller-kit'
import type { VirtualListExpose } from 'vue-virtual-scroller-kit'

interface Row {
  id: number
  title: string
}
const items = ref<Row[]>(
  Array.from({ length: 100_000 }, (_, i) => ({ id: i + 1, title: `Row ${i + 1}` })),
)
const listRef = ref<VirtualListExpose | null>(null)
const watchedIds = ref<Set<number>>(new Set([1, 50_000]))

const tracker = useVisibilityTracker({
  root: () => listRef.value?.getScrollElement() ?? null,
})

// Строки размонтируются при прокрутке за пределы виртуализированного диапазона, поэтому
// отслеживание/снятие происходит на монтировании/размонтировании, а не в предположении,
// что наблюдаемый элемент остаётся живым.
function onRowMount(el: Element, id: number) {
  if (watchedIds.value.has(id)) tracker.observe(el, id)
}
function onRowUnmount(id: number) {
  tracker.unobserve(id)
}
</script>

<template>
  <VirtualList
    ref="listRef"
    :items="items"
    key-field="id"
    :estimated-item-size="48"
    style="height: 500px"
  >
    <template #default="{ item }">
      <div
        :ref="(el) => el && onRowMount(el as Element, item.id)"
        @vue:unmounted="onRowUnmount(item.id)"
        :style="{
          background:
            watchedIds.has(item.id) && tracker.isVisible(item.id) ? '#fef08a' : 'transparent',
        }"
      >
        {{ item.title }}
      </div>
    </template>
  </VirtualList>
</template>