「前言」
列表滚动加载、图片懒加载,这些都是目前流行的技术方案。本文从原理到代码实现,完整记录整个思考过程。
「知识准备」
- 可视区
- clientWidth、clientHeight 网页可视区宽高
- offsetWidth、offsetHeight 网页可视区宽高(包括边线的宽)
- 实际内容
- scrollWidth、scrollHeight 网页正文全文宽高
- 滚动条距离
- scrollTop、scrollLeft
- DOM 距离屏幕
- screenTop、screenLeft
「滚动加载」
首先确定触发加载数据的时机:列表滚动至触底。那么判断列表触底的条件是什么呢? 可视区高度 + 滚动距离 >= 内容实际高度
React代码
import React, { Component } from 'react';
import './index.css';
export default class Scroll extends Component {
constructor(props) {
super(props);
this.state = {
list: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
};
this.ref = React.createRef();
}
componentDidMount() {
this.ref.current.addEventListener('scroll', this.scrollEvent);
}
scrollEvent = (e) => {
//可视区高度
let scrollHeight = e.target.scrollHeight;
//滚动高度
let scrollTop = e.target.scrollTop;
//列表内容实际高度
let offsetHeight = e.target.offsetHeight;
if (offsetHeight + scrollTop >= scrollHeight) {
console.log('列表触底');
}
};
render() {
const { list } = this.state;
return (
<div ref={this.ref} className="scroll">
{list.map((item, index) => {
return (
<div key={index} className="scroll-item">
「{index}」
</div>
);
})}
</div>
);
}
}
在列表触底的时候请求接口,无限滚动加载便实现了。
scrollEvent = async (e) => {
let scrollHeight = e.target.scrollHeight;
let scrollTop = e.target.scrollTop;
let offsetHeight = e.target.offsetHeight;
if (offsetHeight + scrollTop >= scrollHeight) {
console.log('列表触底,触发接口请求数据');
this.setState({ loading: true });
let result = await this.loadData();
this.setState({
loading: false,
list: this.state.list.concat(result),
});
}
};
//模拟数据请求,数据处理时间 1s
loadData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([1, 1, 1, 1, 1]);
}, 1000);
});
};
「图片懒加载」
图片资源一般都比较大,页面中引入大量图片,加载页面时会占用大量的宽带资源。
「图片懒加载原理」 大量的图片第一时间并未进入可视区,未进入可视区的图片可以先不加载源图片,只有当图片进入可视区,再将图片标签 src 设置成真正的源图片 url 地址。
判断 「图片进入可视区」 的条件:每张图片具体顶部的距离 < 可视区高度 + 滚动距离
「关键操作」 当判断图片进入可视区时,将图片的 data-src 属性赋给图片的 src,即可完成图片的加载。
import React, { Component } from 'react';
import './index.css';
export default class Scroll extends Component {
constructor(props) {
super(props);
this.state = {
list: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
};
this.ref = React.createRef();
}
componentDidMount() {
//获取所有的图片元素
const images = document.getElementsByTagName('img');
this.ref.current.addEventListener(
'scroll',
this.scrollEvent.bind(null, images),
);
//初始化加载图片
this.scrollEvent(images);
}
scrollEvent = (images) => {
// 可视区
let clientHeight = this.ref.current.clientHeight;
// 滚动距离
let scrollTop = this.ref.current.scrollTop;
for (let image of images) {
//图片距离顶部距离
let top = image.offsetTop;
if (top < clientHeight + scrollTop) {
// 设置图片源地址,完成目标图片加载
image.src = image.dataset.src;
}
}
};
render() {
const { list } = this.state;
return (
<div ref={this.ref} className="scroll">
{list.map((item, index) => {
return (
<div key={index} className="scroll-item">
<img
style={{ width: '100%', height: '100%' }}
src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690"
data-src="https://blog.levenx.com/levenxBlog/8ec4f4a0b502443b8f2ae620a5cd30cf.jpg"
/>
</div>
);
})}
</div>
);
}
}
效果图