<script> new IntersectionObserver(() => {}, { rootMargin: "10em" // SyntaxError: Failed to construct 'Intersection': rootMargin must be specified in pixels or percent. }) </script>
rootMargin 用百分比的话就是相对根元素的真实尺寸的百分比了,比如 rootMargin: "0px 0px 50% 0px",表示根元素的尺寸向下扩大了 50%。
如果使用了负 margin,真实的根元素区域会被缩小,对应的延迟加载就会延后,比如用了 rootMargin: "-100px" 的话,目标元素滚动进根元素可视区域内部 100px 的时候才有可能触发回调。
实例
实例属性
root
该观察者实例的根元素(默认值为 null):
new IntersectionObserver(() => {}).root // null new IntersectionObserver(() => {}, {root: document.body}).root // document.body
rootMargin
rootMargin 参数(默认值为 "0px")经过序列化后的值:
new IntersectionObserver(() => {}).rootMargin // "0px 0px 0px 0px" new IntersectionObserver(() => {}, {rootMargin: "50px"}).rootMargin // "50px 50px 50px 50px" new IntersectionObserver(() => {}, {rootMargin: "50% 0px"}).rootMargin // "50% 0px 50% 0px" new IntersectionObserver(() => {}, {rootMargin: "50% 0px 50px"}).rootMargin // 50% 0px 50px 0px" new IntersectionObserver(() => {}, {rootMargin: "1px 2px 3px 4px"}).rootMargin // "1px 2px 3px 4px"
thresholds
threshold 参数(默认值为 0)经过序列化后的值,即便你传入的是一个数字,序列化后也是个数组,目前 Chrome 的实现里数字的精度会有丢失,但无碍:
new IntersectionObserver(() => {}).thresholds // [0] new IntersectionObserver(() => {}, {threshold: 1}).thresholds // [1] new IntersectionObserver(() => {}, {threshold: [0.3, 0.6]}).thresholds // [[0.30000001192092896, 0.6000000238418579]] Object.isFrozen(new IntersectionObserver(() => {}).thresholds) // true, 是个被 freeze 过的数组
这三个实例属性都是用来标识一个观察者实例的,都是让人来读的,在代码中没有太大用途。
实例方法
observe()
观察某个目标元素,一个观察者实例可以观察任意多个目标元素。注意,这里可能有同学会问:能不能 delegate?能不能只调用一次 observe 方法就能观察一个页面里的所有 img 元素,甚至那些未产生的?答案是不能,这不是事件,没有冒泡。
unobserve()
取消对某个目标元素的观察,延迟加载通常都是一次性的,observe 的回调里应该直接调用 unobserve() 那个元素.
disconnect()
取消观察所有已观察的目标元素
takeRecords()
理解这个方法需要讲点底层的东西:在浏览器内部,当一个观察者实例在某一时刻观察到了若干个相交动作时,它不会立即执行回调,它会调用 window.requestIdleCallback() (目前只有 Chrome 支持)来异步的执行我们指定的回调函数,而且还规定了最大的延迟时间是 100 毫秒,相当于浏览器会执行:
requestIdleCallback(() => { if (entries.length > 0) { callback(entries, observer) } }, { timeout: 100 })
你的回调可能在随后 1 毫秒内就执行,也可能在第 100 毫秒才执行,这是不确定的。在这不确定的 100 毫秒之间的某一刻,假如你迫切需要知道这个观察者实例有没有观察到相交动作,你就得调用 takeRecords() 方法,它会同步返回包含若干个 IntersectionObserverEntry 对象的数组(IntersectionObserverEntry 对象包含每次相交的信息,在下节讲),如果该观察者实例此刻并没有观察到相交动作,那它就返回个空数组。
注意,对于同一个相交信息来说,同步的 takeRecords() 和异步的回调函数是互斥的,如果回调先执行了,那么你手动调用 takeRecords() 就必然会拿到空数组,如果你已经通过 takeRecords() 拿到那个相交信息了,那么你指定的回调就不会被执行了(entries.length > 0 是 false)。
这个方法的真实使用场景很少,我举不出来,我只能写出一个验证上面两段话(时序无规律)的测试代码:
<script> setInterval(() => { let observer = new IntersectionObserver(entries => { if (entries.length) { document.body.innerHTML += "<p>异步的 requestIdleCallback() 回调先执行了" } }) requestAnimationFrame(() => { setTimeout(() => { if (observer.takeRecords().length) { document.body.innerHTML += "<p>同步的 takeRecords() 先执行了" } }, 0) }) observer.observe(document.body) scrollTo(0, 1e10) }, 100) </script>
回调函数