【Intersection Observer 介绍】React 中使用 Intersection Observer 和 LazyLoad来实现无限滚动,分页和懒加载
实现前需要了解这些Intersection Observer API根据MDN的文档,“Intersection Observer API 提供了一种异步观察目标元素与祖先元素或顶级文档viewport的交集中的变化的方法。”。通俗讲,就是一个能够监听元素是否到了当前视口的事件,一步到位使用例子对于实现一张图片的懒加载,我们可以采用ntersection Observer + dataSet。假如
实现前需要了解这些
Intersection Observer API
根据MDN的文档,“Intersection Observer API 提供了一种异步观察目标元素与祖先元素或顶级文档viewport的交集中的变化的方法。”。
通俗讲,就是一个能够监听元素是否到了当前视口的事件,一步到位
使用场景
- 页面滚动时懒加载图片
例如,对于实现一张图片的懒加载,我们可以采用ntersection Observer + dataSet。假如我们的电脑分辨率是1920*1080的话,现在有个图片,他距离顶部有1500px,我们希望在它在滚动到可视区域时加载,即懒加载 - 实现无线滚动页面(Infinite scrolling)
- 根据某个元素是否出现在视窗从而执行某些逻辑
HTML
<img data-src="yuren.jpg" alt="" style="width: 300px;height: 300px;background-color: antiquewhite;margin-top: 1500px;">
JS
该方法接收一个回调函数,并且该函数的第一个参数是IntersectionObserverEntry接口的实例,实例描述了目标元素与其根元素容器在某一特定过渡时刻的交叉状态,我们使用该实例的一个属性intersectionRatio,该属性返回intersectionRect
与 boundingClientRect
的比例值, 判断该属性,为真则替换data-src为src,完成懒加载功能。
const observer = new IntersectionObserver((changes) => {
// changes: 目标元素集合
changes.forEach((change) => {
// intersectionRatio
if (change.isIntersecting) {
const img = change.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
实例化方法
IntersectionObserver.disconnect()
使 IntersectionObserver 对象停止监听目标。
IntersectionObserver.observe()
使 IntersectionObserver 开始监听一个目标元素。
IntersectionObserver.takeRecords()
返回所有观察目标的 IntersectionObserverEntry 对象数组。
IntersectionObserver.unobserve()
使 IntersectionObserver 停止监听特定目标元素
兼容性
兼容性目前享有被大多数浏览器支持(ie bushi)
LazyLoad插件
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';
const App = () => {
return (
<div className="list">
<LazyLoad height={200}>
<img src="tiger.jpg" />
/*开箱即用支持延迟加载图像,无需额外配置,基本使用的话设置“height”即可 */
</LazyLoad>
<LazyLoad height={200} once >
/* once属性指的是一旦该元素进入可视区域范围,元素DOM被加载,
LazyLoad之后就不会再将它重新回缩,但在后面项目我们不设置这种参数 */
<MyComponent />
</LazyLoad>
<LazyLoad height={200} offset={100}>
/* 此组件将在顶部时加载边缘距离视口为100px。这会让用户无法感知到懒加载 */
<MyComponent />
</LazyLoad>
<LazyLoad>
<MyComponent />
</LazyLoad>
</div>
);
};
ReactDOM.render(<App />, document.body);
更多props参数可以去 github地址
具体应用场景
解决的痛点:由于异常数据有时候会有几w条,我们需要对其进行card list渲染,因此需要滚动分页和懒加载来进行优化
useEffect Hooks
首先,我们定义一个reducer函数–pageReducer
。这个Reducer处理两个Action。
NEXT_PAGE
Action 将page分页+1,接口将发送分页请求。FIRST_PAGE
Action 设置page为1。
下一步是将这个reducer连接到useReducer 钩子函数上。一旦完成该步,我们就会得到pager(是分页数据), pagerDispatch(更新reducer
对象的函数)。
function App() {
function pageReducer(state: any, action: { type: string }) {
switch (action.type) {
case 'NEXT_PAGE':
return { ...state, page: state.page + 1 };
case 'FIRST_PAGE': {
return { ...state, page: 1 }; }
default: return state;
}
}
const [pager, pagerDispatch] = useReducer(pageReducer, { page: 1, limit: 16 });
//...
}
IntersectionObserver API使用
接下来我们使用IntersectionObserver进行监听,我们定义一个变量bottomBoundaryRef
,并将其值设置为useRef(null)
。useRef
可以让变量在整个组件重新渲染时保留其值,也就是说,当包含的组件重新渲染时,变量的当前值会持续存在。改变其值的唯一方法是重新分配该变量的.current
属性。
在我们的例子中,bottomBoundaryRef.current
的起始值为null
。随着页面不断的滚动,当我们的页面到底时,将其当前属性设置为节点<div id='page-bottom-border'>
。
代码如下
let bottomBoundaryRef = useRef(null);
const useInfiniteScroll = (scrollRef: any, dispatch: any) => {
const scrollObserver = useCallback( node => {
new IntersectionObserver(entries => {
entries.forEach(en => {
if (en.intersectionRatio > 0) {
dispatch({ type: 'NEXT_PAGE' });
}
});
}).observe(node);
}, [dispatch] );
useEffect(() => {
if (scrollRef.current) {
scrollObserver(scrollRef.current);
}
}, [scrollObserver, scrollRef]);
而在渲染时,根据分辨率划分3个或者4个card
LazyLoad设置scrollContainer为滚动区域荣容器,height为DOM高度
{
dataSource.map((j: any, index: any) => {
return (
<LazyLoad offset={420}
style={{
display: 'flex',
flexWrap: 'wrap',
marginLeft: index % colNum === 0 ? 0 : '16px',
width: window.innerWidth > 1440 ? 'calc((100% - 48px) / 4)' : 'calc((100% - 32px) / 3)'
}}
height={405}
key={`lazy${j.id}`}
scroll={true}
scrollContainer=".container" >
...
</LazyLoad> ); }
)}
<div id="page-bottom-boundary" ref={bottomBoundaryRef} />
{loading && <Loading />
}
整个功能的流程总结如下
与观测区域产生交集 ==> 派发
NEXT_PAGE
action ==> 将pager.page的值增量1 ==> useEffect钩子函数执行axios请求分页调用 ==> axios请求分页调用执行 ==> 返回的数据被concat到原数据列表中。
这里用到concat
而没有用到es6+的...
语法,因为需要频繁触发该操作,做了concat
比...
的对比实验,发现concat
性能好。
结论
H5不断的新增的API,便利着我们实现更多功能,以前实现懒加载,可能需要
window.scroll
监听 Element.getBoundingClientRect()
并使用 _.throttle
节流。**
这一套组合拳太复杂了,于是浏览器出了一个三合一事件: IntersectionObserver
API,一个能够监听元素是否到了当前视口的事件,一步到位!
希望能对你有用,有哪里不对的也请大佬们指点,喜欢的可以点赞支持
更多推荐
所有评论(0)