假设现在根元素和目标元素已经是相交状态,这时假如把目标元素甚至是根元素从 DOM 树中删除,或者通过 DOM 操作让目标元素不在是根元素的后代元素,再或者通过改变 CSS 属性导致根元素不再是目标元素的包含块,又或者通过 display:none 隐藏某个元素,这些操作都会让两者的相交率突然变成 0,回调函数就有可能被触发:
<div> 删除目标元素也会触发回调 <button>删除 target</button> </div> <div></div> <style> #info { position: fixed; } #target { position: absolute; top: 100px; width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => { if (!document.getElementById("target")) { info.textContent = "target 被删除了" } }) observer.observe(target) </script>
关于 iframe
在 IntersectionObserver API 之前,你无法在一个跨域的 iframe 页面里判断这个 iframe 页面或者页面里的某个元素是否出现在了顶层窗口的视口里,这也是为什么要发明 IntersectionObserver API 的一个很重要的原因。请看下图演示:
无论怎么动,无论多少层 iframe, IntersectionObserver 都能精确的判断出目标元素是否出现在了顶层窗口的视口里,无论跨域不跨域。
前面讲过根元素为 null 表示实际的根元素是当前窗口的视口,现在更明确点,应该是最顶层窗口的视口。
如果当前页面是个 iframe 页面,且和顶层页面跨域,在根元素为 null 的前提下触发回调后,你拿到的 IntersectionObserverEntry 对象的 rootBounds 属性会是 null;即便两个页面没有跨域,那么 rootBounds 属性所拿到的矩形的坐标系统和 boundingClientRect 以及 intersectionRect 这两个矩形也是不一样的,前者坐标系统的原点是顶层窗口的左上角,后两者是当前 iframe 窗口左上角。
鉴于互联网上的广告 90% 都是跨域的 iframe,我想 IntersectionObserver API 能够大大简化这些广告的延迟加载和真实曝光量统计的实现。
根元素不能是其它 frame 下的元素
如果没有跨域的话,根元素可以是上层 frame 中的某个祖先元素吗?比如像下面这样:
<div> <iframe></iframe> </div> <script> let iframeHTML = ` <div></div> <style> #target { width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => { alert("intersecting") }, { root: top.root }) observer.observe(target) <\/script>` iframe.src = URL.createObjectURL(new Blob([iframeHTML], {"type": "text/html"})) </script>
我不清楚上面这个 demo 中 root 算不算 target 的祖先包含块,但规范明确规定了这种观察操作无效,根元素不能是来自别的 frame。总结一下就是:根元素要么是 null,要么是同 frame 里的某个祖先包含块元素。
真的只是判断两个元素相交吗?
实际情况永远没表面看起来那么简单,浏览器真的只是判断两个矩形相交吗?看下面的代码:
<div> <div></div> </div> <style> #parent { width: 20px; height: 20px; background: red; overflow: hidden; } #target { width: 100px; height: 100px; background: blue; } </style> <script> let observer = new IntersectionObserver(([entry]) => { alert(`相交矩形为: ${entry.intersectionRect.width} x ${entry.intersectionRect.width}`) }) observer.observe(target) </script>
这个 demo 里根元素为当前视口,目标元素是个 100x100 的矩形,如果真的是判断两个矩形的交集那么简单,那这个相交矩形就应该是 100 x 100,但弹出来的相交矩形是 20 x 20。因为其实在相交检测之前,有个裁减目标元素矩形的步骤,裁减完才去和根元素判断相交,裁减的基本思想就是,把目标元素被“目标元素和根元素之间存在的那些元素”遮挡的部分裁掉,具体裁减步骤是这样的(用 rect 代表最终的目标元素矩形):
1、让 rect 为目标元素矩形
2、让 current 为目标元素的父元素
3、如果 current 不是根元素,则进行下面的循环:
如果 current 的 overflow 不是 visible(是 scroll 或 hidden 或 auto) 或者 current 是个 iframe 元素(iframe 天生自带 overflow: auto),则:
让 rect 等于 rect 和 current 的矩形(要排除滚动条区域)的交集
让 current 为 current 的父元素(iframe 里的 html 元素的父元素就是父页面里的 iframe 元素)
也就是说,实际上是顺着目标元素的 DOM 树一直向上循环求交集的过程。再看上面的 demo,目标元素矩形一开始是 100x100,然后和它的父元素相交成了 20x20,然后 body 元素和 html 元素没有设置 overflow,所以最终和视口做交集的是 20x20 的矩形。
关于双指缩放
移动端设备和 OS X 系统上面,允许用户使用两根手指放大页面中的某一部分:
如果页面某一部分被放大了,那同时也就意味着页面边缘上某些区域显示在了视口的外面: