# intersecion-oberver **Repository Path**: w_mao/intersecion-oberver ## Basic Information - **Project Name**: intersecion-oberver - **Description**: 学习 intersecionOberver API - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-06-21 - **Last Updated**: 2021-07-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # IntersectionObserver API Intersection Observer API提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。 什么是相交? 这是数学中的相交表示  而在网页中,元素都是矩形的,即使是不规则的形状,也会被当做最小矩形来使用  在过去,检测两个元素的相交通常要用到事件监听,并且需要频繁调用Element.getBoundingClientRect() 方法以获取相关元素的边界信息。事件监听和调用 Element.getBoundingClientRect() 都是在主线程上运行,因此频繁触发、调用可能会造成性能问题。这种检测方法极其怪异且不优雅。 而 Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时(或者 viewport ),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样,我们网站的主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。 ## 基本语法 创建一个 IntersectionObserver对象,回调用函数和配置参数,该回调函数将会在目标(target)元素和根(root)元素的交集大小超过阈值(threshold)规定的大小时候被执行。 ```javascript let options = { root: document.querySelector('#rootBox') || null, rootMargin: '0px', threshold: 1.0 } let observer = new IntersectionObserver(callback, options); observer.observe(document.querySelector('#targetBox')) ``` ### options 对象 * `root`: 指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。 * `rootMargin` 根(root)元素的外边距。类似于 CSS 中的 margin 属性,比如 "10px 20px 30px 40px" (top, right, bottom, left)。如果有指定root参数,则rootMargin也可以使用百分比来取值。该属性值是用作root元素和target发生交集时候的计算交集的区域范围,使用该属性可以控制root元素每一边的收缩或者扩张。 使用了负 margin,真实的根元素区域就会被缩小 ,默认值为0。 * `threshold` 可以是单一的number也可以是number数组,target元素和root元素相交程度达到该值的时候IntersectionObserver注册的回调函数将会被执行。如果你只是想要探测当target元素的在root元素中的可见性超过50%的时候,你可以指定该属性值为0.5。如果你想要target元素在root元素的可见程度每多25%就执行一次回调,那么你可以指定一个数组[0, 0.25, 0.5, 0.75, 1]。默认值是0(意味着只要有一个target像素出现在root元素中,回调函数将会被执行)。 上述代码中threshold值为1.0含义是:当target完全出现在root元素中时候 回调才会被执行。 下面的动画演示了当 threshold 参数为 [0, 0.5, 1] 时,向下滚动页面时回调函数是在何时触发的: [点击查看](example/video/in.mp4) 不仅当目标元素从视口外移动到视口内时会触发回调,从视口内移动到视口外也会: [点击查看](example/video/out.mp4) ### 回调函数 只要目标满足为IntersectionObserver指定的阈值,就会调用回调。回调接收 IntersectionObserverEntry 对象和观察者的列表: ```javascript let callback =(entries, observer) => { entries.forEach(entry => { // Each entry describes an intersection change for one observed // target element: // entry.boundingClientRect // entry.intersectionRect // entry.intersectionRatio // entry.isIntersecting // entry.rootBounds // entry.target // entry.time }); }; ``` 因为一个observer实例可以同时监测多个目标元素,所以第一个参数包含有若干个 IntersectionObserverEntry 对象的数组,每个 IntersectionObserverEntry 对象都代表一次相交,它的各个属性就表示了那次相交的各种信息。 第二个参数就是观察者实例本身,一般没用,因为实例通常我们已经赋值给一个变量了,而且回调函数里的 this 也是那个实例。 #### IntersectionObserverEntry 对象 IntersectionObserverEntry接口 (从属于 Intersection Observer API )描述了目标元素与其根元素容器在某一特定过渡时刻的交叉状态. IntersectionObserverEntry 的实例作为 entries 参数被传递到一个 IntersectionObserver 的回调函数中; 此外, 这些对象还能通过调用IntersectionObserver.takeRecords() 来获取(下面会讲). 它具有以下几个只读属性: * `boundingClientRect`: 发生相交时目标元素的矩形信息,等价于 `target.getBoundingClientRect()`。 * `intersectionRect`: 发生相交时**相交区域**的矩形信息 * `intersectionRatio`: 0 到 1 的数值,表示**相交区域**的矩形面积与目标元素的矩形面积之比。 * `isIntersecting`: 返回一个布尔值, 如果目标元素与root元素相交,则返回 true .如果返回 true, 则 IntersectionObserverEntry 描述了变换到交叉时的状态; 如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态. * `rootBounds`: 表示发生相交时root根元素的矩形信息, 计算方式与 `Element.getBoundingClientRect()` 相同 * `target`: 与根元素出现相交区域改变的元素,因为一个根元素可以观察多个目标元素,所以这个 target 不一定是哪个元素。 * `time`: 返回一个记录从 IntersectionObserver 的时间原点(time origin)到交叉被触发的时间的时间戳,也就是相交发生时距离IntersectionObserver实例创建时的毫秒数(有小数)。 通常会用到`isIntersecting`直接来判断目标元素是否与根元素相交,从而执行一些操作。 ### IntersectionObserver 实例属性 * `root`: 所监听对象的具体祖先元素(element)。如果未传入值或值为null,则默认使用顶级文档的视窗。 * `rootMargin`: rootMargin 参数(默认值为 "0px")经过序列化后的值: ```javascript 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 的实现里数字的精度会有丢失,但无碍: ```javascript 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 过的数组 ``` ### IntersectionObserver 实例方法 * `disconnect()`: 让`IntersectionObserver`对象停止一切监听工作 * `observe()`:让`IntersectionObserver`开始监听一个目标元素,一个`IntersectionObserver`对象可以同时监听多个目标元素 * `unobserve()`:使`IntersectionObserver`停止监听特定目标元素。 * `takeRecords()`: 返回所有观察目标的`IntersectionObserverEntry`对象数组。 `IntersectionObserver` 的方法`takeRecords()` 返回一个 `IntersectionObserverEntry` 对象数组, 每个对象的目标元素都包含每次相交的信息, 可以显式通过调用此方法或隐式地通过观察者的回调自动调用. 但要注意**如果使用回调函数来监视相交情况,那就不要调用此方法。因为调用此方法会清除挂起的相交状态列表,就不会去运行回调函数。** #### `takeRecords()` 方法的特殊性 理解这个方法需要讲点底层的东西:在浏览器内部,当一个观察者实例在某一时刻观察到了若干个相交动作时,它不会立即执行回调,它会调用 `window.requestIdleCallback()` (目前只有 Chrome 支持)来异步的执行我们指定的回调函数,而且还规定了最大的延迟时间是 100 毫秒,相当于浏览器会执行: ```javascript requestIdleCallback(() => { if (entries.length > 0) { callback(entries, observer) } }, { timeout: 100 }) ``` 你的回调可能在随后 1 毫秒内就执行,也可能在第 100 毫秒才执行,这是不确定的。在这不确定的 100 毫秒之间的某一刻,假如你迫切需要知道这个观察者实例有没有观察到相交动作,你就得调用 `takeRecords()` 方法,它会同步返回包含若干个 `IntersectionObserverEntry` 对象的数组,如果该观察者实例此刻并没有观察到相交动作,那它就返回个空数组。 注意,对于同一个相交信息来说,同步的 `takeRecords()` 和异步的回调函数是互斥的,如果回调先执行了,那么你手动调用 `takeRecords()` 就必然会拿到空数组,如果你已经通过 `takeRecords()` 拿到那个相交信息了,那么你指定的回调就不会被执行了(`entries.length > 0` 是 false)。 这个方法的真实使用场景很少,举一个特殊的例子 ```javascript const scrollBox = document.getElementById('scrollBox') const targetBox = document.getElementById('targetBox') const observer = new IntersectionObserver(function () {}, { root: scrollBox, threshold: 0.5 }) observer.observe(targetBox) console.log('takeRecords', observer.takeRecords()) ``` ## `IntersectionObserver API` 的使用场景 * 图片懒加载——当图片滚动到可见时才进行加载 * 内容无限滚动——也就是用户滚动到接近内容底部时直接加载更多,而无需用户操作翻页,给用户一种网页可以无限滚动的错觉 * 检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况(埋点中常使用) * 在用户看见某个区域时执行任务或播放动画 ... ## 注意点 `IntersectionObserver API` 是异步的,不随着目标元素的滚动同步触发。 规格写明,`IntersectionObserver`的实现,底层采用了`requestIdleCallback()`,即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行。 ### 真的只是判断两个元素相交吗? ```html