传统的实现方法是,监听到scroll事件后,调用目标元素(绿色方块)的getBoundingClientRect()方法,得到它对应于视口左上角的坐标,再判断是否在视口之内。这种方法的缺点是,由于scroll事件密集发生,计算量很大,容易造成性能问题。
目前有一个新的 IntersectionObserver API,可以自动”观察”元素是否可见,Chrome 51+ 已经支持。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做”交叉观察器”。
一、API
它的用法非常简单。
var io = new IntersectionObserver(callback, option);
复制代码
上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。
构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点。
// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();
复制代码
上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。
二、callback 参数
目标元素的可见性变化时,就会调用观察器的回调函数callback。
callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
var io = new IntersectionObserver(
entries => {
console.log(entries);
}
);
复制代码
上面代码中,回调函数采用的是箭头函数的写法。callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。
三、案例代码
话不多说直接上代码
// 懒加载监听事件
const observer = new IntersectionObserver(
function(entires) {
entires.forEach(function(item) {
if (item.isIntersecting){ // isIntersecting是当前监听元素交叉区域是否在可视区域指定的阈值内返回的是一个布尔值
item.target.src = item.target.getAttribute('data-src')
// 这里资源加载后就停止进行观察
observer.unobserve(item.target)
}
// 卸载组件时记得关闭 observer.disconnect();
});
}
);
复制代码
由于React操作dom元素我比较习惯用refs来实现,所以我们可以这样来存储dom元素
const refs = []; 定义监听的dom元素组合
const img_contain = List.map((item,i) => {
let ref = "image_" + i; // 节点的ref名字
refs.push(ref);
return <img ref={ref} src="默认图片" data-src={item.url} key={i} />
}
复制代码
保存了dom元素后怎么实现懒加载监听事件的绑定呢?
// 懒加载初始化
initLazyImg = (refs) =>{
refs.forEach(el=>{
observer.observe(this.refs[el]);
})
}
render() {
return <div>
{/* 懒加载初始化设置监听 */}
<img style={{opacity:0}} src={refs.join(',')} onError={()=>{this.initLazyImg(refs)}} />
</div>
}
复制代码
最后但同样重要的是 注销组件的时候我们需要卸载这个监听事件,防止内存滥用
componentWillUnmount(){ //卸载组件时触发
console.log("关闭观察器")
observer.disconnect();
}
复制代码
案例代码展示
import React, { Component } from 'react';
// 懒加载监听事件
const observer = new IntersectionObserver(
function(entires) {
entires.forEach(function(item) {
if (item.isIntersecting){ // isIntersecting是当前监听元素交叉区域是否在可视区域指定的阈值内返回的是一个布尔值
item.target.src = item.target.getAttribute('data-src')
// 这里资源加载后就停止进行观察
observer.unobserve(item.target)
}
// 卸载组件时记得关闭 observer.disconnect();
});
}
);
export class GatherGallery extends Component {
componentWillUnmount(){ //卸载组件时触发
console.log("关闭观察器")
observer.disconnect();
}
// 懒加载初始化
initLazyImg = (refs) =>{
refs.forEach(el=>{
observer.observe(this.refs[el]);
})
}
render() {
const refs = []; 定义监听的dom元素组合
const img_contain = [{url:"page1.png"}].map((item,i) => {
let ref = "image_" + i; // 节点的ref名字
refs.push(ref);
return <img ref={ref} src="默认图片" data-src={item.url} key={i} />
}
return <div>
{img_contain}
{/* 懒加载初始化设置监听 */}
<img style={{opacity:0}} src={refs.join(',')} onError={()=>{this.initLazyImg(refs)}} />
</div>
}
}
复制代码
要是有更好的处理方式或者文档有什么不足的,麻烦各位大佬评论指教一下哈,感谢各位阅读