new IntersectionObserver(function(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) } })
回调函数共有两个参数,第二个参数就是观察者实例本身,一般没用,因为实例通常我们已经赋值给一个变量了,而且回调函数里的 this 也是那个实例。第一个参数是个包含有若干个 IntersectionObserverEntry 对象的数组,也就是和 takeRecords() 方法的返回值一样。每个 IntersectionObserverEntry 对象都代表一次相交,它的属性们就包含了那次相交的各种信息。entries 数组中 IntersectionObserverEntry 对象的排列顺序是按照它所属的目标元素当初被 observe() 的顺序排列的。
time
相交发生时距离页面打开时的毫秒数(有小数),也就是相交发生时 performance.now() 的返回值,比如 60000.560000000005,表示是在页面打开后大概 1 分钟发生的相交。在回调函数里用 performance.now() 减去这个值,就能算出回调函数被 requestIdleCallback 延迟了多少毫秒:
<script> let observer = new IntersectionObserver(([entry]) => { document.body.textContent += `相交发生在 ${performance.now() - entry.time} 毫秒前` }) observer.observe(document.documentElement) </script>
你可以不停刷新上面这个 demo,那个毫秒数最多 100 出头,因为浏览器内部设置的最大延迟就是 100。
target
相交发生时的目标元素,因为一个根元素可以观察多个目标元素,所以这个 target 不一定是哪个元素。
rootBounds
一个对象值,表示发生相交时根元素可见区域的矩形信息,像这样:
{ "top": 0, "bottom": 600, "left": 0, "right": 1280, "width": 1280, "height": 600 }
boundingClientRect
发生相交时目标元素的矩形信息,等价于 target.getBoundingClientRect()。
intersectionRect
根元素和目标元素相交区域的矩形信息。
intersectionRatio
0 到 1 的数值,表示相交区域占目标元素区域的百分比,也就是 intersectionRect 的面积除以 boundingClientRect 的面积得到的值。
贴边的情况是特例
上面已经说过,IntersectionObserver API 的基本工作原理就是检测相交率的变化。每个观察者实例为所有的目标元素都维护着一个上次相交率(previousThreshold)的字段,在执行 observe() 的时候会给 previousThreshold 赋初始值 0,然后每次检测到新的相交率满足(到达或跨过)了 thresholds 中某个指定的临界值,且那个临界值和当前的 previousThreshold 值不同,就会触发回调,并把满足的那个新的临界值赋值给 previousThreshold,依此反复,很简单,对吧。
但是不知道你有没有注意到,前面讲过,当目标元素从距离根元素很远到和根元素贴边,这时也会触发回调(假如 thresholds 里有 0),但这和工作原理相矛盾啊,离的很远相交率是 0,就算贴边,相交率还是 0,值并没有变,不应该触发回调啊。的确,这和基本工作原理矛盾,但这种情况是特例,目标元素从根元素外部很远的地方移动到和根元素贴边,也会当做是满足了临界值 0,即便 0 等于 0。
还有一个反过来的特例,就是目标元素从根元素内部的某个地方(相交率已经是 1)移动到和根元素贴边(还是 1),也会触发回调(假如 thresholds 里有 1)。
目标元素宽度或高度为 0 的情况也是特例
很多时候我们的目标元素是个空的 img 标签或者是一个空的 div 容器,如果没有设置 CSS,这些元素的宽和高都是 0px,那渲染出的矩形面积就是 0px2,那算相交率的时候就会遇到除以 0 这种在数学上是非法操作的问题,即便在 JavaScript 里除以 0 并不会抛异常还是会得到 Infinity,但相交率一直是 Infinity 也就意味着回调永远不会触发,所以这种情况必须特殊对待。
特殊对待的方式就是:0 面积的目标元素的相交率要么是 0 要么是 1。无论是贴边还是移动到根元素内部,相交率都是 1,其它情况都是 0。1 到 0 会触发回调,0 到 1也会触发回调,就这两种情况:
由于这个特性,所以为 0 面积的目标元素设置临界值是没有意义的,设置什么值、设置几个,都是一个效果。
但是注意,相交信息里的 intersectionRatio 属性永远是 0,很烧脑,我知道:
<div></div> <script> let observer = new IntersectionObserver(([entry]) => { alert(entry.intersectionRatio) }) observer.observe(target) </script>
observe() 之前就已经相交了的情况是特例吗?