基於vue解決大資料表格卡頓問題

楊金凱發表於2019-03-19

點我線上體驗Demo(請用電腦檢視)

親測蘋果電腦,chrome瀏覽器無卡頓現象,其它瀏覽器並未測試,如遇到卡頓請備註系統和瀏覽器,方便我後續優化,謝謝

先看一下效果,一共1000 X 100 = 10W個單元格基本感受不到卡頓,而且每個單元格點選可以編輯,支援固定頭和固定列

Github專案原始碼 覺得可以就Star一下,哪裡有問題也歡迎指出,感謝

解決問題核心點:根據可視區域,橫向滾動載入,豎向滾動載入,控制dom數量

專案背景

筆者最近在做廣告排期功能,需要進行點位預佔,大的合同可能需要對多個資源排期,週期可能到幾年這樣,然後我們的頁面互動是這樣

橫向每個月30個單元格,最多的3年,36個月,每行36*30=1080個單元格

豎向100個資源,總共約️10W個單元格,然後每個單元格里面會有一個輸入框,一個庫存總數,所以總數是20W個,內網使用,介面請求根本不是問題,可以瀏覽器渲染就扛不住了介面回來之後會出現幾十秒的白屏,整個頁面處於卡死狀態

這還不算,載入出之後頁面操作也是非常卡,滑動延遲嚴重,頁面基本處於癱瘓狀態

之前的功能是基於jquery開發的,專案重構用的vue,UI採用了ElementUI,ElmentUI中的表格在資料量較大是有嚴重的效能問題,最直接的表現就是白屏時間較長,而且會出現渲染錯亂

所以就想著自己實現一個表格,解決卡頓問題

實現思路

表格拆分,動態載入

表格橫向按月拆分,每個月份單獨一個table,月份table外層放一個佔位div,根據橫向滾動位置控制展示

豎向按資源拆分,同樣包裹一個佔位div,按照滾動位置動態載入,始終保持dom數量上線

動態編輯,按需生成編輯輸入框

不同的標籤在瀏覽器渲染時效能是不一樣的,比如input這種標籤就比span等標籤重許多,所以不能滿屏input

方案就是點選單元格展示輸入框,焦點丟失移除,此處的展示非display控制顯示隱藏,而是v-if控制dom是否載入

程式碼分解

固定頭

<div class="table-head">
          <div class="module"
            v-bind:style="{ transform: 'translateX(' + scrollLeft + 'px)' }"  
            v-for="(item, index) in monthData" v-bind:key="index">
            <table cellspacing="0" cellpadding="0">
              <thead>
                <tr>
                  <td colspan="30">{{item.month}}</td>
                </tr>
                <tr>
                  <td width="100" 
                    v-for="(d_item, d_index) in item.days" v-bind:key="d_index"
                    style="min-width:100px">{{d_item}}</td>
                </tr>
              </thead>
            </table>
          </div>
        </div>
複製程式碼

固定列

 <div class="table-fix-cloumns">
          <div class="module fix-left-top">
            <table width="100" cellspacing="0" cellpadding="0">
              <thead>
                <tr>
                  <td>位置</td>
                </tr>
                <tr>
                  <td>position</td>
                </tr>
              </thead>
            </table>
          </div>
          <div class="module"  v-bind:style="{ transform: 'translateY(' + scrollTop + 'px)' }">
            <table width="100" cellspacing="0" cellpadding="0">
              <thead>
                <tr v-for="(item, index) in projectData" v-bind:key="index">
                  <td>{{item.name}}</td>
                </tr>
              </thead>
            </table>
          </div>
        </div>
複製程式碼

表體

<div class="table-body" @scroll="tableScroll" style="height: 300px">
          <div class="module" 
            style="width:3000px;"
            v-for="(item, index) in monthData" v-bind:key="index">
            <div class="content" 
              v-if="Math.abs(index - curModule)  < 3">
              <div class="row"
                style="height:30px"
                v-for="(p_item, p_index) in projectData" 
                v-bind:key="p_index">
                <table width="3000"
                  v-if="Math.abs(p_index - curRow)  < 20"
                  cellspacing="0" cellpadding="0">
                  <tbody>
                    <tr>
                      <td 
                        @click="clickTd(p_index,item.month, d_item, $event)" 
                        v-for="(d_item, d_index) in item.days" v-bind:key="d_index">
                      <span v-if="!originProjectData[p_index][''+item.month][''+d_item]['show']">{{originProjectData[p_index][''+item.month][''+d_item]['last']}}</span>
                      <input
                        @blur="blurTd(p_index,item.month, d_item)"
                        v-if="originProjectData[p_index][''+item.month][''+d_item]['show']"
                        v-model="originProjectData[p_index][''+item.month][''+d_item]['last']"
                        v-focus="originProjectData[p_index][''+item.month][''+d_item]['focus']"/>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
複製程式碼

經過如上優化,完美解決表格卡頓問題,但是我並沒有封裝元件,原因如下

·外掛封裝後會有很多限制,不能再用vue那種模板寫法,用json傳入資料,自定義內容不是很靈活
·可以根據自己的應用場景自行修改擴充,程式碼已經很簡潔
·比較懶
複製程式碼

如果你有類似需求可以試一下我這個,怎麼該你說了算

相關文章