JS怎么實(shí)現(xiàn)前端長(zhǎng)列表優(yōu)化 5種虛擬滾動(dòng)方案提升萬級(jí)列表性能

前端長(zhǎng)列表優(yōu)化的核心是虛擬滾動(dòng),通過只渲染可視區(qū)域內(nèi)的列表項(xiàng)提升性能。1. 固定高度虛擬滾動(dòng):適用于列表項(xiàng)高度一致的場(chǎng)景,通過計(jì)算滾動(dòng)位置確定可視區(qū)域索引并渲染;2. 動(dòng)態(tài)高度虛擬滾動(dòng):記錄每個(gè)項(xiàng)的實(shí)際高度,適應(yīng)高度不一致的情況;3. intersection observer 虛擬滾動(dòng):利用 api 精確監(jiān)聽元素是否進(jìn)入可視區(qū)域,減少無效渲染;4. react 虛擬滾動(dòng)組件:如 react-window,支持固定和動(dòng)態(tài)高度,使用便捷;5. vue 虛擬滾動(dòng)組件:如 vue-virtual-scroller,適用于 vue 項(xiàng)目。選擇方案時(shí)需考慮高度是否固定及技術(shù),同時(shí)注意優(yōu)化渲染復(fù)雜度、資源加載與數(shù)據(jù)緩存以進(jìn)一步提升性能。

JS怎么實(shí)現(xiàn)前端長(zhǎng)列表優(yōu)化 5種虛擬滾動(dòng)方案提升萬級(jí)列表性能

前端長(zhǎng)列表優(yōu)化,核心在于避免一次性渲染大量dom節(jié)點(diǎn)導(dǎo)致頁面卡頓。虛擬滾動(dòng)是關(guān)鍵,只渲染可視區(qū)域內(nèi)的列表項(xiàng),大幅提升性能。

JS怎么實(shí)現(xiàn)前端長(zhǎng)列表優(yōu)化 5種虛擬滾動(dòng)方案提升萬級(jí)列表性能

解決方案

虛擬滾動(dòng)通過監(jiān)聽滾動(dòng)事件,動(dòng)態(tài)計(jì)算可視區(qū)域內(nèi)的列表項(xiàng),并更新DOM。本質(zhì)上,它是“按需渲染”,只渲染用戶實(shí)際看到的部分。下面提供5種虛擬滾動(dòng)方案,幫你搞定萬級(jí)列表:

JS怎么實(shí)現(xiàn)前端長(zhǎng)列表優(yōu)化 5種虛擬滾動(dòng)方案提升萬級(jí)列表性能

  1. 固定高度虛擬滾動(dòng): 這是最簡(jiǎn)單的方案。假設(shè)每個(gè)列表項(xiàng)高度固定,通過滾動(dòng)條位置計(jì)算出可視區(qū)域起始和結(jié)束的索引,然后只渲染這部分?jǐn)?shù)據(jù)。

    立即學(xué)習(xí)前端免費(fèi)學(xué)習(xí)筆記(深入)”;

    const list = document.getElementById('list'); const itemHeight = 50; // 每個(gè)列表項(xiàng)高度 const visibleCount = 20; // 可視區(qū)域顯示多少個(gè) const totalHeight = data.length * itemHeight; // 列表總高度  list.style.height = totalHeight + 'px'; // 設(shè)置容器高度,撐開滾動(dòng)條  function renderList(startIndex, endIndex) {   // 清空現(xiàn)有列表   list.innerHTML = '';   for (let i = startIndex; i <= endIndex; i++) {     const item = document.createElement('div');     item.style.height = itemHeight + 'px';     item.textContent = data[i];     list.appendChild(item);   } }  list.addEventListener('scroll', () => {   const scrollTop = list.scrollTop;   const startIndex = Math.floor(scrollTop / itemHeight);   const endIndex = Math.min(startIndex + visibleCount - 1, data.length - 1);   renderList(startIndex, endIndex); });  // 初始渲染 renderList(0, visibleCount - 1);

    這種方案簡(jiǎn)單直接,但要求列表項(xiàng)高度一致。如果高度不一致,會(huì)導(dǎo)致計(jì)算錯(cuò)誤,體驗(yàn)很差。

    JS怎么實(shí)現(xiàn)前端長(zhǎng)列表優(yōu)化 5種虛擬滾動(dòng)方案提升萬級(jí)列表性能

  2. 動(dòng)態(tài)高度虛擬滾動(dòng): 解決固定高度的局限性。需要記錄每個(gè)列表項(xiàng)的實(shí)際高度,并在滾動(dòng)時(shí)動(dòng)態(tài)計(jì)算可視區(qū)域。可以使用 getBoundingClientRect() 獲取元素高度。

    const list = document.getElementById('list'); const itemHeights = []; // 存儲(chǔ)每個(gè)列表項(xiàng)高度 let totalHeight = 0;  function renderList(startIndex, endIndex) {   list.innerHTML = '';   for (let i = startIndex; i <= endIndex; i++) {     const item = document.createElement('div');     item.textContent = data[i];     list.appendChild(item);      // 獲取高度并存儲(chǔ)     item.onload = () => { // 確保內(nèi)容加載完畢后獲取高度       const height = item.getBoundingClientRect().height;       itemHeights[i] = height;       totalHeight += height; // 動(dòng)態(tài)計(jì)算總高度       list.style.height = totalHeight + 'px';     };   } }  list.addEventListener('scroll', () => {   const scrollTop = list.scrollTop;   let startIndex = 0;   let currentHeight = 0;    // 計(jì)算起始索引   for (let i = 0; i < data.length; i++) {     currentHeight += itemHeights[i] || 50; // 默認(rèn)高度,防止初始計(jì)算出錯(cuò)     if (currentHeight >= scrollTop) {       startIndex = i;       break;     }   }    let endIndex = startIndex;   currentHeight = 0;   // 計(jì)算結(jié)束索引   for (let i = startIndex; i < data.length; i++) {     currentHeight += itemHeights[i] || 50;     if (currentHeight >= scrollTop + list.clientHeight) {       endIndex = i;       break;     }   }    renderList(startIndex, endIndex); });  // 初始渲染 renderList(0, Math.min(20, data.length - 1));

    動(dòng)態(tài)高度的實(shí)現(xiàn)復(fù)雜一些,需要維護(hù)每個(gè)列表項(xiàng)的高度信息,但能適應(yīng)更復(fù)雜的場(chǎng)景。

  3. Intersection Observer 虛擬滾動(dòng): 利用 IntersectionObserver API 監(jiān)聽元素是否進(jìn)入可視區(qū)域。 這種方式可以更精確地判斷元素是否可見,減少不必要的渲染。

    const list = document.getElementById('list'); const observer = new IntersectionObserver(entries => {   entries.forEach(entry => {     if (entry.isIntersecting) {       // 元素進(jìn)入可視區(qū)域,進(jìn)行渲染或加載數(shù)據(jù)       const index = entry.target.dataset.index;       entry.target.textContent = data[index];       observer.unobserve(entry.target); // 停止監(jiān)聽,避免重復(fù)渲染     }   }); });  for (let i = 0; i < data.length; i++) {   const item = document.createElement('div');   item.dataset.index = i;   item.style.height = '50px'; // 占位高度   list.appendChild(item);   observer.observe(item); // 開始監(jiān)聽 }

    IntersectionObserver 的優(yōu)點(diǎn)是性能更好,可以異步監(jiān)聽,不會(huì)阻塞線程

  4. React 虛擬滾動(dòng)組件: 如果你使用 React,有很多現(xiàn)成的虛擬滾動(dòng)組件可以使用,比如 react-window、react-virtualized。 這些組件封裝了虛擬滾動(dòng)的邏輯,使用起來非常方便。

    import { FixedSizeList } from 'react-window';  const Row = ({ index, style }) => (   <div style={style}>     Row {index}   </div> );  const MyListComponent = () => (   <FixedSizeList     height={600}     itemCount={10000}     itemSize={50}     width={800}   >     {Row}   </FixedSizeList> );

    react-window 性能很好,支持固定高度和動(dòng)態(tài)高度的列表。

  5. Vue 虛擬滾動(dòng)組件: Vue 也有類似的組件,比如 vue-virtual-scroller。 用法和 React 組件類似,可以輕松實(shí)現(xiàn)虛擬滾動(dòng)。

    <template>   <recycle-scroller     class="list"     :items="listData"     :item-size="50"   >     <template v-slot="{ item }">       <div>{{ item }}</div>     </template>   </recycle-scroller> </template>  <script> import { RecycleScroller } from 'vue-virtual-scroller' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'  export default {   components: {     RecycleScroller   },   data() {     return {       listData: Array.from({ length: 10000 }, (_, i) => `Item ${i}`)     }   } } </script>

    選擇合適的虛擬滾動(dòng)方案,取決于你的項(xiàng)目需求和技術(shù)棧。

如何選擇合適的虛擬滾動(dòng)方案?

選擇哪種方案,主要看列表項(xiàng)的高度是否固定,以及你使用的前端框架。如果高度固定,第一種方案最簡(jiǎn)單。如果高度不固定,需要考慮動(dòng)態(tài)高度的方案或者使用現(xiàn)成的組件。

虛擬滾動(dòng)性能瓶頸在哪里?

即使使用了虛擬滾動(dòng),如果列表項(xiàng)的渲染邏輯過于復(fù)雜,仍然可能出現(xiàn)性能問題。比如,列表項(xiàng)包含大量的圖片或者復(fù)雜的計(jì)算。這時(shí)候需要優(yōu)化列表項(xiàng)的渲染邏輯,比如使用圖片懶加載、減少計(jì)算量。

除了虛擬滾動(dòng),還有其他優(yōu)化方案嗎?

除了虛擬滾動(dòng),還可以考慮以下優(yōu)化方案:

  • 分頁加載: 將長(zhǎng)列表分成多個(gè)頁面,每次只加載一頁數(shù)據(jù)。
  • 懶加載: 對(duì)于圖片等資源,只在需要顯示的時(shí)候才加載。
  • 數(shù)據(jù)緩存: 將已經(jīng)加載的數(shù)據(jù)緩存起來,避免重復(fù)加載。
  • 骨架屏: 在數(shù)據(jù)加載完成之前,顯示一個(gè)骨架屏,提升用戶體驗(yàn)。

選擇合適的優(yōu)化方案,需要根據(jù)具體的場(chǎng)景進(jìn)行分析。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊6 分享