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

vue實(shí)現(xiàn)不定高虛擬列表的示例詳解

 更新時(shí)間:2023年10月31日 16:04:21   作者:通往自由之路  
這篇文章主要為大家詳細(xì)介紹了在vue環(huán)境單頁(yè)面項(xiàng)目下,如何實(shí)現(xiàn)不定高虛擬列表,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

虛擬列表主要解決大數(shù)據(jù)量數(shù)據(jù)一次渲染性能差的問題。

之前寫過(guò)一篇關(guān)于虛擬列表實(shí)現(xiàn)的文章:造輪子之不同場(chǎng)景下虛擬列表實(shí)現(xiàn),主要講了定高(高度統(tǒng)一和高度不統(tǒng)一兩種情況)虛擬列表的實(shí)現(xiàn),本文著重研究不定高虛擬列表的實(shí)現(xiàn)。在vue環(huán)境單頁(yè)面項(xiàng)目下研究實(shí)現(xiàn)。

前文講過(guò)虛擬列表的要做的事是確保性能的前提下,利用一定的技術(shù)模擬全數(shù)據(jù)一次性渲染后效果。

定高虛擬列表原理

綠色部分為containter,也就是父容器,它會(huì)有固定的高度。黃色部分為content,它是父容器的子元素。

當(dāng)content的高度超過(guò)父容器的高度,就可以滾動(dòng)內(nèi)容區(qū)了,這就是一般滾動(dòng)原理。

虛擬列表需要使用這個(gè)滾動(dòng)原理。虛擬列表使用占位div,設(shè)置占位div的高度為所有列表數(shù)據(jù)的高度進(jìn)而撐開containter,形成滾動(dòng)條。

然后虛擬列表具體渲染過(guò)程中,只是渲染可視區(qū)也就是父容器區(qū)域

至于可視區(qū)域的內(nèi)容滾動(dòng)通過(guò)監(jiān)聽滾動(dòng)條scroll事件,獲取到滾動(dòng)距離scrllTop,轉(zhuǎn)換為可視區(qū)域的偏移位置,同時(shí)獲取渲染數(shù)據(jù)的起始和結(jié)束索引,渲染指定段數(shù)據(jù)形成假象的滾動(dòng)。

不定高內(nèi)容數(shù)渲染

上一篇文章造輪子之不同場(chǎng)景下虛擬列表實(shí)現(xiàn)已經(jīng)給出了定高虛擬列表的實(shí)現(xiàn)。不定高相對(duì)定高的難點(diǎn)在于數(shù)據(jù)沒有渲染之前根本不知道數(shù)據(jù)的實(shí)際高度,解決方案理論上有

  • 在屏幕外渲染,但消耗性能
  • 以預(yù)估高度先行渲染,然后獲取真實(shí)高度并緩存

采用第一種方案顯然是不完美的,所以采用第二個(gè)方案,這也是之前有人實(shí)現(xiàn)過(guò)的。

不定高假數(shù)據(jù)

為了更接近業(yè)務(wù),這里使用vue-codemirror方式渲染數(shù)據(jù),為vue-codemirror造假數(shù)據(jù)

function generateRandomNumber () {
  const min = 100
  const max = 1000
  // 生成隨機(jī)整數(shù)
  const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min
  return randomNumber
}
function getRandomLetter () {
  const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  const randomIndex = Math.floor(Math.random() * letters.length)
  const randomLetter = letters.charAt(randomIndex)
  return randomLetter
}
function generateString (length) {
  const minLength = 100
  const maxLength = 1000

  // 確保長(zhǎng)度在最小和最大范圍內(nèi)
  if (length < minLength) {
    length = minLength
  } else if (length > maxLength) {
    length = maxLength
  }

  // 生成字符串
  const string = getRandomLetter().repeat(length)

  return string
}
const d = []
for (let i = 0; i < 500; i++) {
  const length = generateRandomNumber()
  d.push({
    data: generateString(length),
    index: i
  })
}

這里造了500條,具體是隨機(jī)生成的字符串,字符串長(zhǎng)度100-1000,字符從A-Z中選取。

溫故定高虛擬列表

因?yàn)椴欢ǜ咛摂M列表有和定高虛擬列表相似之處,再來(lái)回顧一下之前定高(統(tǒng)一高度和不統(tǒng)一高度)的解決方案。這里只展示一下統(tǒng)一高度的,不統(tǒng)一高度的可以查看造輪子之不同場(chǎng)景下虛擬列表實(shí)現(xiàn)。統(tǒng)一高度組件代碼

<template>
  <div ref="list" class="render-list-container" @scroll="scrollEvent($event)">
    <!-- 占位div -->
    <div class="render-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    <div class="render-list" :style="{ transform: getTransform }">
      <template
        v-for="item in visibleData"
      >
        <slot :value="item.value"  :height="itemSize + 'px'"  :index="item.id"></slot>
      </template>
    </div>
  </div>
</template>

<script>
export default {
  name: 'VirtualList',
  props: {
    // 所有列表數(shù)據(jù)
    listData: {
      type: Array,
      default: () => []
    },
    // 每項(xiàng)高度
    itemSize: {
      type: Number,
      default: 100
    }
  },
  computed: {
    // 列表總高度
    listHeight () {
      return this.listData.length * this.itemSize
    },
    // 可顯示的列表項(xiàng)數(shù)
    visibleCount () {
      return Math.ceil(this.screenHeight / this.itemSize)
    },
    // 偏移量對(duì)應(yīng)的style
    getTransform () {
      return `translate3d(0,${this.startOffset}px,0)`
    },
    // 獲取真實(shí)顯示列表數(shù)據(jù)
    visibleData () {
      return this.listData.slice(this.start, Math.min(this.end, this.listData.length))
    }
  },
  mounted () {
    this.screenHeight = this.$el.clientHeight
    this.end = this.start + this.visibleCount
  },
  data () {
    return {
      // 可視區(qū)域高度
      screenHeight: 0,
      // 偏移量
      startOffset: 0,
      // 起始索引
      start: 0,
      // 結(jié)束索引
      end: null
    }
  },
  methods: {
    scrollEvent () {
      // 當(dāng)前滾動(dòng)位置
      const scrollTop = this.$refs.list.scrollTop
      // 此時(shí)的開始索引
      this.start = Math.floor(scrollTop / this.itemSize)
      // 此時(shí)的結(jié)束索引
      this.end = this.start + this.visibleCount
      // 此時(shí)的偏移量
      this.startOffset = scrollTop - (scrollTop % this.itemSize)
    }
  }
}
</script>

<style scoped>
.render-list-container {
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
  height: 200px;
}

.render-list-phantom {
  position: absolute;
  left: 0;
  right: 0;
  z-index: -1;
}

.render-list {
  text-align: center;
}

</style>

研究不定高虛擬列表組件

按照統(tǒng)一高度方式渲染

正如上面所說(shuō)為了解決不定高內(nèi)容高度不定的問題,采用

以預(yù)估高度先行渲染,然后獲取真實(shí)高度并緩存方案

所以給每條假數(shù)據(jù)一條預(yù)估高度,然后使用定高虛擬列表渲染數(shù)據(jù),渲染數(shù)據(jù)代碼

<template>
  <div class="render-show">
    <div>
      <NoHasVirtualList :listData="data">
        <template slot-scope="{ item, height }">
          <codemirror
            class="unit"
            :style="{height: height}" 
            v-model="item.data"
            :options="cmOptions"
          ></codemirror>
        </template>
      </NoHasVirtualList>
    </div>
  </div>
</template>

設(shè)置codemirror組件高度固定。查看一下效果

定高下查看.gif

問題很明顯,由于codemirror組件設(shè)置固定高度,導(dǎo)致渲染內(nèi)容擠到一起了,分不清哪個(gè)是哪個(gè)高度方向出現(xiàn)重合。所以預(yù)估高度不是這樣用的,預(yù)估高度的意義:它是一種高度占位,是一種占位是務(wù)必要修正的。

修正高度

為了修正這個(gè)高度,需要等待數(shù)據(jù)渲染后拿到真實(shí)高度,這個(gè)需求可以在vue生命周期函數(shù)updated實(shí)現(xiàn),也可以通過(guò)IntersectionObserver實(shí)現(xiàn)。本文采用updated實(shí)現(xiàn)。

修正高度不僅修正每一條數(shù)據(jù)的高度,因?yàn)橛脕?lái)?yè)纹鹂梢晠^(qū)域的占位div高度也是根據(jù)預(yù)估高度計(jì)算的,所以占位div高度也需要更新,然后還需要更新偏移量。

具體在updated里獲取真實(shí)元素大小,修改對(duì)應(yīng)的尺寸緩存;更新占位div高度(使用計(jì)算屬性實(shí)現(xiàn));更新真實(shí)偏移量。

  updated () {
    this.$nextTick(() => {
      // 獲取真實(shí)元素大小,修改對(duì)應(yīng)的尺寸緩存
      this.updateItemsSize()

      // 更新真實(shí)偏移量
      this.setStartOffset()
    })
  },

獲取數(shù)據(jù)實(shí)際高度,修改對(duì)應(yīng)尺寸緩存

創(chuàng)建計(jì)算屬性_listData拷貝列表數(shù)據(jù)。目的盡量不修改傳進(jìn)來(lái)的listData列表數(shù)據(jù),同時(shí)給渲染列表數(shù)據(jù)添加索引,實(shí)際是給渲染用的visibleCount添加唯一索引

  computed: {
    _listData () {
      return this.listData.reduce((init, cur, index) => {
        init.push({
          // _轉(zhuǎn)換后的索引
          _key: index,
          value: cur
        })
        return init
      }, [])
    },
   ...
  }

緩存每條數(shù)據(jù)的高度、以及數(shù)據(jù)坐標(biāo):用topbottom標(biāo)記

   // 初始化緩存
    initPositions () {
      this.positions = this._listData.map((d, index) => ({
        index,
        height: this.itemSize,
        top: index * this.itemSize,
        bottom: (index + 1) * this.itemSize
      }))
    },

上面計(jì)算屬性_listData以及緩存每條數(shù)據(jù)均是服務(wù)于這一步:獲取渲染數(shù)據(jù)實(shí)際高度,修改對(duì)應(yīng)數(shù)據(jù)緩存尺寸

    // 獲取實(shí)際高度,修正內(nèi)容高度
    updateItemsSize () {
      const nodes = this.$refs.items
      nodes.forEach((node) => {
        // 獲取元素自身的屬性
        const rect = node.getBoundingClientRect()
        const height = rect.height
        const index = +node.id // id就是_listData上的唯一索引
        const oldHeight = this.positions[index].height
        const dValue = oldHeight - height
        // 存在差值
        if (dValue) {
          this.positions[index].bottom = this.positions[index].bottom - dValue
          this.positions[index].height = height
          this.positions[index].over = true // TODO

          for (let k = index + 1; k < this.positions.length; k++) {
            this.positions[k].top = this.positions[k - 1].bottom
            this.positions[k].bottom = this.positions[k].bottom - dValue
          }
        }
      })
    },

更新列表總高度

獲取數(shù)據(jù)實(shí)際高度,修改對(duì)應(yīng)尺寸緩存目的之一是為了更新列表總高度

  computed: {
    ...
    // 列表總高度
    listHeight () {
      return this.positions[this.positions.length - 1].bottom
    },
    ...
  },

上述代碼中this.listHeight是一個(gè)計(jì)算屬性,是占位div的高度。

<template>
      <div
        ref="list"
        class="infinite-list-container"
        @scroll="scrollEvent($event)"
      >
        <!-- 占位div -->
        <div ref="phantom" class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
       ...
      </div>
  </template>

更新真實(shí)偏移量

獲取數(shù)據(jù)實(shí)際高度,修改對(duì)應(yīng)尺寸緩存目的之二是為了更新真實(shí)偏移量。

借助this.positions數(shù)組數(shù)據(jù),通過(guò)設(shè)置this.startOffset,在傳導(dǎo)到計(jì)算屬性this.contentTransform更新偏移量

<template>
    <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
      <!-- 占位div -->
      <div
        class="infinite-list-phantom"
        :style="{ height: listHeight + 'px' }"
      ></div>

      <div
        ref="content"
        :style="{ transform: contentTransform }"
        class="infinite-list"
      >
      ....
      </div>
    </div>
  </template>
  ...
 computed: {
    ...
    // 偏移量對(duì)應(yīng)的style
    contentTransform () {
      return `translateY(${this.startOffset}px)`
    },
     ...
   },
   ...
    // 更新偏移量
    setStartOffset () {
      if (this.start >= 1) {
        const size =
            this.positions[this.start].top -
            (this.positions[this.start - this.aboveCount]
              ? this.positions[this.start - this.aboveCount].top
              : 0)
        this.startOffset = this.positions[this.start - 1].bottom - size
      } else {
        this.startOffset = 0
      }
    }

滾動(dòng)事件

滾動(dòng)事件用以觸發(fā)更新

   // 滾動(dòng)事件
    scrollEvent () {
      // 當(dāng)前滾動(dòng)位置
      const scrollTop = this.$refs.list.scrollTop
      // 更新滾動(dòng)狀態(tài)
      // 排除不需要計(jì)算的情況
      if (
        scrollTop > this.anchorPoint.bottom ||
          scrollTop < this.anchorPoint.top
      ) {
        // 此時(shí)的開始索引
        this.start = this.getStartIndex(scrollTop)
        // 此時(shí)的結(jié)束索引
        this.end = this.start + this.visibleCount
        // 更新偏移量
        this.setStartOffset()
      }
    }

其中this.anchorPoint是計(jì)算屬性

  computed: {
    ...
    anchorPoint () {
      return this.positions.length ? this.positions[this.start] : null
    }
    ...
  },

上述代碼中之所以排除不需要計(jì)算的情況,需要解釋一下。

真實(shí)的滾動(dòng)就是滾動(dòng)條滾動(dòng)了多少,可視區(qū)就向上移動(dòng)多少。但虛擬滾動(dòng)不是。當(dāng)起始索引發(fā)生變化時(shí),渲染數(shù)據(jù)發(fā)生變化了,但渲染數(shù)據(jù)的高度不是連續(xù)的,所以需要?jiǎng)討B(tài)的設(shè)置偏移量。當(dāng)滾動(dòng)時(shí)起始索引不發(fā)生變化時(shí),因?yàn)閿?shù)據(jù)變化是連續(xù)的,此時(shí)可以什么也不做,滾動(dòng)顯示的內(nèi)容由瀏覽器控制。排除的部分就是索引沒發(fā)生變化的情況。

根據(jù)滾動(dòng)高度獲取起始索引方法this.getStartIndex

  methods: {
    ...
    // 獲取列表起始索引
    getStartIndex (scrollTop = 0) {
      // 二分法查找
      return this.binarySearch(this.positions, scrollTop)
    },
    // 二分法查找 用于查找開始索引
    binarySearch (list, value) {
      let start = 0
      let end = list.length - 1
      let tempIndex = null

      while (start <= end) {
        const midIndex = parseInt((start + end) / 2)
        const midValue = list[midIndex].bottom
        if (midValue === value) {
          return midIndex + 1
        } else if (midValue < value) {
          start = midIndex + 1
        } else if (midValue > value) {
          if (tempIndex === null || tempIndex > midIndex) {
            tempIndex = midIndex
          }
          end = end - 1
        }
      }
      return tempIndex
    },
    ...  
  }

效果查看以及優(yōu)化

虛擬.gif

給滾動(dòng)增加緩沖,緩沖就是多渲染幾條,上方和下方渲染額外的數(shù)據(jù),比如前后多渲染2條。增加計(jì)算屬性aboveCountbelowCount,同時(shí)修改visibleData

  computed: {
    ...
    aboveCount () {
      return Math.min(this.start, 2)
    },
    belowCount () {
      return Math.min(this.listData.length - this.end, 2)
    },
    visibleData () {
      const start = this.start - this.aboveCount
      const end = this.end + this.belowCount
      return this._listData.slice(start, end)
    }
  },

存在問題

即便是給滾動(dòng)增加緩沖,過(guò)快滑動(dòng)時(shí)依然會(huì)出現(xiàn)白屏現(xiàn)象,究其本質(zhì)是滾動(dòng)過(guò)快而真實(shí)dom更新趕不上它

有多余.gif

總結(jié)

本文主要研究了不定高虛擬列表的一種實(shí)現(xiàn)。基本原理依然是原生滾動(dòng)觸發(fā),渲染首先是預(yù)估高度,之后數(shù)據(jù)渲染后更新預(yù)估高度、更新占位div高度、更新偏移量。

另外就是對(duì)于滾動(dòng)事件做限制,如果滾動(dòng)高度恰好位于當(dāng)前元素范圍內(nèi)不做處理。

另外對(duì)于數(shù)據(jù)更新除了可以使用vue的生命周期函數(shù)updated還可以使用IntersectionObserver實(shí)現(xiàn)。

后期計(jì)劃:為了解決過(guò)快滑動(dòng)導(dǎo)致的白屏現(xiàn)象,會(huì)將不定高虛擬列表與虛擬滾動(dòng)結(jié)合。虛擬滾動(dòng)前幾天寫過(guò)一篇實(shí)現(xiàn)方案:虛擬滾動(dòng)實(shí)現(xiàn)

可優(yōu)化的方案:

  • 采用多線程更新方法this.updateItemsSize里內(nèi)容
  • 使用css隱藏原生滾動(dòng)條,模擬出一個(gè)新滾動(dòng)條,人為控制新滾動(dòng)條的滾動(dòng)速度
 .infinite-list-container::-webkit-scrollbar {
  width:0;
 }

本項(xiàng)目代碼地址:github.com/zhensg123/rareRecord/tree/main/virtual-list

以上就是vue實(shí)現(xiàn)不定高虛擬列表的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue虛擬列表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 搭建Vue從Vue-cli到router路由護(hù)衛(wèi)的實(shí)現(xiàn)

    搭建Vue從Vue-cli到router路由護(hù)衛(wèi)的實(shí)現(xiàn)

    這篇文章主要介紹了搭建Vue從Vue-cli到router路由護(hù)衛(wèi)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Vue的路由配置過(guò)程(Vue2和Vue3的路由配置)

    Vue的路由配置過(guò)程(Vue2和Vue3的路由配置)

    這篇文章回顧了Vue2和Vue3中路由的配置步驟,包括安裝正確的路由版本、創(chuàng)建路由實(shí)例、配置routes以及在入口文件中注冊(cè)路由,Vue2中使用Vue.use(VueRouter),而Vue3中使用createRouter和createWebHashHistory
    2025-01-01
  • vue axios請(qǐng)求成功卻進(jìn)入catch的原因分析

    vue axios請(qǐng)求成功卻進(jìn)入catch的原因分析

    這篇文章主要介紹了vue axios請(qǐng)求成功卻進(jìn)入catch的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • vscode 開發(fā)Vue項(xiàng)目的方法步驟

    vscode 開發(fā)Vue項(xiàng)目的方法步驟

    這篇文章主要介紹了vscode 開發(fā)Vue項(xiàng)目的方法步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-11-11
  • vue3實(shí)現(xiàn)密碼輸入框效果的示例代碼

    vue3實(shí)現(xiàn)密碼輸入框效果的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用vue3實(shí)現(xiàn)6位的密碼輸入框效果,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-08-08
  • vue3如何定義變量及ref、reactive、toRefs特性說(shuō)明

    vue3如何定義變量及ref、reactive、toRefs特性說(shuō)明

    這篇文章主要介紹了vue3如何定義變量及ref、reactive、toRefs特性說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 解決VUE-npm ERR! C:\rj\node-v14.4.0-win-x64\nod問題

    解決VUE-npm ERR! C:\rj\node-v14.4.0-win-x64\nod問題

    這篇文章主要介紹了解決VUE-npm ERR! C:\rj\node-v14.4.0-win-x64\nod問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • vue之使用vuex進(jìn)行狀態(tài)管理詳解

    vue之使用vuex進(jìn)行狀態(tài)管理詳解

    這篇文章主要介紹了vue之使用vuex進(jìn)行狀態(tài)管理詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue2.x 的雙向綁定原理及實(shí)現(xiàn)

    Vue2.x 的雙向綁定原理及實(shí)現(xiàn)

    這篇文章主要介紹了Vue2.x 的雙向綁定原理,Vue 是利用的 Object.defineProperty() 方法進(jìn)行的數(shù)據(jù)劫持,利用 set、get 來(lái)檢測(cè)數(shù)據(jù)的讀寫。需要的朋友可以參考下面文章的具體內(nèi)容
    2021-09-09
  • 詳解vue中axios的封裝

    詳解vue中axios的封裝

    這篇文章大家分享了vue中axios的封裝的相關(guān)知識(shí)點(diǎn)以及實(shí)例代碼,有興趣的朋友參考學(xué)習(xí)下。
    2018-07-07

最新評(píng)論