效果
《今日头条》网页里页面向下滚动时,新闻会不断的追加。查看了 dom 元素,他们是把新请求到的新闻不断地追加到 ul 里。
思路
1. 判断网页是否滚动到了底部,并且当前没有请求在执行
2. 请求数据
3. 追加数据
细节
1. 可以规定距离页面底部多少距离算底部(这里用的是20),在这个区域内会请求数据
2. 用一个状态锁 isFetching 判断当前有没有请求正在执行,有请求在执行的话就不发请求了,以免数据混乱
3. 没有更多数据时的那个一次性定时器一定要清理
4. 用节流函数优化了滚动事件,规定300毫秒执行一次
完整代码
基于 React
index.scss
.container {
width: 300px;
margin: 0 auto;
font-size: 20px;
position: relative;
.ul {
list-style: none;
margin: 0;
padding: 0;
}
.item {
height: 100px;
margin-bottom: 10px;
background-color: lightblue;
&:last-child {
margin-bottom: 0;
}
}
.bottomTip {
font-size: 12px;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 20px;
text-align: center;
background-color: #ccc;
color: #fff;
}
}
index.jsx
import React, {Component} from 'react';
import './index.scss';
import request from '@/server/request';
export default class InfiniteScroll extends Component {
constructor(props) {
super(props);
this.state = {
list: [], //列表数据
pageNo: 1,
pageSize: 10,
total: 32, //数据总数
isFetching: false, //是否正在加载,时间锁
noMore: false, //更多数据
}
this.timer = null;
this.handleScrollThrottle = this.throttle(this.handleScroll, 300);
}
componentDidMount() {
this.fetchData();
window.addEventListener('scroll', this.handleScrollThrottle, false)
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScrollThrottle);
}
// 是否滚动到页面底部
isScrollToPageBottom() {
const offsetHeight = document.documentElement.offsetHeight; //文档高度
const innerHeight = window.innerHeight; //浏览器窗口的高
const scrollY = window.scrollY; //页面滚动条的垂直距离
return offsetHeight - scrollY - innerHeight < 20;
}
handleScroll = () => {
const { pageNo, pageSize, total, isFetching } = this.state;
// 如果当前不是在加载中,并且滚动到距离页面底部,则加载数据
if (!isFetching && this.isScrollToPageBottom()) {
if (Math.ceil(total / pageSize) > pageNo) {
this.setState({
pageNo: this.state.pageNo + 1,
}, this.fetchData);
} else {
// 清除上一次的定时器,防止上一次定时器对底部提示文案的隐藏效果影响到本次
clearTimeout(this.timer);
this.setState({
noMore: true
}, () => {
this.timer = setTimeout(() => {
this.setState({
noMore: false
})
}, 1500)
})
}
}
}
fetchData = () => {
this.setState({
isFetching: true
}, () => {
request('/api/getItems', {pageNo: this.state.pageNo}).then(res => {
this.setState({
list: [...this.state.list, ...res],
isFetching: false
});
})
})
}
// 节流函数
throttle = (func,delay) => {
var timer = null;
return () => {
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
func.apply(context,args);
timer = null;
},delay);
}
}
}
render () {
const { list, isFetching, noMore } = this.state;
return (
<div className='container'>
<ul className='ul'>
{
list.map(item => <li key={item.id} className='item'>{item.id}</li>)
}
</ul>
{
isFetching ? <div className='bottomTip'>加载中...</div> : null
}
{
noMore ? <div className='bottomTip'>没有更多数据了</div> : null
}
</div>
)
}
}