IntersectionObserver实现无线滚动和懒加载

最近发现一个有意思的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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值