React 和 JavaScript 中的未来无限滚动
在现代用户体验中,无限滚动的使用场景有很多。以前,开发人员使用视口和元素的高度来确定元素在视口中的交点。这种方法的主要问题是,计算函数会在主队列中执行,这会导致应用运行缓慢且不稳定。几天前,我偶然发现了 Intersection Observer API,它可以用于以下应用:
- 页面滚动时延迟加载图像或其他内容。
- 实现“无限滚动”网站,滚动时会加载和呈现越来越多的内容,以便用户不必翻阅页面。
- 报告广告可见度以计算广告收入。
- 根据用户是否会看到结果来决定是否执行任务或动画过程。
交叉观察器 API 提供了一种异步观察目标元素与祖先元素或顶级文档视口的交叉点变化的方法。
源代码位于https://github.com/dhairyanadapara/infinite-scoller-example
演示链接:https://dhairyanadapara.github.io/infinite-scoller-example/
让我们从解决方案开始。
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
};
}
componentDidMount() {
this.createObserver();
}
createObserver = () => {
let options = {
root: null,
rootMargin: " 40px",
threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
};
const boxElement = document.getElementById("loading");
const observer = new IntersectionObserver(
this.handleIntersect,
options
);
observer.observe(boxElement);
};
handleIntersect = (entries, observer) => {
const { arr } = this.state;
entries.forEach((entry) => {
console.log(entry.intersectionRatio);
if (entry.intersectionRatio > 0) {
this.setState({
arr: arr.concat([
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
]),
});
}
});
};
render() {
const { arr } = this.state;
return (
<div className="App" id="app">
<div id="infinite-container">
<div class="cards-list" id="card-list">
{arr.map((x) => (
<div class="card 1">
<div class="card_image">
{" "}
<img src="https://i.redd.it/b3esnz5ra34y.jpg" />
</div>
<div class="card_title title-white">
<p>Card Title</p>
</div>
</div>
))}
</div>
<div id="loading" style={{ height: "100px" }}>
Loading
</div>
</div>
</div>
);
}
}
export default App;
如你所见,我们使用了 React 类组件,因此很容易理解。你也可以使用函数式组件。
让我们从了解观察者初始化开始。
createObserver = () => {
let options = {
root: null,
rootMargin: " 40px",
threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
};
const boxElement = document.getElementById("loading");
const observer = new IntersectionObserver(this.handleIntersect, options);
observer.observe(boxElement);
};
IntersectionObserver
接受 2 个参数。
-
options
是 Intersection Observer 的配置项。它有 3 个属性:- root:要用作视口的元素。如果要使用浏览器的视口,请传递
null
。 - rootMargin :计算交点时将偏移量添加到目标矩形
- threshold:阈值列表,按数字升序排列。
intersectionRatio
超过阈值时将调用回调函数。
- root:要用作视口的元素。如果要使用浏览器的视口,请传递
-
回调
回调有两个参数:- IntersectionObserverEntry 的条目列表,描述目标元素与根元素之间的交集
- 观察者IntersectionObserver 对象与我们在 createObserver 中创建的相同
这里我们观察的是位于卡片列表底部的正在加载的元素。在本例中,观察器中只有一个目标元素,因此在条目中只会获取一个对象。如果多个目标元素指向同一个观察器,则会获取更多条目。
handleIntersect = (entries, observer) => {
const { arr } = this.state;
entries.forEach((entry) => {
if (entry.intersectionRatio > 0) {
this.setState({
arr: arr.concat([
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
]),
});
}
});
};
IntersectionObserverEntry 对象具有多个属性boundingClientRect
,如intersectionRatio
,,,,,,,intersectionRect
。isIntersecting
rootBounds
target
time
主要属性有:
- intersectionRatio:返回intersectionRect与boundingClientRect的百分比
- isIntersecting:返回目标和根是否相交。
- 目标:当我们将多个目标附加到同一个观察者时,这是一个重要的属性
在上面的函数中,我们迭代了所有条目,并检查了交集比例是否大于 0,这是否意味着目标元素与根元素或视口元素存在交集。如你所见,我们正在观察loading
位于 card-list 元素底部的 id 为 的元素。因此,当我们向下滚动到加载元素时,会发生交集,并且状态会相应地更新。
在这种情况下,我们没有进行任何 API 调用,因此数据更新速度很快。如果是获取请求,最好使用rootMargin
。您还可以根据需要更新阈值。