最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

1 前言

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

2 基本原理

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

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

雖然大列表的數(shù)據(jù)量很大,但是設(shè)備的顯示區(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)容不渲染的思路,則應(yīng)該渲染數(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)如下,? ?線上效果預(yù)覽??。

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??的位置,這與視窗位置無關(guān)。視窗只決定了要渲染那些子節(jié)點,不影響子節(jié)點的相對位置。

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

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)在以下兩點:一是列表高度撐起的方式不同,以上兩種方案的高度是通過設(shè)置??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)如下,? ?線上效果預(yù)覽??。

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左右。實際訪問體驗和性能相關(guān)數(shù)據(jù)都得到了較大的提升。

5 總結(jié)

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

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

相關(guān)文章

  • 詳解react-webpack2-熱模塊替換[HMR]

    詳解react-webpack2-熱模塊替換[HMR]

    這篇文章主要介紹了詳解react-webpack2-熱模塊替換[HMR], 小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解

    使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解

    這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • react-native只保留3x圖原理解析

    react-native只保留3x圖原理解析

    這篇文章主要為大家介紹了react-native只保留3x圖原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • React父子組件互相通信的實現(xiàn)示例

    React父子組件互相通信的實現(xiàn)示例

    React中是單向數(shù)據(jù)流,數(shù)據(jù)只能從父組件通過屬性的方式傳給其子組件,本文主要介紹了React父子組件互相通信的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2023-11-11
  • ReactRouterV6如何獲取當前路由參數(shù)

    ReactRouterV6如何獲取當前路由參數(shù)

    這篇文章主要介紹了ReactRouterV6如何獲取當前路由參數(shù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React優(yōu)雅的封裝SvgIcon組件示例

    React優(yōu)雅的封裝SvgIcon組件示例

    這篇文章主要為大家介紹了React優(yōu)雅的封裝SvgIcon組件示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • 關(guān)于React動態(tài)加載路由處理的相關(guān)問題

    關(guān)于React動態(tài)加載路由處理的相關(guān)問題

    這篇文章主要介紹了關(guān)于React動態(tài)加載路由處理的相關(guān)問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • react中使用better-scroll滾動插件的實現(xiàn)示例

    react中使用better-scroll滾動插件的實現(xiàn)示例

    滾動在很多地方都可以使用,本文主要介紹了react中使用better-scroll滾動插件的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧
    2023-07-07
  • react的context和props詳解

    react的context和props詳解

    這篇文章主要介紹了react的context和props的相關(guān)資料,幫助大家更好的理解和學(xué)習使用React,感興趣的朋友可以了解下
    2021-11-11
  • React中updateContainerImpl方法更新容器源碼解析

    React中updateContainerImpl方法更新容器源碼解析

    updateContainerImpl方法是React中更新容器的核心方法,負責創(chuàng)建和調(diào)度更新,通過createUpdate方法創(chuàng)建更新對象,并通過enqueueUpdate方法將其加入Fiber節(jié)點的更新隊列,本文介紹React中updateContainerImpl方法更新容器源碼分析,感興趣的朋友一起看看吧
    2026-01-01

最新評論

中西区| 尚志市| 错那县| 文水县| 盐津县| 青神县| 石棉县| 张北县| 安岳县| 营山县| 岑巩县| 班玛县| 嘉荫县| 龙南县| 剑河县| 黄龙县| 阳原县| 淄博市| 马山县| 泗水县| 厦门市| 吉水县| 葫芦岛市| 新乡县| 鹤岗市| 军事| 个旧市| 和平县| 衡阳县| 铁力市| 元阳县| 朝阳县| 白银市| 错那县| 黑山县| 察雅县| 方山县| 安多县| 五寨县| 施甸县| 安远县|