最近项目中遇到了懒加载需求,使用了IntersectionObserver去实现,为了方便后续的复习,在这里记录一下,也感谢大家可以帮我指出错误哈哈
废话少说,先上代码
// vue自定义指令
Vue.directive("intersect", {
bind(el, binding, vnode) {
const observer = new IntersectionObserver(async (entries, observer) => {
if (entries[0].isIntersecting && entries[0].intersectionRatio > 0) {
console.log("--监测元素出现在可视区范围--", entries);
if (binding.value) {
await binding.value();
observer.unobserve(el);
}
}
});
observer.observe(el);
vnode.elm.$observer = observer;
},
unbind(el, binding, vnode) {
if (vnode.elm.$observer) {
vnode.elm.$observer.disconnect();
vnode.elm.$observer = null;
}
},
});
// 在vue中使用,当 #myElement 元素进入视口时,会触发 onIntersect 方法。
<div v-intersect="onIntersect" id="myElement">Hello World</div>
// 在 Vue 组件中定义 onIntersect 方法
methods: {
onIntersect() {
console.log('元素进入视口');
}
这段代码定义了一个vue指令,主要目的是在一个元素进入视口(可视区域)时,执行某个回调函数,并且在回调函数执行后停止对该元素的监测。通过使用 IntersectionObserver
API 来实现这一功能。
详细解释:
1、定义指令
Vue.directive("intersect", {
bind(el, binding, vnode) {
// ...
},
unbind(el, binding, vnode) {
// ...
},
});
bind
: 被绑定到元素时调用。这是初始化观察者的地方。unbind
: 与元素解绑时调用。在这里我们断开观察者的连接,以确保不会有内存泄漏。
2、创建 Intersection Observer
const observer = new IntersectionObserver(async (entries, observer) => {
if (entries[0].isIntersecting && entries[0].intersectionRatio > 0) {
console.log("--监测元素出现在可视区范围--", entries);
if (binding.value) {
await binding.value();
observer.unobserve(el);
}
}
});
IntersectionObserver
构造函数接收一个回调函数,当被观察的元素的可见性发生变化时会调用这个回调函数。entries
: 是一个数组,数组里面每个元素代表一个被观察的目标元素的信息。
参数 entries
是一个 IntersectionObserverEntry
对象的数组,每个 IntersectionObserverEntry
对象有这些信息
- boundingClientRect: 一个
DOMRectReadOnly
对象,表示目标元素的边界信息。 - intersectionRatio: 目标元素的交叉区域与目标元素的整个区域的比例(从 0 到 1)。
- intersectionRect: 一个
DOMRectReadOnly
对象,表示目标元素与视口(或根元素)的交叉区域。 - isIntersecting: 一个布尔值,表示目标元素是否与视口(或根元素)交叉。
- rootBounds: 一个
DOMRectReadOnly
对象,表示根元素的边界信息。如果没有指定根元素,则为视口的边界。 - target: 被观察的目标元素。
- time: 触发回调函数的时间戳(高精度时间)。
这里我们只用到了isIntersecting、intersectionRatio这两个属性
observer
: 是创建的IntersectionObserver
实例本身。
3、判断元素是否进入视口
if (entries[0].isIntersecting && entries[0].intersectionRatio > 0) {
console.log("--监测元素出现在可视区范围--", entries);
if (binding.value) {
await binding.value();
observer.unobserve(el);
}
}
entries[0].isIntersecting
: 当目标元素与视口交叉时为true
,否则为false
。entries[0].intersectionRatio > 0
: 确保交叉区域的比例大于零,即确实有一部分元素进入了视口。binding.value
: 获取传递给指令的值,通常是一个回调函数。当元素进入视口时执行这个回调函数并等待其完成。observer.unobserve(el)
: 停止观察指定的el
元素,因为已经执行了回调函数。这个方法不会影响其他可能正在被观察的元素,如果你同一个IntersectionObserver
实例使用多个元素,那么unobserve
只是移除了对某一个元素的监听。observer.disconnect()
:会停止观察所有通过该观察者实例添加的元素,- 它不仅会停止观察,还会清理内部的资源和回调函数,使得整个
IntersectionObserver
实例完全失效。
4、开始观察和清除观察者
observer.observe(el);
vnode.elm.$observer = observer;
observer.observe(el)
: 开始观察 DOM 元素el
的可见性变化。vnode.elm.$observer = observer
: 临时添加到 DOM 元素上的一个属性,用来存储IntersectionObserver
实例。这么做的目的主要是为了能够在指令的另一个生命周期钩子(如unbind
)中访问同一个IntersectionObserver
实例,以便进行清理工作(例如断开观察器,防止内存泄漏)。
if (vnode.elm.$observer) {
vnode.elm.$observer.disconnect();
vnode.elm.$observer = null;
}
- 在
unbind
钩子中,检查是否存在观察者实例,如果存在则调用disconnect
方法停止观察并清理。 - 虽然在某些情况下,单独调用
unobserve
已经足够,但为了确保彻底清理并防止潜在的内存泄漏或其他副作用,最好还是在元素解绑时调用disconnect,
可以避免潜在的内存泄漏和其他意外行为,使应用程序更加健壮。这有几个原因:
- 保证所有观察停止:如果有遗漏的情况,某些元素未被
unobserve
,disconnect
可以确保所有观察都停止。 - 释放资源:
disconnect
会释放IntersectionObserver
占用的所有资源,包括内部的引用和回调,以便垃圾回收机制能够有效地清理它们。
vnode.elm.$observer = null
: 明确设置为null
,可以明确表明我们不再需要这个属性。这种显式的操作有助于代码的可读性和维护性。虽然disconnect()
方法已经释放了内部资源,但将引用设置为null
可以确保没有残留引用防止垃圾回收
5、如何使用
这个自定义指令可以用于懒加载图片、无尽滚动等需要检测元素是否进入视口的场景。
<div v-intersect="onIntersect" id="myElement">Hello World</div>
// 在 Vue 组件中定义 onIntersect 方法
methods: {
onIntersect() {
console.log('元素进入视口');
}
}
在这个例子中,当 #myElement
元素进入视口时,会触发 onIntersect
方法。
适可而止吧,差不多得了。。。
其他
观察多个元素
使用同一个 IntersectionObserver
实例如何观察多个元素
IntersectionObserver
可以用来观察多个元素,只需在相同的 IntersectionObserver
实例上调用多次 observe()
方法,每次传入不同的目标元素即可。这样可以共享一个观察器实例,从而有效地管理资源和回调逻辑。
假设我们有以下 HTML 结构:
<div class="watch-me" id="element1">Element 1</div>
<div class="watch-me" id="element2">Element 2</div>
<div class="watch-me" id="element3">Element 3</div>
们可以创建一个 IntersectionObserver
实例,并使其同时观察这三个元素:
// 创建 IntersectionObserver 实例
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(`Element ${entry.target.id} is in view`);
// 如果需要,可以在这里执行任何你希望的操作,例如加载数据或应用动画
} else {
console.log(`Element ${entry.target.id} is out of view`);
}
});
});
// 获取所有要观察的元素
const elementsToWatch = document.querySelectorAll('.watch-me');
// 遍历每个元素并调用 observe 方法
elementsToWatch.forEach(el => observer.observe(el));
在 vue中通过自定义指令使用
如果你想在 Vue 中通过自定义指令来实现这个功能,你可以如下定义:
确保在 Vue 自定义指令中使用同一个 IntersectionObserver
实例来监听多个元素,可以通过将该实例存储在 Vue 实例的上下文中(例如 vnode.context
)。
vnode.context
指向当前正在被渲染的 Vue 组件实例。
这样可以保证每个指令都能访问和复用同一个观察器实例,从而实现对多个元素的同时监听。
Vue.directive("intersect-multiple", {
bind(el, binding, vnode) {
// 检查是否已经存在 Observer 实例,如果不存在则创建一个新的
const observer = vnode.context.$observer || new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(`${entry.target.id} is intersecting`);
if (binding.value) {
binding.value(entry.target);
}
}
});
});
// 保存到上下文中,确保其他指令也能访问到同一个 Observer 实例
vnode.context.$observer = observer;
// 开始观察当前元素
observer.observe(el);
// 将 observer 实例存储在 DOM 元素的 $observer 属性上(可选)
el.$observer = observer;
},
unbind(el, binding, vnode) {
// 获取保存的 observer 实例,并调用 unobserve 方法停止观察当前元素
if (el.$observer) {
el.$observer.unobserve(el);
el.$observer = null; // 确保引用被清除
}
// 如果没有更多的元素需要观察,可以选择性地断开 observer
// 这里假设你可能有一些逻辑来决定何时完全断开 observer,比如检查尚在观察的元素数量
// if (需要断开 observer 的条件) {
// vnode.context.$observer.disconnect();
// vnode.context.$observer = null;
// }
},
});
在 Vue 组件中使用这个指令:
<template>
<div v-intersect-multiple="onIntersect" id="element1">Element 1</div>
<div v-intersect-multiple="onIntersect" id="element2">Element 2</div>
<div v-intersect-multiple="onIntersect" id="element3">Element 3</div>
</template>
<script>
export default {
methods: {
onIntersect(target) {
console.log('Intersected:', target.id);
// 执行你的逻辑,比如加载数据或触发动画
}
}
}
</script>
还能干什么
IntersectionObserver构造函数除了观察的元素的可见性发生变化,还可以用来干什么
1、懒加载图像和其他资源
这是 IntersectionObserver
最常见的用途之一。你可以使用它来延迟加载图像、视频或者其他媒体资源,只有当这些资源即将进入视口时才开始加载,从而节省带宽和加快初始页面加载速度。
const lazyImages = document.querySelectorAll('img.lazy');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
lazyImages.forEach(image => {
observer.observe(image);
});
2、实现无限滚动
你可以在用户滚动到页面底部或某个特定区域时自动加载更多内容,实现类似于社交媒体平台上的无限滚动效果。
const loadMore = document.querySelector('#load-more-trigger');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 加载更多数据
loadMoreData();
}
});
});
observer.observe(loadMore);
function loadMoreData() {
// 异步请求更多数据并添加到DOM中
}
3、动态触发动画
你可以根据元素是否进入视口动态地触发 CSS 或 JavaScript 动画效果,使得页面看起来更加生动和互动。
const animatedElements = document.querySelectorAll('.animate-on-scroll');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animated');
observer.unobserve(entry.target); // 一旦动画触发,就不再观察该元素
}
});
});
animatedElements.forEach(element => {
observer.observe(element);
});
4、报告广告曝光
在广告投放系统中,你可以使用 IntersectionObserver
来报告广告是否被用户实际看到,以此来统计广告曝光和点击率。
const ads = document.querySelectorAll('.ad-banner');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
reportAdExposure(entry.target.id);
observer.unobserve(entry.target);
}
});
});
ads.forEach(ad => {
observer.observe(ad);
});
function reportAdExposure(adId) {
// 发送广告曝光数据给服务器
}
5、跟踪用户阅读进度
你可以跟踪用户滚动页面时阅读的进度,以便为用户提供更好的导航体验或者记录用户的阅读行为。
const sectionHeaders = document.querySelectorAll('h2');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
updateReadingProgress(entry.target.id);
}
});
});
sectionHeaders.forEach(header => {
observer.observe(header);
});
function updateReadingProgress(sectionId) {
// 更新阅读进度条或其他UI
}
6、延迟初始化复杂脚本
有些第三方库或复杂脚本可能会对性能产生较大影响,你可以使用 IntersectionObserver
来延迟这些脚本的初始化,直到它们的容器进入视口。
const heavyComponent = document.querySelector('#heavy-component');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
initializeHeavyComponent();
observer.unobserve(entry.target);
}
});
});
observer.observe(heavyComponent);
function initializeHeavyComponent() {
// 初始化复杂的第三方库或大型组件
}
总结
除了可见性检测,IntersectionObserver
还可以用于懒加载资源、实现无限滚动、触发动画、报告广告曝光、跟踪阅读进度和延迟初始化等多种场景。通过灵活运用 IntersectionObserver
API,可以显著提升网页的性能和用户体验。