如何讓vue長列表快速加載

vue-long-list-load,滿足特殊條件的長列表加載。支持:1、各個節點高度不同且可自由設定 2、各個節點可修改不影響加載效果 3、可精確的滾動到指定位置。

背景

有個長列表渲染的需求,本來用vue-virtual-scroll-list的。但是每個節點的高度不一樣,用著有點問題。如果也有相應的需求可以參考下我的方案。歡迎大傢交流!

vue-long-list-load

滿足特殊條件的的長列表加載。 列表內各個節點高度不一,各個節點可以進行修改,定位到指定位置指定節點。 www.npmjs.com/package/vue…

主要內容

  1. vue-long-list-load 與 vue-virtual-scroll-list 對比
  2. vue-long-list-load實現思路
  3. 基本代碼
  4. 插件使用方式
  5. 插件參數說明

一、組件對比

vue-long-list-load ,vue-virtual-scroll-list兩個插件各有優缺點,當我們在選擇插件的時候要選擇最適合應用場景的插件。下面是兩個插件的基本功能對比。vue-long-list-load 主要特點是適用於各個節點尺寸不統一的場景,vue-virtual-scroll-list更適用於高度統一節點以列表長度以w計的這種列表。

組件 vue-long-list-load vue-virtual-scroll-list
npm地址 www.npmjs.com/package/vue… www.npmjs.com/package/vue…
核心 先空dom占位,顯示區域內組件掛載顯示 計算當前顯示區域內要顯示的組件掛載
橫向 支持 支持
列表內高度一致 支持 支持
列表內高度不一致 支持 支持不好
滾動到指定組件 支持 高度不一致時計算不準確
滾動事件 支持 支持
組件高度改變事件 支持 支持
隱藏組件 支持 不支持

二、實現思路

主要思想就是通過\color{red}{虛擬dom}虛擬dom占位各個節點,根據可顯示視口的變化來展示該展示的節點。影響可顯示視口的因素的監聽,頁面整體寬高變化、節點高度變化、頁面的滾動定位到某一個節點等都可能會影響到視口的變化。當視口發生變化後計算可顯示的節點,將可顯示的節點掛載上節點的組件,不在視口內的節點銷毀組件緊保留一個空的div。下圖為實現思路的流程圖。

三、關鍵方法源碼分析

主入口html結構如下,\color{red}{v-for}v−for 展示長列表長度的的節點,並且通過:style 來設置一個\color{red}{最小高度}最小高度,設置最小高度的原因是這個高度值可能不準確,當真正組件渲染完之後計算出最準確的高度,如果直接height的話可能會使節點內的組件展示不全。同時每個節點設置唯一id(scrollItem_ 唯一標識),在根據數據獲取dom信息時候使用。節點組件定義瞭唯一的class (long-item- 唯一標識),主要用來掛載真實列表組件,同時監聽組件的高度變化。showList[index]來控制節點是否時候顯示的唯一標識。

  <!--html代碼-->
  <template>
    <div 
      :
      :style="{'min-height': (item.height>=0?item.height:height) + 'px'}"
      :key="item[dataKey]"
      :id="'scrollItem_' + item[dataKey]"
      v-for="(item,index) in dataList" 
      >
      <long-item 
        v-if="showList[index]"
        :dataKey="dataKey" 
        :item="item"
        :boxHeight="item.height||0"
        :direction="direction"
        :heightChange="heightChange"
        :extendCcomments="extendCcomments"> 
      </long-item>
    </div>
  </template>

當showList[index]為true的時候,對應的節點顯示。long-item 在mounted生命周期時,回調extendCcomments。通過\color{red}{Vue.extend Profile}Vue.extendProfile掛載到對應的dom上。componentProps是節點組件傳過來的一些參數,在掛載的時候全部掛載上。

 <!--掛載組件-->
  extendCcomments(item){
    this.componentProps.item=item
    var Profile = Vue.extend(this.dataComponent);
        // 創建 Profile 實例,並掛載到一個元素上
        new Profile({
          propsData:this.componentProps
        }
      ).$mount('.long-item-'+item[this.dataKey]);
  }

通過\color{red}{element-resize-detector}element−resize−detector來監聽dom尺寸的變化,每個節點的寬高發生變化的時候,並且與原來的尺寸不一樣回調heightChange方法,進行尺寸的更新及顯示節點的操作計算。

 <!--每個節點尺寸發生變化-->
  this.$nextTick(()=> {
    this.$DomListener.listenTo(document.getElementById('long-item-'+this.item[this.dataKey]), (element)=>{
      if(this.boxHeight != element[this.directionConfig.width]){
        this.heightChange(this.item, element[this.directionConfig.width])
      } 
    })
  });

獲取可顯示的視口區域的方法,頁面滾動和尺寸變化都會調用到這個方法,所以這個方法在調用的時候做防抖處理300ms內有連續調用隻會執行最後一次調用,要不然會頻繁計算影響性能。可顯示視口區域計算方式是當前視口 及 前後兩個視口總共三個視口的尺寸。這樣在小的滾動范圍內會有較好的體驗。

  getShowLimit(startTop) {
    const scrollTop = startTop || this.scrollWrap[this.directionConfig.scrollTo] || 0; // 滾動距離
    this.viewClientHeight = this.scrollWrap[this.directionConfig.width]; // 可視區域高度
    this.scrollTop = scrollTop
    this.showStart = scrollTop - this.viewClientHeight
    this.showEnd = scrollTop + 2*this.viewClientHeight
    if(this.setTopTimer){
      clearTimeout(this.setTopTimer)
    } 
    this.setTopTimer = setTimeout(() => {
      this.setItemTopheight()
    }, 300);
  },

根據高度或者寬度來計算節點是否顯示,因為這個計算量比較大避免而且這個方法與其他方法沒有什麼關聯,直接單獨開一個\color{red}{獨立線程}獨立線程進行計算。通過引入\color{red}{simple-web-worker}simple−web−worker這個插件單獨開一個線程進行計算顯示節點。計算方法主要有三點:當前節點的開頭在顯示視口內、當前節點的結尾在顯示視口內、當前節點開頭和結尾都不在顯示視口內。分為這三種情況,隻要滿足一種情況,則該節點就顯示在顯示視口中。

  // 根據高度計算節點是否顯示
  setItemTopheight(){
    let stsartId = this.dataList[0]&&this.dataList[0][this.dataKey]
    let startDom = stsartId && document.getElementById('scrollItem_'+stsartId)
    let startTop = startDom ? startDom[this.directionConfig.offset] : 0
    this.worker = this.$worker.run((dataList,showStart,showEnd, startTop,hideIds,dataKey,height) =>{
      let topHeight = startTop; // 題目頂部距離頂部距離
      let bottomHeight = 0; // 題目底部距離頂部距離
      let showList = []
      for(let i=0,len=dataList.length;i<len;i++){
        let item = dataList[i]
        if(hideIds.indexOf(item[dataKey]) != -1){
          showList[i] = false;
          continue;
        }
        bottomHeight = topHeight + (item.height>=0?item.height:height)
        // 判斷 1.題目頂部在顯示范圍內 2.題目底部在顯示范圍內 3.題目頂部和底部都不在顯示范圍內 
        if((topHeight>=showStart && topHeight<=showEnd)||
          (bottomHeight>=showStart && bottomHeight<=showEnd)||
          (topHeight<showStart && bottomHeight>showEnd) ){
          showList[i] = true}
         else{
          showList[i] = false
        } 
        topHeight += ((item.height>=0?item.height:height));
      }
      return showList
    }, [this.dataList, this.showStart, this.showEnd, startTop, this.hideIds,this.dataKey,this.height])
    .then(res => {
      this.showList = res
    })
    this.worker = null
  },

四、使用方式

安裝vue-long-list-load:

npm install vue-long-list-load --save

項目內調用

<long-list 
 ref="vueLongList"
 dataKey='id' 
 scrollWrap
 :dataList="dataList" 
 :dataComponent="dataComponent" 
 :componentProps="componentProps"
 height=100
 > 
</long-list>

五、參數說明

參數 說明 類型 必填 可選值 默認值
scrollWrapId 列表滾動容器id string true
dataKey 節點數據內唯一鍵值 String true
dataList 列表數據 Array true 具體見下方說明
dataComponent 自定義的節點組件 true
ref 調用組件內部方法 string false
direction 滾動方向 Number false 0:縱向,1橫向 0
hideIds 列表中需要隱藏的節點 Array false 具體見下方說明 []
height 節點高度 Number false 100
componentProps 節點組件要傳遞的參數 Object false {}
scroll 滾動區域內滾動回調方法 Function false
resized 某個節點寬高發生變化回調方法 Function false 具體見下方說明

部分參數說明

  &lt;--假設 dataKey=id-->
  
  &lt;--列表中需要隱藏的節點-->
  hideIds:[1, 2]
  &lt;--列表數據 dataList 內 height 為 **Number**。-->
  dataList:[
    {id:1,height:100},
    {id:2,height:200},
    {id:3,height:300},
    {id:4,height:300},
    {id:5,height:300}
  ]
  
  &lt;--節點高度-->
  height:100
  &lt;--如果dataList的數據內有height值 不需要設置這個height-->
  &lt;--如果dataList 和 height 都不傳遞的話,默認為100 可能滾動略有卡頓;-->
  &lt;--建議在每個高度都不相同的時候通過dataList傳遞,都相同時候通過height傳遞-->
  
  &lt;--某個節點寬高發生變化回調方法 返回參數為id 與高度-->
  resized(id, height){ }

總結

項目中實踐數據,基本每個節點至少500個dom節點,平均也在800個dom節點以上,用vue-long-list-load 不在渲染區域內的題目隻會渲染2個dom節點。按正常800左右個dom節點的題目計算 一般渲染區域渲染的節點在9個左右,如果是n節點的列表 ,每次加載 dom操作都減少(n-9)x(800-2)個dom的渲染,如果\color{red}{1000個節點}1000個節點的列表每次加載和操作的時候相當於減少瞭\color{red}{726180}726180個dom節點的渲染。首次渲染還有修改後的重繪都大大減少瞭dom的渲染加快瞭加載速度提高瞭用戶體驗。 此方案已經在項目中實踐一段時間,用戶反饋很好。如果大傢也有類似的場景需求,歡迎大傢使用!交流!

以上就是如何讓vue長列表快速加載的詳細內容,更多關於vue 長列表快速加載的資料請關註WalkonNet其它相關文章!

推薦閱讀: