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

React實(shí)現(xiàn)虛擬滾動(dòng)的三種思路詳解

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

1 前言

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

2 基本原理

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

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

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

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

解釋:

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

3 實(shí)現(xiàn)

3.1 Transform

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

簡(jiǎn)單代碼實(shí)現(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>
  );
}

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

3.2 Absolute

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

簡(jiǎn)單代碼實(shí)現(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>
  );
}

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

3.3 Padding

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

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

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

簡(jiǎn)單代碼實(shí)現(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>
  );
}

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

4 性能

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

5 總結(jié)

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

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

相關(guān)文章

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

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

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

    詳解React中錯(cuò)誤邊界的原理實(shí)現(xiàn)與應(yīng)用

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

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

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

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

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

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

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

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

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

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

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

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

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

    Electron打包React生成桌面應(yīng)用方法詳解

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

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

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

最新評(píng)論