利用IntersectionObserver构造函数观察的页面元素的可见性,写一个实现懒加载的vue自定义指令

最近项目中遇到了懒加载需求,使用了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 对象有这些信息

  1. boundingClientRect: 一个 DOMRectReadOnly 对象,表示目标元素的边界信息。
  2. intersectionRatio: 目标元素的交叉区域与目标元素的整个区域的比例(从 0 到 1)。
  3. intersectionRect: 一个 DOMRectReadOnly 对象,表示目标元素与视口(或根元素)的交叉区域。
  4. isIntersecting: 一个布尔值,表示目标元素是否与视口(或根元素)交叉。
  5. rootBounds: 一个 DOMRectReadOnly 对象,表示根元素的边界信息。如果没有指定根元素,则为视口的边界。
  6. target: 被观察的目标元素。
  7. 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,可以避免潜在的内存泄漏和其他意外行为,使应用程序更加健壮。这有几个原因:
  1. 保证所有观察停止:如果有遗漏的情况,某些元素未被 unobservedisconnect 可以确保所有观察都停止。
  2. 释放资源: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,可以显著提升网页的性能和用户体验。

 完结撒花!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值