最轻量可控的 Vue 自定义指令方案是直接封装 IntersectionObserver API:bind 中创建并观察,unbind 中 disconnect 清理;支持 onEnter/onLeave、threshold、once 等配置,通过 intersectionRatio > 0 精准判断可见性,并缓存实例防重复绑定。

直接用 IntersectionObserver API 封装成 Vue 自定义指令,是最轻量、最可控、性能最优的方式。它绕开了框架抽象层的开销,也不依赖第三方库,能精准控制观察时机、清理逻辑和触发条件。
核心思路:用原生 API 封装指令生命周期
自定义指令在 bind 钩子中创建 IntersectionObserver 实例并开始观察,在 unbind 中主动断开连接,避免内存泄漏。回调函数只在交叉状态变化时执行,不随滚动频繁触发。
-
bind:获取元素、初始化 observer、调用
observe(el) - update(可选):当绑定值变化(如阈值更新),可重新配置 observer 或重置观察
-
unbind:必须调用
observer.disconnect(),确保销毁干净
支持灵活配置的参数设计
通过 binding.value 接收对象参数,让指令具备扩展性。常见字段包括:
- onEnter:元素进入视口时执行的函数
- onLeave:元素离开视口时执行的函数
-
threshold:设置触发回调的交叉比例(如
0.1表示 10% 进入即触发) -
rootMargin:模拟“提前加载”,比如
"100px"让元素距视口还有 100px 时就开始响应 -
once:设为
true则只触发一次(适合懒加载图片后停用观察)
防抖与精准判断的关键细节
实际使用中容易忽略两个关键点:
-
不只看
isIntersecting:它在元素完全移出视口时才变false,但有时需要更早响应(比如用户快速滚动略过)。应结合intersectionRatio > 0判断是否“有任意像素可见” -
避免重复绑定同一元素:指令内部可用
el._ioInstance缓存 observer 实例,防止多次 bind 导致多个 observer 同时监听一个元素
一个简洁可用的实现示例
以下指令支持 once 和 threshold,并在进入时执行回调:
对应指令代码核心片段:
Vue.directive('intersect', {
bind(el, binding) {
const { onEnter, onLeave, threshold = 0, once = false } = binding.value || {}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && entry.intersectionRatio > 0) {
onEnter && onEnter()
if (once) observer.unobserve(el)
} else if (onLeave && !entry.isIntersecting) {
onLeave()
}
},
{ threshold }
)
observer.observe(el)
el._ioInstance = observer
},
unbind(el) {
if (el._ioInstance) {
el._ioInstance.disconnect()
el._ioInstance = null
}
}
})


















