瀑布流使用虛擬列表性能優化

来源:https://www.cnblogs.com/nextl/archive/2022/11/15/16891648.html
-Advertisement-
Play Games

瀑布流算是比較常見的佈局了,一個般常見縱向瀑布流的交互,當我們滾動到底的時候載入下一頁的數據追加到上去。因為一次載入的數據量不是很多,頁面操作是也不會有太大的性能消耗。但是如果當你一直往下滾動載入,載入幾十頁的時候,就會開始感覺不那麼流暢的,這是因為雖然每次操作的很少,但是頁面的 DOM 越來越多,... ...


瀑布流算是比較常見的佈局了,一個般常見縱向瀑布流的交互,當我們滾動到底的時候載入下一頁的數據追加到上去。因為一次載入的數據量不是很多,頁面操作是也不會有太大的性能消耗。但是如果當你一直往下滾動載入,載入幾十頁的時候,就會開始感覺不那麼流暢的,這是因為雖然每次操作的很少,但是頁面的 DOM 越來越多,記憶體占用也會增大,而且發生重排重繪時候瀏覽器計算量耗時也會增大,就導致了慢慢不能那麼流暢了。這個時候可以選擇結合虛擬列表方式使用,虛擬列表本身就是用來解決超長列表時的處理方案。

瀑布流

瀑布流的實現方式有很多種,大體分為:

  • CSS: CSS 實現的有 multi-column、grid ,CSS 實現存在一定局限性,例如無法調整順序,當元素高度差異較大時候不是很好處理各列間隔差等。
  • JavaScript:JavaScript 實現的有 JavaScript + flex、JavaScript + position,JavaScript 實現相容性較好,可控制性高。

因為我的瀑布流是可提前計算元素寬高,列數是動態的,所以採用了 JavaScript + position 來配合 虛擬列表 進行優化。

js + flex 實現

如果你的瀑布流 列是固定,列寬不固定 的,使用 flex 是個很好選擇,當你的容器寬度變話時候,每一列寬度會自適應,大致實現方式

將你的數據分為對應列數

let data1 = [], //第一列
    data2 = [], //第二列
    data3 = [], //第三列
    i = 0;

while (i < data.length) {
    data1.push(data[i++]);
    if (i < data.length) {
        data2.push(data[i++]);
    }
    if (i < data.length) {
        data3.push(data[i++]);
    }
}

然後將你的每列數據插入進去就可以了,設置 list 為 flex 容器,並設置主軸方向為 row

<div class="list">
    <!-- 第一列 -->
    <div class="column">
        <div class="item"></div>
        <!-- more items-->
    </div>
    <!-- 第二列 -->
    <div class="column">
        <div class="item"></div>
        <!-- more items-->
    </div>
    <!-- 第三列 -->
    <div class="column">
        <div class="item"></div>
        <!-- more items-->
    </div>
</div>

js + position 實現

這種方式比較適合 列定寬,列數量不固定情況,而且最好能計算出每個元素的大小。

大致 HTML 結構如下:

<ul class="list">
    <li class="list-item"></li>
    <!-- more items-->
</ui>
<style>
    .list {
        position: relative;
    }

    .list-item {
        position: absolute;
        top: 0;
        left: 0;
    }
</style>

JavaScript 部分,首先需要獲取 list 寬度,根據 list.width/列寬 計算出列的數量,然後根據列數量去分組數據和計算位置

// 以列寬為300 間隔為20 為例

let catchColumn = (Math.max(parseInt((dom.clientWidth + 20) / (300 + 20)), 1))

const toTwoDimensionalArray = (count) => {
    let list = []
    for (let index = 0; index < count; index++) {
        list.push([])
    }
    return list;
}

const minValIndex = (arr = []) => {
    let val = Math.min(...arr);
    return arr.findIndex(i => i === val)
}

// 緩存累計高度
let sumHeight = toTwoDimensionalArray(catchColumn)

data.forEach(item => {
    // 獲取累計高度最小那列
    const minIndex = minValIndex(sumHeight)

    let width = 0 // 這裡寬高更具需求計算出來
    let height = 0

	item._top = minIndex * (300 + 20) // 緩存位置信息,後面會用到
    item.style = {
        width: width + 'px',
        height: height + 'px',
        // 計算偏移位置
        transform: `translate(${minIndex * (300 + 20)}px, ${sumHeight[minIndex]}px)`
    }

    sumHeight[minIndex] = sumHeight[minIndex] + height + 20 
})

動態列數

可以使用 ResizeObserver(現代瀏覽器相容比較好了) 監聽容器元素大小變化,當寬度變化時重新計算列數量,當列數量發生變化時重新計算每項的位置信息。

const observer = debounce((e) => {
    const column = updateVisibleContainerInfo(visibleContainer)
    if (column !== catchColumn) {
        catchColumn = column
        // 重新計算
        this.resetLayout()
    }
}, 300)

const resizeObserver = new ResizeObserver(e => observer(e));

// 開始監聽
resizeObserver.observe(dom);

過渡動畫

當列數量發生變化時候,元素項的位置很多都會發生變化,如下圖,第 4 項的位置從第 3 列變到了第 4 項,如果不做處理會顯得比較僵硬。

1 2 3 4 4

好在我們使用了 transform(也是為什麼使用 top、left 原因,transform 動畫性能更高) 進行位置偏移,可以直接使用 transition 過渡。

.list-item {
    position: absolute;
    top: 0;
    left: 0;
    transition: transform .5s ease-in-out;
}

使用虛擬列表

瀑布流存在的問題

很多虛擬列表的都是使用的單列定高使用方式,但是瀑布流使用虛擬列表方式有點不同,瀑布流存在多列且時是錯位的。所以常規 length*height 為列表總高度,根據 scrollTop/height 來確定下標方式就行不通了,這個時候高度需要根據瀑布流高度動態決定了,可顯示元素也不能通過 starindex-endindex 去截取顯示了。

如下圖:藍色框的元素是不應該顯示的,只有與可視區域存在交叉的元素才應該顯示

1 2 3 4 5 6 8 9 10 7 12 11 13 15 14

可視元素判定

先來看下麵圖,當元素完全不在可視區域時候就視為當前元素不需要顯示,只有與可視區域存在交叉或被包含時候視為需要顯示。

1 2 3 4 5 6 8 9 10 7 12 11 13 14

因為上面瀑布流的實現採用的是 position 定位的,所以我們完全能知道所有元素距離頂部的距離,很容易計算出與可視區域交叉位置。

元素偏移位置 < 滾動高度+可視區域高度 && 元素偏移位置 + 元素高度 > 滾動高度

如果只渲染可視區域範圍,滾動時候會存在白屏再出現,可視適當的擴大渲染區域,例如把上一屏和下一屏都算進來,進行預先渲染。

const top = scrollTop - clientHeight
const bottom = scrollTop + clientHeight * 2
const visibleList = data.filter(item => item._top + item.height > top && item._top < bottom)

然後通過監聽滾動事件,根據滾動位置去處理篩選數。這裡會存在一個隱藏性能問題,當滾動載入數據比較多的時候,滾動事件觸發也是比較快的,每一次都進行一次遍歷,也是比較消耗性能的。可以適當控制一下事件觸發頻率,當然這也只是治標不治本,歸根倒是查詢顯示元素方法問題。

標記下標
應為列表數據的 _top 值是從小到大正序的,所以我們可以標記在可視區元素的下標,當發生滾動的時候,我們直接從標記下標開始查找,根據滾動分幾種情況來判斷。
1> 如果滾動後,標記下標元素還在可視範圍內,可以直接從標記下標二分查找,往上往下找直到不符合條件就停止。
2> 如果滾動後,標記下標元素不在可視範圍內,根據滾動方嚮往上或者往下去查找,然後更新下標值。這個時候存在一種情況,就是當用戶拖動滾動條滾動幅度特別大的時候,可以將下標往上或者往下偏移,偏移量根據 滾動高度/預估平均高度*列數 去估算一個,然後在跟新這個預估下標進行查找。

抖動問題

我們 absolute 定位會撐開容器高度,但是滾動時候還是會存在抖動問題,我們可以自定義一個元素高度去撐開,這個元素高度也就是我們之前計算的每一列累計高度 sumHeight 中最大的那個了。

過渡動畫問題

當列寬發生變化時,元素位置發生了變化,在可視區域的元素也發生了變化,有些元素可能之前並沒有渲染,所以使用上面 CSS 會存在新出現元素不會產生過渡動畫。好在我們能夠很清楚的知道元素原位置信息和新的位置信息,我們可以利用 FLIP 來處理這動畫,很容易控制元素過渡變化,如果有些元素之前不存在,就沒有原位置信息,我們可以在可視範圍內給他隨機生成一個位置進行過渡,保證每一個元素都有個過渡效果避免僵硬。

總結

上面情況僅僅是針對動態列數量,又能計算出高度情況下優化,可能業務中也是可能存在每項高度是動態的,這個時候可以採用預估元素高度在渲染後緩存大小位置等信息,或者離屏渲染等方案解決做出進一步的優化處理。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 資料庫用戶通常依賴隔離級別來確保數據一致性,但很多資料庫卻並未達到其所表明的級別。主要原因是:一方面,資料庫開發者對各個級別的理解有細微差異;另一方面,實現層面沒有達到理論上的要求。 用戶在使用或開發者在交付資料庫前,需要對隔離級別進行快速的正確性驗證,並且希望驗證是可靠的(沒有誤差)、快速的(多項 ...
  • 摘要:本文講解了GaussDB(DWS)上模糊查詢常用的性能優化方法,通過創建索引,能夠提升多種場景下模糊查詢語句的執行速度。 本文分享自華為雲社區《GaussDB(DWS) 模糊查詢性能優化》,作者: 黎明的風 。 在使用GaussDB(DWS)時,通過like進行模糊查詢,有時會遇到查詢性能慢的 ...
  • 1 前言 ElasticSearch是一個實時的分散式搜索與分析引擎,常用於大量非結構化數據的存儲和快速檢索場景,具有很強的擴展性。縱使其有諸多優點,在搜索領域遠超關係型資料庫,但依然存在與關係型資料庫同樣的深度分頁問題,本文就此問題做一個實踐性分析探討 2 from + size分頁方式 from ...
  • 項目中需要計算使用年限,按月份算。剛開始踩了坑,不足1年應該按1年算。記錄下~ 和當前時間比較,用DATEDIFF函數DateDiff(month,比較的時間,getdate())先算出月份,再除以12算年份 查看代碼 --月份差值 2.083333 select CONVERT(decimal,D ...
  • 這裡說的占位符,實際就是排版時需要展示的圖片,圖片基於占位符填充,那麼處理圖片時,怎麼解決占位符(圖片)的上下偏移在設置占位符屬性時,我通過以下方法來實現它: + (NSAttributedString *)wxImageAttributeCoreTextFromPaperQuestion:(WXT ...
  • 本文繼續組件庫開發環境的搭建,前面兩篇分別介紹了組件庫中組件項目的初始化、組件庫 CSS 架構,本文介紹通用工具庫的搭建。在組件開發過程中,可能會調用一些通用的工具函數,這些工具函數便可以提取到一個獨立的 npm 包中。 ...
  • electron vue3 項目搭建 一.vue項目搭建 安裝electron 需要搭建vue項目,這裡用的vue3項目。 1.安裝 下載 node 這裡用的16版本 https://nodejs.org/zh-cn/ 2.設置淘寶鏡像 npm config set registry https:/ ...
  • 有足夠的地圖數據,可以點擊到街道,示例我只出到市級 以umi為框架,版本是: "react": "^18.2.0", "umi": "^4.0.29", "echarts": "^5.4.0", "echarts-for-react": "^3.0.2", 示例圖: 示例中需要地圖的Geojson數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...