这些情况下 IntersectionObserver API 都不会做专门处理,无论是根元素还是目标元素,它们的矩形都是缩放前的真实尺寸(就像 getBoundingClientRect() 方法所表现的一样),而且即便相交真的发生在了那些因缩放导致用户眼睛看不到的区域内,回调函数也照样触发。如果你用的 Mac 系统,你现在就可以测试一下上面的任意一个 demo。
关于垃圾回收
一个观察者实例无论对根元素还是目标元素,都是弱引用的,就像 WeakMap 对自己的 key 是弱引用一样。如果目标元素被垃圾回收了,关系不大,浏览器就不会再检测它了;如果是根元素被垃圾回收了,那就有点问题了,根元素没了,但观察者实例还在,如果这时使用哪个观察者实例会怎样:
<div></div> <div></div> <script> let observer = new IntersectionObserver(() => {}, {root: root}) // root 元素一共有两个引用,一个是 DOM 树里的引用,一个是全局变量 root 的引用 document.body.removeChild(root) // 从 DOM 树里移除 root = null // 全局变量置空 setTimeout(() => { gc() // 手动 gc,需要在启动 Chrome 时传入 --js-flags='--expose-gc' 选项 console.log(observer.root) // null,观察者实例的根元素已经被垃圾回收了 observer.observe(target) // Uncaught InvalidStateError: observe() called on an IntersectionObserver with an invalid root,执行 observer 的任意方法都会报错。 }) </script>
也就是说,那个观察者实例也相当于死了。这个报错是从 Chrome 53 开始的(我提议的),51 和 52 上只会静默失败。
后台标签页
由于 Chrome 不会渲染后台标签页,所以也就不会检测相交了,当你切换到前后才会继续。你可以通过 Command/Ctrl + 左键打开上面任意的 demo 试试。
吐槽命名
threshold 和 thresholds
构造函数的参数里叫 threshold,实例的属性里叫 thresholds。道理我都懂,前者既能是一个单数形式的数字,也能是一个复数形式的数组,所以用了单数形式,而后者序列化出来只能是个数组,所以就用了复数了。但是统一更重要吧,我觉的都用复数形式没什么问题,一开始研究这个 API 的时候我尝试传了 {thresholds: [1]},试了半天才发现多了个 s,坑死了。
disconnect
什么?disconnect?什么意思?connect 什么了?我只知道 observe 和 unobserve,你他么的叫 unobserveAll 会死啊。这个命名很容易让人不明觉厉,结果是个很简单的东西。叫这个其实是为了和 MutationObserver 以及 PerformanceObserver 统一。
rootBounds & boundingClientRect & intersectionRect
这三者都是返回一个矩形信息的,本是同类,但是名字没有一点规律,让人无法记忆。我建议叫 rootRect & targetRect & intersectionRect,一遍就记住了,真不知道写规范的人怎么想的。
Polyfil
写规范的人会在 Github 仓库上维护一个 polyfill,目前还未完成。但 polyfill 显然无法支持 iframe 内元素的检测,不少细节也无法模拟。
其它浏览器实现进度
Firefox:
Safari:
Edge:https://developer.microsoft.com/en-us/microsoft-edge/platform/status/intersectionobserver
总结
虽然目前该 API 的规范已经有一年历史了,但仍非常不完善,大量的细节都没有规定;Chrome 的实现也有半年了,但还是有不少 bug(大多是疑似 bug,毕竟规范不完善)。因此,本文中有些细节我故意略过,比如目标元素大于根元素,甚至根元素面积为 0,支不支持 svg 这些,因为我也不知道什么是正确的表现。
2016-8-2 追记:今天被同事问了个真实需求,“统计淘宝搜索页面在页面打开两秒后展现面积超过 50% 的宝贝”,我立刻想到了用 IntersectionObserver: