JavaScript API: IntersectionObserver

简介

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)。
  • 将最后一个加载元素作为观测目标,调用 IntersectionObserverobserve 方法开始观测。
  • 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>

兼容性

在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知的小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值