Skip to page content

useIntersection composable
Quasar v2.30+

useIntersection() composable 通过一个响应式的布尔值告诉你某个元素是否在屏幕上(或在某个滚动父容器内)可见。在底层它使用 Intersection Observer API。

它是 Intersection 指令 和 QIntersection 组件在 setup 代码中的对应物:这三者按配置共享同一个 Intersection Observer,因此观察大量元素时滚动依然轻量。当你想在自己的组件上、或在任意元素/组件的 ref 上获取该状态,而又不想为此再包裹一层额外的 DOM 元素时,就用这个 composable。

TIP

在 SSR 或 SSG 模式的服务端,该 composable 不会观察任何东西:在客户端接管之前,isIntersecting 始终为 false。

语法

import { useTemplateRef } from 'vue'
import { useIntersection } from 'quasar'

setup () {
  const target = useTemplateRef('target') // an Element or a component

  const { isIntersecting, refresh, stop } = useIntersection({
    // all optional:
    target,               // omit it to observe the component's own root element
    root: null,           // Element used as viewport; null for the browser viewport
    rootMargin: '0px',    // CSS-like margin around the root
    threshold: 0,         // Number or Array of Numbers (0 to 1)
    once: false,          // stop observing after the first time the target is visible
    disabled: false,      // pause observing
    onIntersect (entry) { // called with every IntersectionObserverEntry
      // return false to stop observing
    }
  })

  // ...
}
function useIntersection(
  options?: MaybeRefOrGetter<{
    target?: MaybeRefOrGetter<
      Element | ComponentPublicInstance | null | undefined
    >
    root?: Element | Document | null
    rootMargin?: string
    threshold?: number | number[]
    once?: boolean
    disabled?: boolean
    onIntersect?: (entry: IntersectionObserverEntry) => boolean | void
  }>
): {
  isIntersecting: Ref<boolean>
  refresh: () => void
  stop: () => void
}

不传 target 时,该 composable 会在组件挂载的那一刻起观察它所在组件的根元素。渲染 fragment(多个根节点)的组件没有可供观察的根元素,因此在那种场景下请提供一个 target。

refresh() 会让观察器再次上报当前状态,无论它是否发生了变化。观察器自身只在变化时上报,所以当你的代码对布局做了改动之后需要一个新的判定结果时(例如在其周围内容增大后,想知道原本在视野内的目标是否仍在视野内),可以用它。

stop() 会彻底结束观察。你很少会需要它,因为组件销毁时该 composable 会自行停止。

运行时更改选项

选项可以是一个普通对象、一个 Ref 或一个 getter 函数。普通对象只读取一次。使用 Ref 或 getter 时,该 composable 会追踪选项所读取的任何响应式状态,并在该状态变化时重新应用它们,因此你永远不需要调用任何东西来「更新」它:

  • 切换 disabled 会暂停和恢复观察(暂停期间 isIntersecting 保持其最后的值)
  • 更改 root、rootMargin 或 threshold 会把目标移动到一个采用新配置的观察器上
  • 把 target 指向另一个元素(或让某个 template ref 通过 v-if 发生变化)会随之跟踪
  • 替换 onIntersect 从下一个 entry 起生效

解析结果为相同值的选项是空操作(no-op),因此一个每次运行都重建对象的 getter 不会带来额外开销。

import { ref } from 'vue'
import { useIntersection } from 'quasar'

setup () {
  const paused = ref(false)
  const threshold = ref(0)

  const { isIntersecting } = useIntersection(() => ({
    disabled: paused.value,
    threshold: threshold.value
  }))

  function pause () { paused.value = true }
  function resume () { paused.value = false }

  // from now on the handler fires only when at least
  // half of the element is visible
  function requireHalf () { threshold.value = 0.5 }

  // ...
}

用一个持有整个选项对象的 Ref 也能达到同样效果:

const options = ref({ rootMargin: '0px' })

const { isIntersecting } = useIntersection(options)

// later on
options.value = { rootMargin: '200px' }

一个已经触发过的 once 观察就此结束:之后再切换 disabled 或更改任何其他选项都不会重新开始观察。如果你需要一个全新的观察,请重新调用 useIntersection()。

示例

<template>
  <div class="card">
    <img v-if="isIntersecting" :src="src" />
  </div>
</template>

<script setup>
  import { useIntersection } from 'quasar'

  defineProps({ src: String })

  // observes this component's root element (the card)
  const { isIntersecting } = useIntersection({
    rootMargin: '200px',
    once: true
  })
</script>

上面的图片会在卡片进入视口之前稍早一点开始加载,并且得益于 once,此后就不再观察这张卡片了。

如果想改为观察某个特定元素或子组件,请交给一个 template ref;当你需要原始的 entry(交叉比例、边界矩形)时,可使用 onIntersect 钩子:

<template>
  <div>
    <div ref="chartRef" class="chart" />
  </div>
</template>

<script setup>
  import { useTemplateRef } from 'vue'
  import { useIntersection } from 'quasar'

  const chartRef = useTemplateRef('chartRef')

  const { isIntersecting } = useIntersection({
    target: chartRef,
    threshold: [0, 0.5, 1],
    onIntersect(entry) {
      console.log('ratio', entry.intersectionRatio)
    }
  })
</script>