长列表虚拟滚动、滚动加载

865 阅读1分钟

1. 长列表虚拟滚动

页面渲染太多dom节点会影响性能,对于长列表只渲染可视区域的节点(visibleData),滚动时替换visibleData 数据,实现虚拟滚动。

主要步骤

js:

 // 1. data变量保存总数据,visibleData保存可视区域数据;onscroll事件处理函数中;
 constructor(props) {    
    super(props);    
    let listData = [...new Array(100)].map((item, index) => ({value: `item${index}`));   
    this.state = {      
          listData: []   
     };      
    this.itemHeight = 40;    
    this.listData = listData; 
   }

//  2. 节点挂载后,计算可视区域显示的条数,visibleData = data.slice(0, visibleCount);
 componentDidMount() { 
       const visibleCount = Math.ceil(this.el.clientHeight / this.itemHeight); 
       const visibleData = this.listData.slice(0, visibleCount);      
       this.setState({ listData: visibleData });  
  }

// 3. onscroll  事件处理函数中,根据scrollTop 获取 startIndex,endIndex,visibleData = data.slice(startIndex, endIndex)
updateVisibleData(scrollTop) {    
    scrollTop = scrollTop || 0;  
    const visibleCount = Math.ceil(this.el.clientHeight / this.itemHeight);  
    const start = Math.floor(scrollTop / this.itemHeight);   
    const end = start + visibleCount;    
    let visibleData = this.listData.slice(start, end);    
    this.setState({ listData: visibleData });       
    this.content.style.transform = `translate(0, ${start * this.itemHeight}px)`; }

  onScroll = () => {      
       const scrollTop = this.el.scrollTop;    
       this.updateVisibleData(scrollTop);
    };

html:

<div className="list-view" onScroll={this.onScroll} ref={el => (this.el = el)}>
    <div className="list-view-phantom"
         style={{ height: `${this.listData.length * 40}px` }}></div>   
    <div className="list-view-content" ref={el => (this.content = el)}> 
         <ul>        
             {
                this.state.listData.map(item => (<li className="list-view-item">{item.value}</li>))
              }     
         </ul>       
     </div> 
 </div>

css:

.list-view {      
          height: 400px;  
          overflow: auto;     
          border: 1px solid #aaa;   
          position: relative;   
}
.list-view-phantom {
          position: absolute;    
          left: 0;    
          right: 0;    
          top: 0;  
          z-index: -1;    
}
.list-view-content {     
           position: absolute;     
           top: 0;         
           left: 0;          
           right: 0;     
}
.list-view-item {         
           padding: 5px;      
           color: #666;           
           line-height: 30px;           
           box-sizing: border-box;      
}


2. 滚动加载

滚动分页,快到内容底部时异步加载下一页,将异步响应内容append到之前的数据中。注意页面初始化时内容应该足够多到能出现滚动条。

判断是否加载下页的函数:

  isLowEnough = () => {       
         const clientHeight = this.el.clientHeight;      
         const scrollTop = this.el.scrollTop;    
         const scrollHeight = this.el.scrollHeight;      
         return scrollHeight - clientHeight - scrollTop <= 20;  
  }

onscroll事件处理函数

 onScroll = e => {   
     let {listData, total} = this.state;    
     if (listData.length === total)  return; 
     if (this.isLowEnough()) {
            // setTimeout 模拟异步请求      
           setTimeout(() => {         
           let newData = [...new Array(10)].map((item, index) => ({ value: `item${index}`}));   
           listData = listData.concat(newData);  
           this.setState({listData});   
         }, 2000)   
     } 
}