创建对象
var io = new IntersectionObserver(callback, option);
IntersectionObserver
是浏览器原生提供的构造函数,接受两个参数:callback
是可见性变化时的回调函数,option
是配置对象(该参数可选)。
构造函数的返回值是一个观察器实例。实例的observe
方法可以指定观察哪个 DOM 节点。
// 开始观察,参数是观察对象元素 io.observe(document.getElementById('example')); // 停止观察 io.unobserve(element); // 关闭观察器 io.disconnect();
如果要观察多个节点,就要多次调用这个方法。
io.observe(elementA);
io.observe(elementB);
callback 参数
目标元素的可见性变化时,就会调用观察器的回调函数callback
。callback
一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。callback
函数的参数(entries
)是一个数组,每个成员都是一个IntersectionObserverEntry
对象
const intersectionObserver = new IntersectionObserver((entries) => { console.log(entries);# entries 返回数组
for (entry of entries)
{
if (entry.intersectionRatio > 0)
{
addAnimationClass(entry.target, animationClass);
}
else {
console.log(animationClass); removeAnimationClass(entry.target, animationClass);
}
}
}
IntersectionObserverEntry
对象一共有六个属性
- boundingClientRect:目标元素的矩形区域的信息
- intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
- intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
- rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
- target:被观察的目标元素,是一个 DOM 节点对象
- time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
Option 对象
IntersectionObserver
构造函数的第二个参数是一个配置对象。它可以设置以下属性。
6.1 threshold 属性
threshold
属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0]
,即交叉比例(intersectionRatio
)达到0
时触发回调函数。
new IntersectionObserver( entries => {/* ... */}, { threshold: [0, 0.25, 0.5, 0.75, 1] } );
用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]
就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。
6.2 root 属性,rootMargin 属性
很多时候,目标元素不仅会随着窗口滚动,还会在容器里面滚动(比如在iframe
窗口里滚动)。容器内滚动也会影响目标元素的可见性,IntersectionObserver API 支持容器内滚动。root
属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。
var option = { root: document.querySelector('.container'), rootMargin: "500px 0px" }; var observer = new IntersectionObserver( callback, option );
上面代码中,除了root
属性,还有rootMargin
属性。后者定义根元素的margin
,用来扩展或缩小rootBounds
这个矩形的大小,从而影响intersectionRect
交叉区域的大小。它使用CSS的定义方法,比如10px 20px 30px 40px
,表示 top、right、bottom 和 left 四个方向的值。
这样设置以后,不管是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器。
惰性加载案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <!-- <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6"></script> --> <title>IntersectionObserver</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } div { margin: 0 auto; max-width: 100%; width: 600px; } #top { height: 1200px; background-color: #aaaaaa; } #middle { margin-top: 200px; opacity: 0; height: 400px; background-color: #000000; } #bottom { height: 300px; background-color: #333; } #middle.move, #bottom.move { animation: movefromleft 2s; animation-fill-mode: forwards; } @keyframes movefromleft { from { opacity: 0; transform: translateX(-300px); } to { opacity: 1; transform: translateX(0px); } } </style> </head> <body> <div id="top"></div> <div id="middle"></div> <div id="bottom"></div> </body> <script> window.onload = (event) => { const middle = document.getElementById('middle'), bottom = document.getElementById('bottom'); const animationClass = 'move'; // 创建监听 const intersectionObserver = new IntersectionObserver((entries) => { console.log(entries); for (entry of entries) { if (entry.intersectionRatio > 0) { addAnimationClass(entry.target, animationClass); } else { console.log(animationClass); removeAnimationClass(entry.target, animationClass); } } }); // 添加动画class的操作 function addAnimationClass(elem, animationClass) { elem.className.includes(animationClass) ? 1 : elem.className = elem.className + ' ' + animationClass; } // 移除动画class的操作 function removeAnimationClass(elem, animationClass) { elem.className.includes(animationClass) ? elem.className = elem.className.replace(animationClass, '') : 1; console.log(elem.className); } // 开启监听 intersectionObserver.observe(middle); intersectionObserver.observe(bottom); } </script> </html>
const eles = document.querySelectorAll('img[data-src]') const observer = new IntersectionObserver( entries => { entries.forEach(entry => { if (entry.intersectionRatio > 0) { let oImg = entry.target oImg.src = oImg.getAttribute('data-src') observer.unobserve(oImg) } }) }, { root: document.getElementById('list') }) eles.forEach(item => { observer.observe(item) })
注意:
IntersectionObserver API 是异步的,不随着目标元素的滚动同步触发。
规格写明,IntersectionObserver
的实现,应该采用requestIdleCallback()
,即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行。