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

Vue3實現(xiàn)虛擬列表的示例代碼

 更新時間:2024年11月07日 11:26:08   作者:E_Yen  
虛擬列表是一種優(yōu)化長列表渲染的技術(shù),它可以在保持流暢性的同時,渲染大量的數(shù)據(jù),本文主要介紹了如何通過Vue3實現(xiàn)一個虛擬列表,感興趣的可以了解下

前言

本文的虛擬列表基于上一篇動態(tài)高度虛擬列表原理解析中的核心代碼和Vue3實現(xiàn),上文是看懂思考&設計部分內(nèi)容的前提條件。

使用

安裝

npm install @e.yen/virtual-scroll-vue

在main.js中顯式導入或者在組件中按需導入

// main.ts
import VirtualScroll from '@e.yen/virtual-scroll-vue'
app.use(VirtualScroll)

// 或者
// AnyComponent.vue
import { VirtualScroll } from '@e.yen/virtual-scroll-vue'

導入樣式

// main.ts
import '@e.yen/virtual-scroll-vue/dist/style.css'

Github倉庫:virtual-scroll-vue

props參數(shù)

參數(shù)類型默認值是否必須描述
itemsVirtualScrollItem[]-??列表數(shù)據(jù)
placeholderVirtualScrollItem-?最小子項的模擬數(shù)據(jù)
startPosition[number, number][0, 0]?列表初始位置
preservednumber-?子項的最小高度
paddingnumber100?預渲染區(qū)域高度
type VirtualScrollItem = {
  key?: any
  height?: number // 可以指定元素高度,具有最高優(yōu)先級
  [k: string | symbol]: any
}

注意preserved 的優(yōu)先級高于 placeholder

expose方法

方法參數(shù)返回值類型描述
scrolldelta: number, duration?: number-滾動指定距離
transportnewStartPosition: [number, number]-傳送到指定位置
getPosition-[number, number]獲取列表當前位置

注意事項

讓子項擁有唯一的key

由于列表基于v-for渲染子項,因此為子項擁有唯一的key能夠大幅度提升性能表現(xiàn):

const items = [
  {
    key: 'ABC',
  },
  {
    key: 'BCD',
  },
]

任何時候都不要使最小高度為0

由于元素在被渲染之前無法確認其高度,因此列表依賴于子項目的最小高度確定渲染索引范圍。雖然能夠通過 placeholder 將最小高度設為0,但這會導致列表渲染后續(xù)所有子項:

<!-- preserved默認具有最小值5px,設為0不會有任何效果 -->
<VirtualScroll :items="data" :preserved="0">
  <template #default="{ item }">
    <!-- 子項目結(jié)構(gòu) -->
  </template>
</VirtualScroll>

<!-- 通過placeholder將最小高度設為0,會導致列表一次性渲染所有元素 -->
<VirtualScroll :items="data" :placeholder="{}">
  <template #default="{ item }">
    <!-- 高度為0的子項目 -->
    <div></div>
  </template>
</VirtualScroll>

不要向起始元素之前添加新數(shù)據(jù)

由于列表的渲染索引范圍由起始元素索引、起始元素偏移量和最小項目高度共同決定,因此向起始元素之前的位置添加新元素會導致意料之外的結(jié)果:

// 假設 startIndex 為 1
// 向items頭部添加新數(shù)據(jù),會導致列表渲染的起始元素變?yōu)榕f數(shù)組中索引為 0 的項
items.unshift({
  key: 'CDE',
})

不要修改預渲染區(qū)內(nèi)的元素高度

具體來說是不要修改靠近列表排列起始方向一側(cè)的元素高度(例如列表從上往下排列,則不要修改上方預渲染區(qū)域內(nèi)的元素高度)

不是強制要求的,相反,列表仍能正常工作,但是對于具有過渡效果的高度變化,受制于 ResizeObserver 的滯后性,列表可能出現(xiàn)微小的抖動導致用戶體驗變差

僅在觸屏設備上使用

雖然列表支持滾輪滾動,但是暫不支持滾動條,在PC等非觸屏設備上應考慮使用分頁

示例

列表會自動獲取可視區(qū)域大小,寬高默認為 100%,建議通過 .virtual-scroll_container 進行覆蓋,或者在組件外部包裹一個容器

列表會將要渲染的數(shù)據(jù)通過默認作用域插槽傳遞出來,天然支持動態(tài)高度

<script setup lang="ts">
import { ref } from 'vue'
import DynamicItem from '@/components/DynamicItem/DynamicItem.vue'
import { VirtualScroll, type VirtualScrollInstance } from '@e.yen/virtual-scroll-vue'
import {
  generateRandomFirstWord,
  generateRandomWord,
  lorem,
} from '@/utils/helper'
const defaultItem = { name: 'ab', comment: 'abc', index: -1 }
const items = ref(
  new Array(10000).fill(0).map((_, i) => ({
    key: i.toString(),
    name:
      generateRandomFirstWord() +
      (Math.random() > 0.5
        ? ' ' + generateRandomWord(Math.floor(Math.random() * 8) + 2)
        : ''),
    comment: lorem(Math.floor(Math.random() * 5) + 1),
    index: i,
  })),
)

const vlist = ref<VirtualScrollInstance>()
function lighteningScroll(delta: number) {
  vlist.value!.scroll(delta)
}
</script>

<template>
  <div class="page">
    <div class="scroll_container">
      <VirtualScroll
        ref="vlist"
        :items="items"
        :placeholder="defaultItem"
        :start-position="[1000, 0]"
        :padding="0"
      >
        <template #default="{ item }">
          <DynamicItem
            :index="item.index"
            :name="item.name"
            :comment="item.comment"
          ></DynamicItem>
        </template>
      </VirtualScroll>
    </div>
    <button @click="lighteningScroll(-100000)">向上極速滾動測試</button>
    <button @click="lighteningScroll(100000)">向下極速滾動測試</button>
  </div>
</template>

思考&設計

虛擬列表的關(guān)鍵在于如何獲取列表項高度,確定了每項的高度,就能確定渲染多少個元素。即如何獲取列表項高度決定了虛擬列表的實際表現(xiàn),在這里給出三個思路:

1.固定步長

在瀏覽器每一幀渲染之前進行判斷,若虛擬列表中的元素不足以占滿整個可視區(qū)域且仍有未被渲染的后續(xù)元素,則將渲染結(jié)束的索引后移 n 位。

  • 優(yōu)點:實現(xiàn)簡單直觀
  • 缺點:引入了超參數(shù) n,需要依照實際情況確定一個較為合理的值,較大則造成較多性能浪費,較小則容易導致用戶快速滾動時出現(xiàn)空白頁

2.預渲染 + 固定步長

原理與上述思路沒有區(qū)別,優(yōu)缺點與上面一致,可以認為是在計算元素是否足以占滿可視區(qū)域時,將參與計算的可視區(qū)域進行擴大,從而讓列表提前渲染元素。能夠在一定程度上緩解空白問題,但治標不治本,當以更快的速度滾動(比如通過代碼觸發(fā))時仍會出現(xiàn)空白頁。

3.預渲染 + 高度預測

觀察發(fā)現(xiàn),出現(xiàn)空白頁的根本原因是無法確定究竟最多還需要多少個元素才能占滿可視區(qū)域,為此,可以通過每項的最小高度預測最多需要向后渲染多少個子項,從而保證始終有足夠的元素占滿可視區(qū)域。

  • 優(yōu)點:通過高度預測徹底解決了空白問題
  • 缺點:在快速滾動時,由于實際高度與預測高度可能不同,可能會導致落點位置與預期不符

預測實現(xiàn)

高度預測主要有兩種實現(xiàn)方式:

  • 在設計時就確定好最小高度,單位為CSS像素。實現(xiàn)最為簡單且性能最高,但是對于一些使用了相對單位的項目結(jié)構(gòu)不友好
  • 根據(jù)項目結(jié)構(gòu)自動獲取最小高度。通過傳入一個具有最小高度的元素的模擬數(shù)據(jù),列表動態(tài)地獲取最小高度,通用性最好

錯位處理

維持前文的約定:

起始位置 startPosition[startIndex, offset] 二元組構(gòu)成

渲染信息 renderInfo[viewHeight, paddingHeight, listHeight] 三元組構(gòu)成

函數(shù) move(startPosition, delta, renderInfo, getHeight) => void 通過起始位置、移動距離、渲染信息和高度獲取函數(shù)計算本次移動后的新起始位置

高度預測會導致快速滾動時出現(xiàn)到達的位置與預期不同的錯位問題。粗略地看,當一幀內(nèi)列表滾動到了未渲染區(qū)域,就會轉(zhuǎn)為使用預測高度繼續(xù)計算下一幀的起始位置,預測高度與實際高度不一致時就會導致列表“移動過頭”。舉個例子,假設某個未被渲染的元素實際高度為 110px,但在計算時將其視為 100px,那么剩余滾動距離就多了 10px,這是造成錯位的根本原因。

既然知道了問題,那么研究其發(fā)生條件變得十分重要:

根據(jù)移動函數(shù) move 的計算方式得知:

單次移動中,如果:

  • 向上移動距離大于 max(offset - paddingHeight, 0) + 預測高度
  • 向下移動距離大于 listHeight + 預測高度

就可能導致錯位

由于虛擬列表是由起始位置決定的,因此向上滾動時的錯位將是致命的。原因是計算新的 offset 時使用了預測的高度,但實際高度大于預測高度,導致后續(xù)所有元素都下移。在這里使用了自定義指令 + ResizeObserver的方式解決,處理過程分為3步:

v-auto-record 在元素被掛載時,緩存本次計算時使用的高度

v-watch-size 在元素高度被緩存后調(diào)用 elementResize 進行處理

elementResize 根據(jù)情況更新高度緩存,以及選擇修改 offset 或重新渲染

// vAutoRecord.ts
export default <Directive>{
  mounted(el, binding) {
    if (binding.arg && binding.arg === 'mounted') binding.value?.(el)
  },
  unmounted(el, binding) {
    if (binding.arg && binding.arg === 'unmounted') binding.value?.(el)
  },
}

// vWatchSize.ts
export default <Directive>{
  mounted(el, binding) {
    // ! nextTick保證vWatchSize在vAutoRecord之后執(zhí)行
    nextTick(() => {
      if (binding.value instanceof Function) binding.value(el)
      el.observer = new ResizeObserver(() => {
        if (binding.value instanceof Function) binding.value(el)
      })
      el.observer.observe(el)
    })
  },
  beforeUnmount(el) {
    if (el.observer) {
      el.observer.disconnect()
      delete el.observer
    }
  },
}
<li
  v-for="(i, index) in renderRange"
  :key="props.items[i].key || index"
  class="virtual-scroll_item"
  v-watch-size="el => elementResize(i, el)"
  v-auto-record:mounted="el => elementMap.set(i, el)"
  v-auto-record:unmounted="() => elementMap.delete(i)"
>
  <slot :item="props.items[i]"></slot>
</li>
const elementResize = (index: number, element: HTMLElement) => {
  const cur = element.getBoundingClientRect().height
  const pre = getHeight(index) // 取出高度緩存
  let isInPaddingRange = false
  if (cur === pre) return

  // 判斷高度變化的元素是否在預加載區(qū)間
  let offset = startPosition.value[1]
  let itemIndex = startPosition.value[0]
  let height = getHeight(itemIndex)
  while (height >= 0 && offset > 0) {
    if (itemIndex === index) {
      isInPaddingRange = true
      break
    }
    offset -= height
    height = getHeight(++itemIndex)
  }

  // 更新高度緩存
  updateHeight(index)

  if (isInPaddingRange) {
    // 如果高度變化的元素在預加載區(qū)間內(nèi),將offset加上高度變化量
    startPosition.value[1] += cur - pre
  } else if (cur < pre) {
    // 如果高度變化的元素不在預加載區(qū)間內(nèi),重新渲染
    renderTrigger.value = !renderTrigger.value
  }
}

至于向下滾動時的錯位問題,這是高度預測的固有局限,因此沒有很好的解決方法,一種可能的蒙混過關(guān)的解決方式是:快速滾動時用戶無法分辨頁面上到底呈現(xiàn)了什么,可以在滾動結(jié)束的下一幀立即將起始位置修改為目標位置,實現(xiàn)向下快速滾動到指定位置的錯覺。但如果在列表項中出現(xiàn)了編號這樣容易讓小把戲穿幫的內(nèi)容,可能需要考慮用 transport 定制滾動效果。

以上就是Vue3實現(xiàn)虛擬列表的示例代碼的詳細內(nèi)容,更多關(guān)于Vue3虛擬列表的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue關(guān)于下載文件常用的幾種方式

    vue關(guān)于下載文件常用的幾種方式

    這篇文章主要介紹了vue關(guān)于下載文件常用的幾種方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • Vue數(shù)據(jù)變化后頁面更新詳細介紹

    Vue數(shù)據(jù)變化后頁面更新詳細介紹

    這篇文章主要介紹了Vue在數(shù)據(jù)發(fā)生變化后是如何更新頁面的,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-10-10
  • vue項目中api接口管理總結(jié)

    vue項目中api接口管理總結(jié)

    本篇文章給大家介紹了vue項目中API接口管理的相關(guān)知識點,以及相關(guān)JS代碼分析,有興趣的朋友參考下。
    2018-04-04
  • Vue?element樹形控件添加虛線詳解

    Vue?element樹形控件添加虛線詳解

    這篇文章主要為大家介紹了Vue?element樹形控件添加虛線,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助<BR>
    2021-11-11
  • 關(guān)于vue 的slot分發(fā)內(nèi)容 (多個分發(fā))

    關(guān)于vue 的slot分發(fā)內(nèi)容 (多個分發(fā))

    這篇文章主要介紹了關(guān)于vue 的slot分發(fā)內(nèi)容 (多個分發(fā)),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • vue樹形結(jié)構(gòu)獲取鍵值的方法示例

    vue樹形結(jié)構(gòu)獲取鍵值的方法示例

    這篇文章主要介紹了vue樹形結(jié)構(gòu)獲取鍵值的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • 一個基于vue3+ts+vite項目搭建初探

    一個基于vue3+ts+vite項目搭建初探

    當市面上主流的組件庫不能滿足我們業(yè)務需求的時候,那么我們就有必要開發(fā)一套屬于自己團隊的組件庫,下面這篇文章主要給大家介紹了一個基于vue3+ts+vite項目搭建的相關(guān)資料,需要的朋友可以參考下
    2022-05-05
  • vue實現(xiàn)下拉框的多選功能(附后端處理參數(shù))

    vue實現(xiàn)下拉框的多選功能(附后端處理參數(shù))

    本文介紹了如何使用Vue實現(xiàn)下拉框的多選功能,實現(xiàn)了在選擇框中選擇多個選項的功能,文章詳細介紹了實現(xiàn)步驟和示例代碼,對于想要了解如何使用Vue實現(xiàn)下拉框多選功能的讀者具有一定的參考價值
    2023-08-08
  • vue-form表單驗證是否為空值的實例詳解

    vue-form表單驗證是否為空值的實例詳解

    今天小編就為大家分享一篇vue-form表單驗證是否為空值的實例詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-10-10
  • uniapp使用條件編譯#ifdef(跨平臺設備兼容)

    uniapp使用條件編譯#ifdef(跨平臺設備兼容)

    這篇文章主要介紹了uniapp使用條件編譯#ifdef(跨平臺設備兼容),需要的朋友可以參考下
    2022-12-12

最新評論

延川县| 洛浦县| 来凤县| 潞城市| 德钦县| 中宁县| 西林县| 珠海市| 封开县| 连云港市| 高要市| 万山特区| 南通市| 包头市| 三原县| 漳平市| 阳城县| 西畴县| 刚察县| 抚州市| 民乐县| 水城县| 鸡东县| 宾阳县| 南雄市| 青浦区| 夹江县| 额尔古纳市| 贺州市| 南宫市| 上虞市| 灵山县| 平利县| 松桃| 土默特右旗| 平顶山市| 巴马| 河津市| 原阳县| 凤翔县| 峨边|