欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React實現(xiàn)虛擬滾動的三種思路詳解

 更新時間:2024年04月25日 08:24:03   作者:youthfighter  
在??web??開發(fā)的過程中,或多或少都會遇到大列表渲染的場景,為了解決大列表造成的渲染壓力,便出現(xiàn)了虛擬滾動技術,本文主要介紹虛擬滾動的三種思路,希望對大家有所幫助

1 前言

在??web??開發(fā)的過程中,或多或少都會遇到大列表渲染的場景,例如全國城市列表、通訊錄列表、聊天記錄列表等等。當列表數(shù)據(jù)量為幾百條時,依靠瀏覽器本身的性能基本可以支撐,一般不會出現(xiàn)卡頓的情況。但當列表數(shù)量級達到上千,頁面渲染或操作就可能會出現(xiàn)卡頓,而當列表數(shù)量突破上萬甚至十幾萬時,網(wǎng)頁可能會出現(xiàn)嚴重卡頓甚至直接崩潰。為了解決大列表造成的渲染壓力,便出現(xiàn)了虛擬滾動技術。本文主要介紹虛擬滾動的基本原理,以及子項定高的虛擬滾動列表的簡單實現(xiàn)。

2 基本原理

首先來看一下直接渲染的大列表的實際表現(xiàn)。以有10萬條子項的簡單大列表為例,頁面初始化時,??FP??時間大概在4000ms左右,大量的時間被用于執(zhí)行腳本和渲染。而當快速滾動列表時,網(wǎng)頁的??FPS??維持在35左右,可以明顯的感覺到頁面的卡頓。借助谷歌??Lighthouse??工具,最終網(wǎng)頁的性能得分僅為49。通過實際訪問體驗和性能相關數(shù)據(jù)可以看出,直接渲染的大列表在加載操作方面體驗是十分糟糕的。點擊? ?鏈接??,體驗實際效果。

通過以上的測試數(shù)據(jù)可以看到,在頁面初始化時腳本的執(zhí)行和??DOM??渲染占據(jù)的大部分的時間。而隨著列表子項的減少,頁面初始化時間會變短并且滾動時??FPS??可以保持在60。由此可以得出結(jié)論大量節(jié)點的渲染是頁面初始化慢和操作卡頓的主要原因。

雖然大列表的數(shù)據(jù)量很大,但是設備的顯示區(qū)域是有限的,也就是說在同一時間,用戶看到的內(nèi)容是有限的。利用這一特點,可以將大列表按需渲染。也就是只渲染某一時刻用戶看的到的內(nèi)容,當用戶滾動頁面時,再通過??JS??的計算重現(xiàn)調(diào)整視窗內(nèi)的內(nèi)容,這樣可以把列表子項的數(shù)量級別從幾萬降到幾十。

借助按需渲染的思想來優(yōu)化大列表在實現(xiàn)層面可以分成三步,一是確定當前視窗在哪,二是確定當前要真實渲染哪些節(jié)點,三是把渲染的節(jié)點移動到視窗內(nèi)。對于問題一,視窗的位置對于長列表來說,其開始位置為列表滾動區(qū)域的??scrollTop??。對于問題二,按照視窗外內(nèi)容不渲染的思路,則應該渲染數(shù)組索引從??Math.floor(scrollTop/itemHeight)??開始共??Math.ceil(viewHeight/itemHeight)??個元素。對于問題三,有多種實現(xiàn)思路,以下將介紹幾種常見虛擬滾動的實現(xiàn)方式。

解釋:

  • scrollTop:列表滾動區(qū)域的scrollTop
  • itemHeight:子節(jié)點的高度
  • viewHeight:視窗的高度

3 實現(xiàn)

3.1 Transform

該方案主要是通過監(jiān)聽滾動區(qū)域的滾動事件,動態(tài)計算視窗內(nèi)渲染節(jié)點的開始索引以及偏移量,然后重新觸發(fā)渲染節(jié)點的渲染并將內(nèi)容通過??transform??屬性將該部分內(nèi)容移動到視窗內(nèi)。

簡單代碼實現(xiàn)如下,? ?線上效果預覽??

function VirtualList(props) {
  const { list, itemHeight } = props;
  const [start, setStart] = useState(0);
  const [count, setCount] = useState(0);
  const scrollRef = useRef(null);
  const contentRef = useRef(null);
  const totalHeight = useMemo(() => itemHeight * list.length, [list.length]);
  useEffect(() => {
    setCount(Math.ceil(scrollRef.current.clientHeight / itemHeight));
  }, []);
  const scrollHandle = () => {
    const { scrollTop } = scrollRef.current;
    const newStart = Math.floor(scrollTop / itemHeight);
    setStart(newStart);
    contentRef.current.style.transform = `translate3d(0, ${
      newStart * itemHeight
    }px, 0)`;
  };
  const subList = list.slice(start, start + count);
  return (
    <div className="virtual-list" onScroll={scrollHandle} ref={scrollRef}>
      <div style={{ height: totalHeight + "px" }}>
        <div className="content" ref={contentRef}>
          {subList.map(({ idx }) => (
            <div
              key={idx}
              className="item"
              style={{ height: itemHeight + "px" }}
            >
              {idx}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

類似思想實現(xiàn)的開源項目:? ?react-list??

3.2 Absolute

該方案與??transform??方案類似,都是通過監(jiān)聽滾動區(qū)域的滾動事件,動態(tài)的計算要顯示的內(nèi)容。但??transform??方案顯示內(nèi)容的偏量是動態(tài)計算并賦值的,而該方案則是利用??absolute??屬性直接將待渲染的節(jié)點定位到其該出現(xiàn)的位置。例如,索引為0的元素,其必定在??top = 0 * itemHeight??的位置,索引為??start??的元素必定在??top = start * itemHeight??的位置,這與視窗位置無關。視窗只決定了要渲染那些子節(jié)點,不影響子節(jié)點的相對位置。

簡單代碼實現(xiàn)如下,? ?線上效果預覽??。

function VirtualList(props) {
  const { list, itemHeight } = props;
  const [start, setStart] = useState(0);
  const [count, setCount] = useState(0);
  const scrollRef = useRef(null);
  const totalHeight = useMemo(() => itemHeight * list.length, [list.length]);
  useEffect(() => {
    setCount(Math.ceil(scrollRef.current.clientHeight / itemHeight));
  }, []);
  const scrollHandle = () => {
    const { scrollTop } = scrollRef.current;
    const newStart = Math.floor(scrollTop / itemHeight);
    setStart(newStart);
  };
  const subList = list.slice(start, start + count);
  return (
    <div className="virtual-list" onScroll={scrollHandle} ref={scrollRef}>
      <div style={{ height: `${totalHeight}px` }}>
        {subList.map(({ idx }) => (
          <div
            key={idx}
            className="item"
            style={{
              position: "absolute",
              width: "100%",
              height: itemHeight + "px",
              top: `${(idx - 1) * itemHeight}px`,
            }}
          >
            {idx}
          </div>
        ))}
      </div>
    </div>
  );
}

類似思想實現(xiàn)的開源項目:? ?react-virtualized??

3.3 Padding

該方案與以上兩種方案有較大的差別,主要體現(xiàn)在以下兩點:一是列表高度撐起的方式不同,以上兩種方案的高度是通過設置??height = list.length * itemHeight???的方式撐起來的,而該方案則是通過??paddingTop + paddingBottom + renderHeight???的方式來撐起來的。二是列表的重新渲染時機不同,以上兩種方案會在??Math.floor(scrollTop / itemHeight)??值變化時重新渲染,而該方案則是在渲染節(jié)點"不夠"在視窗內(nèi)顯示時觸發(fā)。

舉個例子,假定視窗一次可以顯示10個,同時配置虛擬滾動組件一次渲染50節(jié)點,那么當屏幕滾動到第11個時并不需要渲染,因為此時顯示的是11-20個節(jié)點,而將要顯示的21-50已經(jīng)渲染好了。只有當滾動到第41個的時候才需要重新渲染,因為屏幕外已經(jīng)沒有渲染好的節(jié)點了,再滾動就要顯示白屏了。根據(jù)以上例子進一步的分析臨界條件,當前渲染位置為??[itemHeight * start, itemHeight * (start + count)]???,視窗顯示的位置為??[scrollTop, scrollTop + clientHeight]??。

當??scrollTop + clientHeight >= itemHeight * (start + count)??時,說明視窗顯示位置超過了渲染的最大位置,重新觸發(fā)渲染調(diào)整渲染位置,避免底部白屏。
當??scrollTop <= itemHeight * start??時,說明視窗顯示位置不足渲染的最小位置,重新觸發(fā)渲染調(diào)整渲染位置,避免頂部白屏。

簡單代碼實現(xiàn)如下,? ?線上效果預覽??。

function VirtualList(props) {
  // 注意該count是外部傳入的
  const { list, itemHeight, count } = props;
  const totalHeight = useMemo(() => itemHeight * list.length, [list.length]);
  const currentHeight = useMemo(() => itemHeight * count, [itemHeight, count]);
  const [start, setStart] = useState(0);
  const scrollRef = useRef(null);
  const paddingTop = useMemo(() => itemHeight * start, [start]);
  const paddingBottom = useMemo(
    () => totalHeight - itemHeight * start - currentHeight,
    [start]
  );
  const scrollHandle = () => {
    const { scrollTop, clientHeight } = scrollRef.current;
    if (
      scrollTop + clientHeight >= itemHeight * (start + count) ||
      scrollTop <= itemHeight * start
    ) {
      const newStart = Math.floor(scrollTop / itemHeight);
      setStart(Math.min(list.length - count, newStart));
    }
  };
  const subList = list.slice(start, start + count);
  return (
    <div className="virtual-list" onScroll={scrollHandle} ref={scrollRef}>
      <div
        style={{
          paddingTop: `${paddingTop}px`,
          paddingBottom: `${paddingBottom}px`,
        }}
      >
        {subList.map(({ idx }) => (
          <div key={idx} className="item" style={{ height: itemHeight + "px" }}>
            {idx}
          </div>
        ))}
      </div>
    </div>
  );
}

類似思想實現(xiàn)的開源項目:? ?vue-virtual-scroll-list??

4 性能

使用以上三種方案分別測試頁面加載速度和滾動時的??FPS??發(fā)現(xiàn),三者之間的性能數(shù)據(jù)無明顯差別。頁面初始化時,??FP??時間提前到450ms左右,快速滾動時的??FPS??基本穩(wěn)定在60左右,網(wǎng)站的谷歌??Lighthouse??性能跑分提高到95左右。實際訪問體驗和性能相關數(shù)據(jù)都得到了較大的提升。

5 總結(jié)

本文主要是介紹了虛擬滾動的基本原理,并根據(jù)常見虛擬滾動開源庫的實現(xiàn)思路使用??react??進行了簡單的實現(xiàn)。通過簡單的實現(xiàn)可以幫助我們更好的理解虛擬滾動原理,不過在實際開發(fā)過程中,還是建議大家使用成熟的開源庫。

到此這篇關于React實現(xiàn)虛擬滾動的三種思路詳解的文章就介紹到這了,更多相關React虛擬滾動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • React表中顯示JSON數(shù)據(jù)demo

    React表中顯示JSON數(shù)據(jù)demo

    這篇文章主要為大家介紹了React表中顯示JSON數(shù)據(jù)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 詳解React中錯誤邊界的原理實現(xiàn)與應用

    詳解React中錯誤邊界的原理實現(xiàn)與應用

    在React中,錯誤邊界是一種特殊的組件,用于捕獲其子組件樹中發(fā)生的JavaScript錯誤,并防止這些錯誤冒泡至更高層,導致整個應用崩潰,下面我們就來看看它的具體應用吧
    2024-03-03
  • React 項目遷移 Webpack Babel7的實現(xiàn)

    React 項目遷移 Webpack Babel7的實現(xiàn)

    這篇文章主要介紹了React 項目遷移 Webpack Babel7的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • React State狀態(tài)與生命周期的實現(xiàn)方法

    React State狀態(tài)與生命周期的實現(xiàn)方法

    這篇文章主要介紹了React State狀態(tài)與生命周期的實現(xiàn)方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • react-three/postprocessing庫的參數(shù)中文含義使用解析

    react-three/postprocessing庫的參數(shù)中文含義使用解析

    這篇文章主要介紹了react-three/postprocessing庫的參數(shù)中文含義使用總結(jié),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • React?Native性能優(yōu)化紅寶書方案詳解

    React?Native性能優(yōu)化紅寶書方案詳解

    React?Native?是Facebook在React.js?Conf2015推出的開源框架,使用React和應用平臺的原生功能來構(gòu)建Android和iOS應用,這篇文章主要介紹了React?Native性能優(yōu)化紅寶書,需要的朋友可以參考下
    2024-06-06
  • react父組件調(diào)用子組件的方式匯總

    react父組件調(diào)用子組件的方式匯總

    在react中常用props實現(xiàn)子組件數(shù)據(jù)到父組件的傳遞,但是父組件調(diào)用子組件的功能卻不常用,下面這篇文章主要給大家介紹了關于react父組件調(diào)用子組件的相關資料,需要的朋友可以參考下
    2022-08-08
  • react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局

    react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局

    這篇文章主要為大家詳細介紹了react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局,一個路由小案例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • Electron打包React生成桌面應用方法詳解

    Electron打包React生成桌面應用方法詳解

    這篇文章主要介紹了React+Electron快速創(chuàng)建并打包成桌面應用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-12-12
  • React 如何使用時間戳計算得到開始和結(jié)束時間戳

    React 如何使用時間戳計算得到開始和結(jié)束時間戳

    這篇文章主要介紹了React 如何拿時間戳計算得到開始和結(jié)束時間戳,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-09-09

最新評論