Intersection Observer API提供了异步观察目标元素与祖先元素或顶级文档viewport交集变化的方法
Intersection Observer API 会注册一个回调方法,每当期望被监视的元素进入或者退出另外一个元素的时候(或者浏览器的视口)该回调方法将会被执行
// 用法var options = {root: document.querySelector("#scrollArea"),// 该属性值是用作root元素和target发生交集时候的计算交集的区域范围rootMargin: "0px",// 交叉比// 目标(target)元素与根(root)元素之间的交叉度是交叉比(intersection ratio)// 这是目标(target)元素相对于根(root)的交集百分比的表示,它的取值在0.0和1.0之间threshold: 1.0,};// 实例化observer对象var observer = new IntersectionObserver(callback, options);// 为每个观察者配置一个目标var target = document.querySelector("#listItem");observer.observe(target);// 只要目标满足指定的阈值,就会调用回调// 回调接收IntersectionObserverEntry对象和观察者对象的列表var callback = function (entries, observer) {entries.forEach((entry) => {// Each entry describes an intersection change for one observed// target element:// entry.boundingClientRect// entry.intersectionRatio// entry.intersectionRect// entry.isIntersecting// entry.rootBounds// entry.target// entry.time});};
React用法
class Test extends React.Component {
constructor(props) {
super(props);
this.targets = [];
this.observer = null;
}
componentDidMount() {
const root = document.querySelector("#scrollArea");
const options = {
root,
rootMargin: "40px",
threshold: 1.0,
};
// 创建一个 IntersectionObserver对象,挂载到上下文,注册callback;
this.observer = new IntersectionObserver(this.observerCallback, options);
// 挂载上下文不会触发组件的re-render,这里利用element.forceUpdate强制组件刷新,目的是在ref回调里注册观察者
this.forceUpdate();
}
observerCallback = (entries) => {
// 迭代观察者列表通过对比ref应用是否相同,来更新intersection(是否交集)值
this.targets.forEach((target) => {
for (const entry of entries) {
对比引用
if (target.ref === entry.target) {
target.intersecting = entry.isIntersecting;
break;
}
}
});
this.forceUpdate();
};
// 根据观察者对象决定如何渲染组件
isRenderCell = (target) => {
return target ? target.intersecting : false;
};
getTarget = (index) => {
const nameIndex = index;
return this.targets.find((t) => t.index === nameIndex);
};
// ref回调注册观察者对象
getNameCellRef = (ref, index, target) => {
let refHeight = lodash.get(ref, "parentNode", {});
// 通过dom api getBoundingClientRect获取列表子元素的属性
refHeight =
refHeight.getBoundingClientRect &&
refHeight.getBoundingClientRect().height;
// 因为回调每次re-render都会执行,所以这里通过条件执行注册
if (!target && refHeight && this.observer) {
this.observer.observe(ref);
this.targets.push({ ref, intersecting: true, index, height: refHeight });
}
};
render() {
console.log(this.targets, "this.targets");
return (
<div id="scrollArea">
<Table
primaryKey="name"
dataSource={data}
maxBodyHeight={600}
fixedHeader
>
<Table.Column
title="Name"
dataIndex="name"
cell={(name, key) => {
// 通过索引值获取target对象
const target = this.getTarget(key);
return (
<div ref={(ref) => this.getNameCellRef(ref, key, target)}>
{this.isRenderCell(target) ? (
name
) : (
<div style={{ height: target ? target.height : 0 }}>
{key}
</div>
)}
</div>
);
}}
/>
</Table>
</div>
);
}
}
