简介
IntersectionObserver
是一个 JavaScript API
,用于监测一个元素与其父元素或视窗的交叉状态。它可以用来判断一个元素是否可见或者在视窗中的位置是否发生变化。
使用 IntersectionObserver
,你可以注册一个回调函数,当被观察的元素进入或离开视窗,或者与其父元素发生交叉时,该回调函数将被触发。这个 API 提供了一种高效的方法来监测元素的可见性,尤其在处理滚动事件时非常有用。
通过 IntersectionObserver
,你可以实现一些常见的功能,例如延迟加载(当元素进入视窗时再加载内容)、无限滚动(滚动到底部时加载更多内容)以及元素的懒加载(当元素进入视窗时再加载真实内容,而不是占位符)等。它能够帮助你提高页面性能,减少不必要的资源加载,以及改善用户体验。
总结来说,IntersectionObserver
是一个用于监测元素可见性和位置变化的 API,可以实现一些常见的交互效果和性能优化。
具体内容见官方文档:https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
下面说两个简单的应用案例
案例一:滚动动画
<template>
<div id="abc">
<div class="observer-item">content-1</div>
<div class="observer-item">content-2</div>
<div class="observer-item">content-3</div>
<div class="observer-item">content-4</div>
<div class="observer-item">content-5</div>
<div class="observer-item">content-6</div>
<div class="observer-item">content-7</div>
<div class="observer-item">content-8</div>
<div class="observer-item">content-9</div>
<div class="observer-item">content-10</div>
<div class="observer-item">content-11</div>
<div class="observer-item">content-12</div>
<div class="observer-item">content-13</div>
<div class="observer-item">content-14</div>
<div class="observer-item">content-15</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
// 获取所有的元素
const elements = document.querySelectorAll(".observer-item");
console.log(elements.length);
// 使用IntersectionObserver 来检测子元素与父元素的交叉状态
const observer = new IntersectionObserver(callback);
elements.forEach((ele) => {
ele.classList.add("opaque");
// 观察元素
observer.observe(ele);
});
function callback(entries, instance) {
entries.forEach((entry) => {
// 判断元素是否出现在父元素中
// 每个对象表示一个目标元素与父元素或视窗的交叉状态信息,包括目标元素的位置、大小、可见性等
if (entry.isIntersecting) {
const element = entry.target;
element.classList.remove("opaque");
element.classList.add("come-in");
instance.unobserve(element);
}
});
}
});
</script>
<style scoped>
#abc {
width: 400px;
height: 300px;
border: 1px solid red;
overflow-y: scroll;
margin-left: 500px;
}
.observer-item {
width: 100%;
height: 100px;
line-height: 100px;
margin-bottom: 20px;
}
.observer-item:nth-child(odd) {
background-color: pink;
}
.observer-item:nth-child(even) {
background-color: skyblue;
}
.opaque {
opacity: 0;
}
.come-in {
opacity: 1;
transform: translateY(150px);
animation: come-in 1s ease forwards;
}
.come-in:nth-child(odd) {
animation-duration: 1s;
}
@keyframes come-in {
100% {
transform: translateY(0);
}
}
</style>
案例二:无限滚动(滚动到底部时加载更多内容)
这个应用应该更广泛一点,原来判断是否滚动到底的话一般是采用下面这种方案
<script>
window.onload = () => {
// 基本思路
// 滚动体条所能滚动的最大高度 + continer的高度 = 子盒子(item)的高度;
const container = document.querySelector(".container");
console.dir(container);
const item = document.querySelector(".item");
container.addEventListener("scroll",() => {
// 父盒子的高度
const clientHeight = container.clientHeight;
// 子盒子的高度(滚动盒子的高度)
const scrollHeight = container.scrollHeight;
// 滚动的最大距离
const scrollHeight_clientHeight = scrollHeight - clientHeight;
// 实时滚动距离
const scrollTop = container.scrollTop;
// 滚动的最大距离小于等于实时滚动距离时,滚动到了底部
if(scrollHeight_clientHeight <= scrollTop){
console.log("滚动到底部");
}
})
};
</script>
现在的话可以通过IntersectionObserver
来实现,基本思路是:
- 创建一个
IntersectionObserver
实例,指定观测的根元素(滚动容器)和阈值(thresholds)。 - 将最后一个加载元素作为观测目标,调用
IntersectionObserver
的observe
方法开始观测。 - 在
IntersectionObserver
的回调函数中,当最后一个加载元素与容器底部交叉时,触发加载更多的操作。 - 加载更多的操作可以是异步请求数据,更新页面内容等。
- 在加载完成后,更新最后一个加载元素的位置,继续观测。
<template>
<div v-loading="loading">
<div id="abc">
<div v-for="item in list" :key="item" class="observer-item">
content-{{ item }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const loading = ref(false);
const lastElement = ref();
onMounted(() => {
// 使用IntersectionObserver 来检测子元素与父元素的交叉状态
const observer = new IntersectionObserver(callback);
// 观察最后一个元素
lastElement.value = document.querySelector(".observer-item:last-child");
// 观察元素
observer.observe(lastElement.value);
function callback(entries, instance) {
entries.forEach((entry) => {
// 判断元素是否出现在父元素中
if (entry.isIntersecting) {
const target = entry.target;
console.log("滚动到底了:", target);
// 加载新数据
// 取消上一个元素的观察
instance.unobserve(lastElement.value);
if (list.value.length < 15) {
loading.value = true;
list.value.push(...[11, 12, 13, 14, 15]);
setTimeout(() => {
// 更新新的观察对象
lastElement.value = document.querySelector(
".observer-item:last-child"
);
observer.observe(lastElement.value);
loading.value = false;
}, 1000);
}
}
});
}
});
</script>
<style scoped>
#abc {
width: 400px;
height: 300px;
border: 1px solid red;
overflow-y: scroll;
margin-left: 500px;
}
.observer-item {
width: 100%;
height: 100px;
line-height: 100px;
margin-bottom: 20px;
}
.observer-item:nth-child(odd) {
background-color: pink;
}
.observer-item:nth-child(even) {
background-color: skyblue;
}
</style>
优点
<template>
<div v-loading="loading">
<div id="abc">
<div v-for="item in list" :key="item" class="observer-item">
content-{{ item }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const loading = ref(false);
const lastElement = ref();
onMounted(() => {
// 使用IntersectionObserver 来检测子元素与父元素的交叉状态
const observer = new IntersectionObserver(callback);
// 观察最后一个元素
lastElement.value = document.querySelector(".observer-item:last-child");
// 观察元素
observer.observe(lastElement.value);
function callback(entries, instance) {
entries.forEach((entry) => {
// 判断元素是否出现在父元素中
if (entry.isIntersecting) {
const target = entry.target;
console.log("滚动到底了:", target);
// 加载新数据
// 取消上一个元素的观察
instance.unobserve(lastElement.value);
if (list.value.length < 15) {
loading.value = true;
list.value.push(...[11, 12, 13, 14, 15]);
setTimeout(() => {
// 更新新的观察对象
lastElement.value = document.querySelector(
".observer-item:last-child"
);
observer.observe(lastElement.value);
loading.value = false;
}, 1000);
}
}
});
}
});
</script>
<style scoped>
#abc {
width: 400px;
height: 300px;
border: 1px solid red;
overflow-y: scroll;
margin-left: 500px;
}
.observer-item {
width: 100%;
height: 100px;
line-height: 100px;
margin-bottom: 20px;
}
.observer-item:nth-child(odd) {
background-color: pink;
}
.observer-item:nth-child(even) {
background-color: skyblue;
}
</style>