2016.11.1 追加,Firefox 52 也已经实现。
2016.11.29 追加,Firefox 的人担心目前规范不够稳定,未来很难保证向后兼容,所以,需要手动打开 dom.IntersectionObserver.enabled 才行。
IntersectionObserver API 是用来监视某个元素是否滚动进了浏览器窗口的可视区域(视口)或者滚动进了它的某个祖先元素的可视区域内。它的主要功能是用来实现延迟加载和展现量统计。先来看一段视频简介:
再来看看名字,名字里第一个单词 intersection 是交集的意思,小时候数学里面就学过:
不过在网页里,元素都是矩形的:
第二个单词 observer 是观察者的意思,和 MutationObserver 以及已死的 Object.observe 中的 observe(r) 一个意思。
下面列出了这个 API 中所有的参数、属性、方法:
// 用构造函数生成观察者实例 let observer = new IntersectionObserver((entries, observer) => { // 回调函数中可以拿到每次相交发生时所产生的交集的信息 for (let entry of entries) { console.log(entry.time) console.log(entry.target) console.log(entry.rootBounds) console.log(entry.boundingClientRect console.log(entry.intersectionRect) console.log(entry.intersectionRatio) } }, { // 构造函数的选项 root: null, threshold: [0, 0.5, 1], rootMargin: "50px, 0px" }) // 实例属性 observer.root observer.rootMargin observer.thresholds // 实例方法 observer.observe() observer.unobserve() observer.disconnect() observer.takeRecords()
然后分三小节详细介绍它们:
构造函数
new IntersectionObserver(callback, options)
callback 是个必选参数,当有相交发生时,浏览器便会调用它,后面会详细介绍;options 整个参数对象以及它的三个属性都是可选的:
root
IntersectionObserver API 的适用场景主要是这样的:一个可以滚动的元素,我们叫它根元素,它有很多后代元素,想要做的就是判断它的某个后代元素是否滚动进了自己的可视区域范围。这个 root 参数就是用来指定根元素的,默认值是 null。
如果它的值是 null,根元素就不是个真正意义上的元素了,而是这个浏览器窗口了,可以理解成 window,但 window 也不是元素(甚至不是节点)。这时当前窗口里的所有元素,都可以理解成是 null 根元素的后代元素,都是可以被观察的。
下面这个 demo 演示了根元素为 null 的用法:
<div>我藏在页面底部,请向下滚动</div> <div></div> <style> #info { position: fixed; } #target { position: absolute; top: calc(100vh + 500px); width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => { if (!target.isIntersecting) { info.textContent = "我出来了" target.isIntersecting = true } else { info.textContent = "我藏在页面底部,请向下滚动" target.isIntersecting = false } }, { root: null // null 的时候可以省略 }) observer.observe(target) </script>
需要注意的是,这里我通过在 target 上添加了个叫 isIntersecting 的属性来判断它是进来还是离开了,为什么这么做?先忽略掉,下面会有一小节专门解释。
根元素除了是 null,还可以是目标元素任意的祖先元素:
<div> <div>向下滚动就能看到我</div> <div></div> </div> <style> #root { position: relative; width: 200px; height: 100vh; margin: 0 auto; overflow: scroll; border: 1px solid #ccc; } #info { position: fixed; } #target { position: absolute; top: calc(100vh + 500px); width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => { if (!target.isIntersecting) { info.textContent = "我出来了" target.isIntersecting = true } else { info.textContent = "向下滚动就能看到我" target.isIntersecting = false } }, { root: root }) observer.observe(target) </script>
需要注意的一点是,如果 root 不是 null,那么相交区域就不一定在视口内了,因为 root 和 target 的相交也可能发生在视口下方,像下面这个 demo 所演示的:
<div> <div>慢慢向下滚动</div> <div></div> </div> <style> #root { position: relative; width: 200px; height: calc(100vh + 500px); margin: 0 auto; overflow: scroll; border: 1px solid #ccc; } #info { position: fixed; } #target { position: absolute; top: calc(100vh + 1000px); width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => { if (!target.isIntersecting) { info.textContent = "我和 root 相交了,但你还是看不见" target.isIntersecting = true } else { info.textContent = "慢慢向下滚动" target.isIntersecting = false } }, { root: root }) observer.observe(target) </script>
总结一下:这一小节我们讲了根元素的两种类型,null 和任意的祖先元素,其中 null 值表示根元素为当前窗口(的视口)。
threshold
当目标元素和根元素相交时,用相交的面积除以目标元素的面积会得到一个 0 到 1(0% 到 100%)的数值: