最近发现一个有意思的Web API IntersectionObserver
和大家分享一下
IntersectionObserver
可以用于检测元素是否进入视口,可以用于实现无限滚动、懒加载等功能。
使用场景:在Web应用中,可能需要实现无限滚动、懒加载等功能,使用IntersectionObserver可以方便地实现这些功能。
IntersectionObserver的教学 大家可以参考mdn,或者看看阮一峰的博客讲解的比较细致。http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
我大概讲一下IntersectionObserver
的用法
API简介
var observer = new IntersectionObserver(callback,options);
let target = document.querySelector('#listItem');
observer.observe(target); // 开始观察
observer.unobserve(target); // 停止观察
observer.disconnect(); // 关闭观察器
IntersectionObserver支持两个参数:
callback是当被监听元素的可见性变化时,触发的回调函数
options是一个配置参数,可选,有默认的属性值
callback
目标元素的可见性变化时,就会调用观察器的回调函数callback。
let callback =(entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
IntersectionObserverEntry 对象
{
time: 3893.92, // 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
rootBounds: ClientRect { // 根元素的矩形区域的信息
bottom: 920,
height: 1024,
left: 0,
right: 1024,
top: 0,
width: 920
},
boundingClientRect: ClientRect { // 目标元素的矩形区域的信息
// ...
},
intersectionRect: ClientRect { // 目标元素与视口(或根元素)的交叉区域的信息
// ...
},
intersectionRatio: 0.54, // 目标元素的可见比例
target: element // 被观察的目标元素,是一个 DOM 节点对象
}
Option 对象
一、threshold 属性
threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0]
,即交叉比例(intersectionRatio)达到0时触发回调函数。
二、root 属性,rootMargin 属性
root属性指定目标元素所在的容器节点
rootMargin属性根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
案例一 懒加载
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
// loading-image
const imgUrl = ref(new URL("/src/assets/loading.png", import.meta.url).href);
let observer: IntersectionObserver;
onMounted(() => {
// 注册观察者
observer = new IntersectionObserver((entries) => {
for (let index = 0; index < entries.length; index++) {
// 获取到目标元素img标签
const target = entries[index].target as HTMLImageElement;
// 观察者返回的对象
const element = entries[index];
if (element.isIntersecting) {
target.src = target.dataset.src ?? "";
observer && observer.unobserve(target);
}
}
});
// 遍历所有class为lazy-image的图片
const imgs: HTMLCollection = document.getElementsByClassName("lazy-image");
for (const img of Array.from(imgs)) {
img && observer.observe(img);
}
// 断开所有观察
onUnmounted(() => {
observer.disconnect();
});
});
</script>
<template>
<div class="card" v-for="item in 10" :key="item">
<img
class="lazy-image"
data-src="http://xxxx/image_1685348257589.png"
:src="imgUrl"
alt="某网站logo"
/>
</div>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
.card {
padding: 200px;
}
</style>
查看mdnIntersectionObserverEntry
新增了isIntersecting
属性
返回一个布尔值,如果目标元素与交叉区域观察者对象 (intersection observer) 的根相交,则返回 true .如果返回 true, 则 IntersectionObserverEntry
描述了变换到交叉时的状态; 如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态。
案例二 无限加载
<script setup lang="ts">
import FooterVue from "@/pages/my/components/Footer.vue";
import { ref, onMounted, onUnmounted, reactive } from "vue";
// loading-image
const imgUrl = ref(new URL("/src/assets/loading.png", import.meta.url).href);
let observer: IntersectionObserver;
onMounted(() => {
// 注册观察者
var intersectionObserver = new IntersectionObserver(function (entries) {
console.log("entries: ", entries);
// 如果不可见,就返回
if (entries[0].intersectionRatio <= 0) return;
loadItems(10);
console.log("Loaded new items");
});
// 开始观察
const foot = document.querySelector(".scrollerFooter");
if (foot !== null) {
intersectionObserver.observe(foot);
}
// 断开所有观察
onUnmounted(() => {
observer.disconnect();
});
});
const loadItems = (num: number) => {
for (let i = 0; i < 10; i++) {
pageData.list.push(i);
}
};
let pageData = reactive({
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
});
</script>
<template>
<div class="card" v-for="(item, index) in pageData.list" :key="index">
<div class="item">{{ item }}</div>
</div>
<div class="scrollerFooter item"></div>
</template>
<style scoped>
.item {
height: 80px;
border: 1px solid #f0f0f0;
margin: 20px;
}
</style>