在圖片應用較爲頻繁的項目(官網,商城,桌面壁紙項目等)中,若是咱們單純地給每一個img標籤附加src標籤或者給dom節點添加background-image賦值爲圖片真實地址的話,可想而知瀏覽器是須要下載全部的圖片資源,至關佔用網速,這使得咱們的網頁加載的十分緩慢。javascript
因而,關於解決這種問題的方案之一,lazy-load,懶加載思想應運而生。html
監聽滾動事件,當滾動到該圖片所在的位置的時候,告知瀏覽器下載此圖片資源java
如何告知瀏覽器下載圖片資源,咱們須要存出一個真實圖片路徑,放在dom節點的某個屬性中,等到真正滾動到該圖片位置的時候,將路徑放到img標籤的src中或者div等標籤的background-image屬性中node
寫一個純粹一點的html文件來了解該方法react
<!doctype html>
<html>
<head>
<meta charset = "utf-8">
<style>
html, body{
margin : 0;
padding : 0;
}
body{
position : relative;
}
div{
position : absolute;
top : 50px;
left : 100px;
height : 50px;
width : 50px;
background : #5d9;
cursor : pointer;
}
</style>
</head>
<body>
<div onclick = "getPos(this)"></div>
</body>
<script type = 'text/javascript'>
function getPos(node){
console.info(window.innerHeight)
console.info(node.getBoundingClientRect())
}
</script>
</html>
複製代碼
效果就是,在咱們點擊這個綠色區域時,會打印出這些參數api
咱們須要瞭解這個的緣由是由於,在項目中,若是圖片很是多,咱們會採用上拉加載下拉刷新等功能動態添加圖片。此時咱們爲了能保證懶加載繼續使用,就須要監聽由於圖片動態添加形成的子節點改變事件來作處理。數組
<!doctype html>
<html>
<head>
<meta charset = 'urf-8'/>
</head>
<body>
<button onclick = 'addChild()'>addChild</button>
<button onclick = 'addListener()'>addListener</button>
<button onclick = 'removeListener()'>removeListener</button>
<div id = 'father'></div>
</body>
<!-- 設置公共變量 -->
<script type = 'text/javascript'>
window.father = document.getElementById('father');
window.mutationObserver = undefined;
</script>
<!-- 手動給父節點添加子節點,校驗監聽,移除監聽 -->
<script type = 'text/javascript'>
//給父節點添加子節點事件
function addChild(){
let father = window.father;
let div = document.createElement('div');
div.innerHTML = `${Math.random()}(${window.mutationObserver ? '有監聽' : '無監聽'})`;
father.appendChild(div);
}
//監聽給父節點添加子節點事件
function addListener(){
if(window.mutationObserver){
removeListener();
}
window.mutationObserver = new MutationObserver((...rest) => { console.info(rest) });
mutationObserver.observe(window.father, { childList : true , attributes : true , characterData : true });
}
//移除給父節點添加子節點事件監聽
function removeListener(){
window.mutationObserver && window.mutationObserver.disconnect && (typeof window.mutationObserver.disconnect === 'function') && window.mutationObserver.disconnect();
}
</script>
</html>
複製代碼
效果就是,在點擊addChild按鈕時,會添加子元素瀏覽器
點擊addListener按鈕後再點擊addChild按鈕,回調方法調用,控制檯打印參數app
點擊removeListener按鈕後再點擊addChild按鈕,回調方法不執行,控制檯也沒有參數打印dom
有興趣的同窗能夠了解一下MutationObserver的相關概念,該屬性的兼容性以下,若是要兼容IE11如下的狀況,建議使用其餘方法,好比輪詢,來代替這個api的使用
class ReactLazyLoad extends React.Component{
constructor(props){
super(props);
this.state = {
imgList : [],
mutationObserver : undefined,
props : {}
}
this.imgRender = this.imgRender.bind(this);
}
render(){
let { fatherRef , children , style , className } = this.state.props;
return(
<div ref = { fatherRef } className = { className } style = { style }>
{ children }
</div>
)
}
}
ReactLazyLoad.defaultProps = {
fatherRef : 'fatherRef',
className : '',
style : {},
link : 'data-original'
}
export default ReactLazyLoad;
複製代碼
state中的參數
接收4個入參
componentDidMount(){
this.setState({ props : this.props }, () => this.init());
}
componentWillReceiveProps(nextProps){
this.setState({ props : nextProps }, () => this.init());
}
複製代碼
涉及到異步操做,這裏把接收到的參數存入state中,在組件內調用所有調用state中的參數,方便生命週期對參數改變的影響
由於測試時react版本不是最新,各位能夠靈活替換爲新的api
init(){
let { mutationObserver } = this.state;
let { fatherRef } = this.state.props;
let fatherNode = this.refs[fatherRef];
mutationObserver && mutationObserver.disconnect && (typeof mutationObserver.disconnect === 'function') && mutationObserver.disconnect();
mutationObserver = new MutationObserver(() => this.startRenderImg());
this.setState({ mutationObserver }, () => {
mutationObserver.observe(fatherNode, { childList : true , attributes : true , characterData : true });
this.startRenderImg();
})
}
複製代碼
這一個方法添加了監聽子節點變化的監聽事件來調用圖片加載事件
而且開始初始化執行圖片的加載事件
//開始進行圖片加載
startRenderImg(){
window.removeEventListener('scroll', this.imgRender);
let { fatherRef } = this.state.props;
let fatherNode = this.refs[fatherRef];
let childrenNodes = fatherNode && fatherNode.childNodes;
//經過原生操做獲取全部的子節點中具備{link}屬性的標籤
this.setState({ imgList : this.getImgTag(childrenNodes) }, () => {
//初始化渲染圖片
this.imgRender();
//添加滾動監聽
this.addScroll();
});
}
//添加滾動監聽
addScroll(){
let { fatherRef } = this.state.props;
if(fatherRef){
this.refs[fatherRef].addEventListener('scroll', this.imgRender)
}else{
window.addEventListener('scroll', this.imgRender)
}
}
//設置imgList
getImgTag(childrenNodes, imgList = []){
let { link } = this.state.props;
if(childrenNodes && childrenNodes.length > 0){
for(let i = 0 ; i < childrenNodes.length ; i++){
//只要是包含了{link}標籤的元素 則放在渲染隊列中
if(typeof(childrenNodes[i].getAttribute) === 'function' && childrenNodes[i].getAttribute(link)){
imgList.push(childrenNodes[i]);
}
//遞歸當前元素子元素
if(childrenNodes[i].childNodes && childrenNodes[i].childNodes.length > 0){
this.getImgTag(childrenNodes[i].childNodes, imgList);
}
}
}
//返回了具備全部{link}標籤的dom節點數組
return imgList;
}
//圖片是否符合加載條件
isImgLoad(node){
//圖片距離頂部的距離 <= 瀏覽器可視化的高度,說明須要進行虛擬src與真實src的替換了
let bound = node.getBoundingClientRect();
let clientHeight = window.innerHeight;
return bound.top <= clientHeight;
}
//每個圖片的加載
imgLoad(index, node){
let { imgList } = this.state;
let { link } = this.state.props;
//獲取以前設置好的{link}而且賦值給相應元素
if(node.tagName.toLowerCase() === 'img'){
//若是是img標籤 則賦值給src
node.src = node.getAttribute(link);
}else{
//其他情況賦值給背景圖
node.style.backgroundImage = `url(${node.getAttribute(link)})`;
}
//已加載了該圖片,在資源數組中就刪除該dom節點
imgList.splice(index, 1);
this.setState({ imgList });
}
//圖片加載
imgRender(){
let { imgList } = this.state;
//由於加載後則刪除已加載的元素,逆向遍歷方便一些
for(let i = imgList.length - 1 ; i > -1 ; i--) {
this.isImgLoad(imgList[i]) && this.imgLoad(i, imgList[i])
}
}
複製代碼
class ReactLazyLoad extends React.Component{
constructor(props){
super(props);
this.state = {
imgList : [],
mutationObserver : undefined,
props : {}
}
this.imgRender = this.imgRender.bind(this);
}
componentDidMount(){
this.setState({ props : this.props }, () => this.init());
}
componentWillUnmount(){
window.removeEventListener('scroll', this.imgRender);
}
componentWillReceiveProps(nextProps){
this.setState({ props : nextProps }, () => this.init());
}
init(){
let { mutationObserver } = this.state;
let { fatherRef } = this.state.props;
let fatherNode = this.refs[fatherRef];
mutationObserver && mutationObserver.disconnect && (typeof mutationObserver.disconnect === 'function') && mutationObserver.disconnect();
mutationObserver = new MutationObserver(() => this.startRenderImg());
this.setState({ mutationObserver }, () => {
mutationObserver.observe(fatherNode, { childList : true , attributes : true , characterData : true });
this.startRenderImg();
})
}
//開始進行圖片加載
startRenderImg(){
window.removeEventListener('scroll', this.imgRender);
let { fatherRef } = this.state.props;
let fatherNode = this.refs[fatherRef];
let childrenNodes = fatherNode && fatherNode.childNodes;
//經過原生操做獲取全部的子節點中具備{link}屬性的標籤
this.setState({ imgList : this.getImgTag(childrenNodes) }, () => {
//初始化渲染圖片
this.imgRender();
//添加滾動監聽
this.addScroll();
});
}
//添加滾動監聽
addScroll(){
let { fatherRef } = this.state.props;
if(fatherRef){
this.refs[fatherRef].addEventListener('scroll', this.imgRender)
}else{
window.addEventListener('scroll', this.imgRender)
}
}
//設置imgList
getImgTag(childrenNodes, imgList = []){
let { link } = this.state.props;
if(childrenNodes && childrenNodes.length > 0){
for(let i = 0 ; i < childrenNodes.length ; i++){
//只要是包含了{link}標籤的元素 則放在渲染隊列中
if(typeof(childrenNodes[i].getAttribute) === 'function' && childrenNodes[i].getAttribute(link)){
imgList.push(childrenNodes[i]);
}
//遞歸當前元素子元素
if(childrenNodes[i].childNodes && childrenNodes[i].childNodes.length > 0){
this.getImgTag(childrenNodes[i].childNodes, imgList);
}
}
}
//返回了具備全部{link}標籤的dom節點數組
return imgList;
}
//圖片是否符合加載條件
isImgLoad(node){
//圖片距離頂部的距離 <= 瀏覽器可視化的高度,說明須要進行虛擬src與真實src的替換了
let bound = node.getBoundingClientRect();
let clientHeight = window.innerHeight;
return bound.top <= clientHeight;
}
//每個圖片的加載
imgLoad(index, node){
let { imgList } = this.state;
let { link } = this.state.props;
//獲取以前設置好的{link}而且賦值給相應元素
if(node.tagName.toLowerCase() === 'img'){
//若是是img標籤 則賦值給src
node.src = node.getAttribute(link);
}else{
//其他情況賦值給背景圖
node.style.backgroundImage = `url(${node.getAttribute(link)})`;
}
//已加載了該圖片,在資源數組中就刪除該dom節點
imgList.splice(index, 1);
this.setState({ imgList });
}
//圖片加載
imgRender(){
let { imgList } = this.state;
//由於加載後則刪除已加載的元素,逆向遍歷方便一些
for(let i = imgList.length - 1 ; i > -1 ; i--) {
this.isImgLoad(imgList[i]) && this.imgLoad(i, imgList[i])
}
}
render(){
let { fatherRef , children , style , className } = this.state.props;
return(
<div ref = { fatherRef } className = { className } style = { style }>
{ children }
</div>
)
}
}
ReactLazyLoad.defaultProps = {
fatherRef : 'fatherRef',
className : '',
style : {},
link : 'data-original'
}
export default ReactLazyLoad;
複製代碼
/* *
* @state
* imgSrc string 圖片url地址
* imgList array 圖片數組個數
* fatherId string 父節點單一標識
* link string 須要存儲的原生標籤名
*/
import React from 'react';
import ReactLazyLoad from './ReactLazyLoad';
class Test extends React.Component{
constructor(props){
super(props);
this.state = {
imgSrc : 'xxx',
imgList : Array(10).fill(),
fatherId : 'lazy-load-content',
link : 'data-original',
}
}
render(){
let { imgSrc , imgList , fatherId , link } = this.state;
return(
<div>
<ReactLazyLoad fatherRef = { fatherId } style = {{ width : '100%' , height : '400px' , overflow : 'auto' , border : '1px solid #ddd' }}>
{ imgArr && imgArr.length > 0 && imgArr.map((item, index) => {
let obj = { key : index , className : styles.img };
obj[link] = imgSrc;
return React.createElement('div', obj);
}) }
{ imgArr && imgArr.length > 0 && imgArr.map((item, index) => {
let obj = { key : index , className : styles.img };
obj[link] = imgSrc;
return React.createElement('img', obj);
}) }
<div>
這是混淆視聽的部分
<div>
<div>這仍是混淆視聽的部分</div>
{ imgArr && imgArr.length > 0 && imgArr.map((item, index) => {
let obj = { key : index , className : styles.img };
obj[link] = imgSrc;
return React.createElement('img', obj);
}) }
</div>
</div>
</ReactLazyLoad>
<button onClick = {() => { imgArr.push(undefined); this.setState({ imgArr }) }}>添加</button>
</div >
)
}
}
export default Test;
複製代碼
在調用Test方法以後,打開f12指到圖片dom節點
滑動滾動條,會發現滾動條滾到必定的位置
當前dom節點若是是img節點,就會添加src屬性;當前是div節點,則會添加backgroundImage屬性
ps:這裏爲了調試方便我都用了同一個圖片地址,小夥伴們能夠修改代碼,用不一樣的圖片地址,自行調試哦