Virtualize虚拟列表

Virtual Scrolling虚拟滚动/Virtual List虚拟列表是前端一种列表加载技术,他模拟真实的容器滚动条,并支持在数据充足的情况下无限滚动和无限渲染,即便如此却很少消耗内存和性能,因为虚拟列表仅仅是渲染用户视线所及的部分,用户看不到的部分或者不关心的部分并不会被渲染。虚拟列表、懒加载和分页加载是三种长列表解决方案,各有优劣。

前言

虚拟滚动列表和真实滚动列表,使用相同的滚动条,但是他们渲染的内容是有差别的。真实滚动条的滚动距离与真实渲染直接相关,渲染的节点越多,可滚动的距离越长;而虚拟滚动的内容容器是由部分渲染节点,加上内容容器的translate偏移值伪造出了滚动条,虽然滚动距离很长,但是实际上渲染的节点很少,仅仅是铺满视线所及的区域。

因为虚拟滚动列表渲染的节点比较少,所以可以大大较少内存和性能消耗,提高加载速度;对于大型数据列表,在不分页前提下,使用虚拟滚动列表是比较科学的解决方案。

普通参数

参数名 值类型 默认值 说明
axis 'x'/'y' 'y' 滚动方向
classes string/string[] '' 对外容器添加样式名
size number 0 预估项目尺寸,高度还是宽度依据axis值而定,如果0将转换为页面行高数值28px(2.8rem)
spill number 2 首尾外溢缓冲的项目个数
names object {wrap:'div',list:'div',cont:'div',item:'section'} 如果有模块自动创建节点,设置的节点名
index number 0 初始化后第一项显示的索引
dynamic boolean true 项目尺寸是否是动态可变的,默认true
content any '' 数据源,写法同getContent函数
contType any '' 数据类型,写法同getContent函数
contData object {} 请求数据时发送的数据,写法同getContent函数
ajax object {} 请求数据时调用ajax函数时的参数
tplStr string '' 模版字符串,其中可能会用到{{和}},当然需要看使用了哪个tplEng
tplEng function null 模板引擎,默认使用renderTpl函数作为模板引擎

等待函数参数

除了通用的b4Init等待函数,还包含如下专属等待函数。这些等待函数也属于模块参数的一部分。

  • b4Append:添加数据和节点前执行,应该返回一个Promise,如果没有resolve则停止执行;如果有resolve但是没有传值则按原来流程执行;如果有resolve并且传递一个值,则最终添加数据和节点将以新值为准。该函数参数如下:
    • data:新数据,来自append方法中对新数据处理后的数据数据
  • b4UpdateItem:更新某项数据前执行,应该返回一个Promise,如果没有resolve则停止执行;如果有resolve但是没有传值则按原来流程执行;如果有resolve并且传递一个值,则最终添加数据和节点将以新值为准。该函数参数如下:
    • data:新数据,来自updateItem方法中对旧数据处理后的新数据
  • b4Clear:清理所有数据前执行,应该返回一个Promise,如果没有resolve或有resolve但是没传值或传false则停止执行;如果有resolve并且传递true值,则执行清除方法。该函数无参数。

监听事件

事件监听也可以叫做执行钩子函数,当某事件被触发将执行用户自定义的钩子函数,在模块的创建到销毁的生命周期中,除了通用的ready、launch、init、reset、destroy、error等事件,本模块还支持以下专属事件。

  • append:新增数据后执行,在模块参数中写作onAppend,该事件参数如下:
    • data:新数据,来自append方法中对新数据处理后的数据数据
  • updateItem:更新某项数据后执行,在模块参数中写作onUpdateItem,该事件参数如下:
    • data:新数据,来自updateItem方法中对旧数据处理后的新数据
  • rendered:完成渲染后执行,在模块参数中写作onRendered,该事件参数如下:
    • data:渲染完成后的数据,该数据为一个对象,属性如下:
      • startIdx:本次渲染的起始索引
      • endIdx:本次渲染的结束索引
      • nodes:临时节点,为节点数组
      • scrollVal:滚动条卷去的尺寸
      • offsetVal:列表translate偏移值
      • wrapSize:内容器的尺寸
  • getCont:获得数据之后执行,在模块参数中写作onGetCont,该事件参数如下:
    • data:刚获得的数据,数据为一个对象,包含content和source属性,content的值是一个数组将被添加到变量this.content中,source的值是原始数据
  • exhausted:所有数据被渲染了之后执行,在模块参数中写作onExhausted,该事件参数如下:
    • data:即this.content变量,此时this.content中的每一项都被渲染了一遍
  • toStart:滚动到头之后执行,在模块参数中写作onToStart,该事件无参数
  • toEnd:滚动到尾之后执行,在模块参数中写作onToEnd,该事件无参数
  • clear:清除后执行,在模块参数中写作onClear,该事件无参数

操作方法

除了通用的init、destroy、reset、update等方法,本模块还支持以下专属方法。

  • append:新增数据,该方法参数如下:
    • data:新数据,写法同getContent函数的第一个参数
    • cb:回调函数,回调函数支持一个参数即新数据。
  • updateItem:更新某项数据,该方法参数如下:
    • data:新数据,为一个对象,包含的属性如下:
      • content:新值,新值可以是一个节点、一个字符串、一个数字或一个数据对象
      • index:要更新的项目索引
      • override:是否需要覆盖原数据,默认true即覆盖
    • cb:回调函数,回调函数支持一个参数即新数据。
  • scrollTo:跳转项目,该方法参数如下:
    • index:跳转到指定项目的索引
    • cb:回调函数,回调函数支持一个参数即跳转索引。
  • toStart:跳转到头部,该方法无参数
  • toEnd:跳转到尾部,该方法无参数

简单使用

我们使用Mockjs模拟列表数据,将数据传入参数content即可。

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    new ax.Virtualize('#virt02',{
                        content:Array(1000).fill(null).map((k,i)=>`${i}-${Mock.mock('@name')}`),
                    });
                    
                

水平滚动

默认为垂直方向的滚动,如果有必要可通过参数axis设置水平滚动,即axis:'x'

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    new ax.Virtualize('#virt10',{
                        content:Array(1000).fill(null).map((k,i)=>`${i}-${Mock.mock('@name')}`),
                        axis:'x',
                    });
                    
                

基本操作

本示例演示常用的操作方法。

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    let ins = new ax.Virtualize('#virt03',{
                        content:Array(1000).fill(null).map((k,i)=>`${i}-${Mock.mock('@name')}`),
                    });
                    btn01.onclick = ()=>{
                         ins.append({content:Array(500).fill(null).map((k,i)=>`<i class="_c-error">${i}</i>`)});
                    }
                    btn02.onclick = ()=>{
                         for(let k of ins.nodes){
                            ins.updateItem({
                                index:k.index,
                                content:`<i class="_c-succ">${Mock.Random.cparagraph(1,8)}</i>`
                            });
                        }
                    }
                    btn03.onclick = ()=>{
                         ins.clear();
                    }
                    btn04.onclick = ()=>{
                         ins.scrollTo(~~(Math.random()*ins.content.length));
                    }