useIntersection() composable 通过一个响应式的布尔值告诉你某个元素是否在屏幕上(或在某个滚动父容器内)可见。在底层它使用 Intersection Observer API。
它是 Intersection 指令 和 QIntersection 组件在 setup 代码中的对应物:这三者按配置共享同一个 Intersection Observer,因此观察大量元素时滚动依然轻量。当你想在自己的组件上、或在任意元素/组件的 ref 上获取该状态,而又不想为此再包裹一层额外的 DOM 元素时,就用这个 composable。
在 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>