IntersectionObserver
随着网页发展,对检测某些元素是否出现在可视窗相关的需求越来越多了,然而前端要检测一个元素是否可见的成本比较大。IntersectionObserver 应运而生
offsetTop < clientHeight + scrollTop
element.getBoundingClientRect().top < clientHeight
Intersection Observer API提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。
Intersection Observer API 允许你配置一个回调函数,当以下情况发生时会被调用
- 每当目标(target)元素与设备视窗或者其他指定元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根(root)。
- Observer第一次监听目标元素的时候
创建一个 IntersectionObserver对象,并传入相应参数和回调用函数,该回调函数将会在目标(target)元素和根(root)元素的交集大小超过阈值(threshold)规定的大小时候被执行。 一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let callback = (entries, io) => {}
let observer = new IntersectionObserver(callback, options);
IntersectionObserver 接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法
兼容性:IntersectionObserver
方法
- observe(element)开始监听一个目标元素
- disconnect()方法终止对所有目标元素可见性变化的观察。
- unobserve()方法命令IntersectionObserver停止对一个特定元素的观察。
- takeRecords() 返回所有观察目标的IntersectionObserverEntry对象数组,如果使用回调来监视这些更改,则无需调用此方法。调用此方法会清除挂起的相交状态列表,因此不会运行回调。
const item = document.querySelector('.item')
io.observe(item)
io.unobserve(item)
io.disconnect()
参数
options
- root
指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。 - rootMargin
根(root)元素的外边距。类似于 CSS 中的 margin 属性,比如 "10px 20px 30px 40px" (top, right, bottom, left)。如果有指定root参数,则rootMargin也可以使用百分比来取值。该属性值是用作root元素和target发生交集时候的计算交集的区域范围,使用该属性可以控制root元素每一边的收缩或者扩张。默认值为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元素中,回调函数将会被执行)。该值为1.0含义是当target完全出现在root元素中时候 回调才会被执行。
let opts = {
root: document.querySelector('.container'),
rootMargin: "-50px 0px 0px 0px",
threshold: [0, 0.5, 0.75, 1]
}
const io = new IntersectionObserver(callback, opts)
callback
当元素可见比例超过指定阈值后,会调用一个回调函数
let callback = (entries, io) => {}
const io = new IntersectionObserver(callback, opts)
- entries 是一个数组,每个成员都是一个IntersectionObserverEntry对象, 数组的长度是我们监控目标元素的个数。
- io 就是被调用的IntersectionObserver 实例。
IntersectionObserverEntry
IntersectionObserverEntry对象提供目标元素的信息,一共有六个属性。
{
time: 3893.92,
rootBounds: ClientRect {
bottom: 920,
height: 1024,
left: 0,
right: 1024,
top: 0,
width: 920
},
boundingClientRect: ClientRect {
// ...
},
intersectionRect: ClientRect {
// ...
},
intersectionRatio: 0.54,
target: element
}
| 属性 | 说明 |
|---|---|
| target | 被观察的目标元素,是一个 DOM 节点对象 |
| isIntersecting | 布尔值, 如果目标元素与root元素相交,则返回 true,否则false |
| intersectionRect | 目标元素与root元素交叉区域的信息 |
| intersectionRatio | 返回目标元素出现在root元素的比例 |
| boundingClientRect | 目标元素的边界信息 |
| rootBounds | 根元素的矩形区域的信息 |
| time | 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒 |
应用场景
懒加载
图片懒加载——当图片滚动到可见时才进行加载
let callback = (entries) => {
entries.forEach(item => {
if (item.isIntersecting) {
item.target.src = item.target.dataset.src
// 图片加载后即停止监听该元素
io.unobserve(item.target)
}
})
}
const imgList = [...document.querySelectorAll('img')]
const observer = new IntersectionObserver(callback);
imgList.forEach(function (item) {
observer.observe(item);
});
无限滚动
let callback = (entries) => {
if (entries[0].isIntersecting){
let tag = entries[0].target.dataset.tag
if(tag === 'top') {
loadNew();
} else {
loadOld();
}
}
}
const observer = new IntersectionObserver(callback);
let topTag = document.querySelector('.top_tag')
let bottomTag = document.querySelector('.bottom_tag')
observer.observe(topTag);
observer.observe(bottomTag);
scrollama
一个用 IntersectionObserver 进行滚动显示的JavaScript库 example
npm install scrollama intersection-observer --save
import 'intersection-observer'
import scrollama from "scrollama"
const scroller = scrollama();
scroller
.setup({
step: ".step",
})
.onStepEnter((response) => {
// { element, index, direction }
})
.onStepExit((response) => {
// { element, index, direction }
});
window.addEventListener("resize", scroller.resize);
属性
| 属性 | 说明 |
|---|---|
| step | 触发更改元素 |
| offset | 距视口顶部多远才能触发步骤 |
| progress | 是否触发增量步骤进度更新 false |
| threshold | 进度间隔的粒度(以像素为单位) |
| order | 如果前一步触发器被跳过,则触发它们。 true |
| once | 仅触发输入一次的步骤,然后删除侦听器。false |
| debug | 是否显示可视调试工具。false |
方法
| 方法 | 参数 |
|---|---|
| onStepEnter(callback) | 当step元素的顶部或底部边缘进入偏移阈值时触发的回调。element、index、direction |
| onStepExit(callback) | step元素的顶部或底部边缘超出偏移阈值时触发的回调。 element、index、direction |
| onStepProgress(callback) | 触发进度(0-1)达到阈值的回调。 element、index、progress |
| offsetTrigger() | 获取或设置偏移百分比 |
| resize() | 获取浏览器/ DOM的最新尺寸 |
| disable() | 停止观察触发器的变化 |
| enable() | 恢复观察触发变化 |
| destroy() | 删除所有观察者和回调函数 |
应用场景
浏览器可视区内特定位置的数据
this.scroller = scrollama()
this.scroller
.setup({
step: '.table-row',
offset: 0.5 // 取屏幕可视区域中间的数据
})
.onStepEnter(response => {
let { element, index, direction } = response
let item = this.tableData[index]
this.debonceInitTime(item)
})
debonceInitTime: _.throttle(function(item) {
if (!this.$itool.isEmpty(item?._time ?? null)) {
// do something
}
}, 500)
}
获取元素显示的进度
let scroller = scrollama();
let handleStepProgress = (response) => {
console.log(response.progress);
let progress = (response.progress*100).toFixed(0)
response.element.getElementsByTagName('span')[0].innerHTML = `${progress}%`
}
scroller
.setup({
step: ".list .item",
progress: true,
offset: 0.5
})
.onStepProgress(handleStepProgress);
参考资源
Intersection Observer
IntersectionObserver API 使用教程
scrollama