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

Vue3實(shí)現(xiàn)一個(gè)定高的虛擬列表

 更新時(shí)間:2024年12月19日 16:24:46   作者:前端歐陽  
虛擬列表對于大部分一線開發(fā)同學(xué)來說是一點(diǎn)都不陌生的東西了,這篇文章主要來教大家如何在2分鐘內(nèi)實(shí)現(xiàn)一個(gè)定高的虛擬列表,感興趣的可以了解下

前言

虛擬列表對于大部分一線開發(fā)同學(xué)來說是一點(diǎn)都不陌生的東西了,有的同學(xué)是直接使用第三方組件。但是面試時(shí)如果你簡歷上面寫了虛擬列表,卻給面試官說是通過三方組件實(shí)現(xiàn)的,此時(shí)空氣可能都凝固了。所以這篇文章歐陽將會教你2分鐘內(nèi)實(shí)現(xiàn)一個(gè)定高的虛擬列表,至于不定高的虛擬列表下一篇文章來寫。

什么是虛擬列表

有的特殊場景我們不能分頁,只能渲染一個(gè)長列表。這個(gè)長列表中可能有幾萬條數(shù)據(jù),如果全部渲染到頁面上用戶的設(shè)備差點(diǎn)可能就會直接卡死了,這時(shí)我們就需要虛擬列表來解決問題。

一個(gè)常見的虛擬列表是下面這樣的,如下圖:

其中實(shí)線框的item表示在視口區(qū)域內(nèi)真實(shí)渲染DOM,虛線框的item表示并沒有渲染的DOM。

在定高的虛擬列表中,我們可以根據(jù)可視區(qū)域的高度每個(gè)item的高度計(jì)算得出在可視區(qū)域內(nèi)可以渲染多少個(gè)item。不在可視區(qū)域里面的item那么就不需要渲染了(不管有幾萬個(gè)還是幾十萬個(gè)item),這樣就能解決長列表性能很差的問題啦。

實(shí)現(xiàn)滾動條

按照上面的圖,很容易想到我們的dom結(jié)構(gòu)應(yīng)該是下面這樣的:

<template>
  <div class="container">
    <div class="list-wrapper">
      <!-- 只渲染可視區(qū)域列表數(shù)據(jù) -->
    </div>
  </div>
</template>

<style scoped>
  .container {
    height: 100%;
    overflow: auto;
    position: relative;
  }
</style>

給可視區(qū)域container設(shè)置高度100%,也可以是一個(gè)固定高度值。并且設(shè)置overflow: auto;讓內(nèi)容在可視區(qū)域中滾動。

此時(shí)我們遇見第一個(gè)問題,滾動條是怎么來的,可視區(qū)域是靠什么撐開的?

答案很簡單,我們知道每個(gè)item的高度itemSize,并且知道有多少條數(shù)據(jù)listData.length。那么itemSize * listData.length不就是真實(shí)的列表高度了嗎。所以我們可以在可視區(qū)域container中新建一個(gè)名為placeholder的空div,將他的高度設(shè)置為itemSize * listData.length,這樣可視區(qū)域就被撐開了,并且滾動條也有了。代碼如下:

<template>
  <div class="container">
    <div class="placeholder" :style="{ height: listHeight + 'px' }"></div>
    <div class="list-wrapper">
      <!-- 只渲染可視區(qū)域列表數(shù)據(jù) -->
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted, computed } from "vue";
  const { listData, itemSize } = defineProps({
    listData: {
      type: Array,
      default: () => [],
    },
    itemSize: {
      type: Number,
      default: 100,
    },
  });

  const listHeight = computed(() => listData.length * itemSize);
</script>

<style scoped>
  .container {
    height: 100%;
    overflow: auto;
    position: relative;
  }
  .placeholder {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
  }
</style>

placeholder采用絕對定位,為了不擋住可視區(qū)域內(nèi)渲染的列表,所以將其設(shè)置為z-index: -1

接下來就是計(jì)算容器里面到底渲染多少個(gè)item,很簡單,Math.ceil(可視區(qū)域的高度 / 每個(gè)item的高度)。

為什么使用Math.ceil向上取整呢?

只要有個(gè)item在可視區(qū)域漏了一點(diǎn)出來,我們也應(yīng)該將其渲染。

此時(shí)我們就能得到幾個(gè)變量:

  • start:可視區(qū)域內(nèi)渲染的第一個(gè)item的index的值,初始化為0。
  • renderCount:可視區(qū)域內(nèi)渲染的item數(shù)量。
  • end:可視區(qū)域內(nèi)渲染的最后一個(gè)item的index值,他的值等于start + renderCount。注意我們這里使用start + renderCount實(shí)際是多渲染了一個(gè)item,比如start = 0renderCount = 2,我們設(shè)置的是end = 2,實(shí)際是渲染了3個(gè)item。目的是為了預(yù)渲染下一個(gè),后面會講。

監(jiān)聽滾動事件

有了滾動條后就可以開始滾動了,我們監(jiān)聽container容器的scroll事件。

可視區(qū)域中的內(nèi)容應(yīng)該隨著滾動條的滾動而變化,也就是說在scroll事件中我們需要重新計(jì)算start的值。

function handleScroll(e) {
  const scrollTop = e.target.scrollTop;
  start.value = Math.floor(scrollTop / itemSize);
  offset.value = scrollTop - (scrollTop % itemSize);
}

如果當(dāng)前itemSize的值為100。

如果此時(shí)滾動的距離在0-100之間,比如下面這樣:

上面這張圖item1還沒完全滾出可視區(qū)域,有部分在可視區(qū)域內(nèi),部分在可視區(qū)域外。此時(shí)可視區(qū)域內(nèi)顯示的就是item1-item7的模塊了,這就是為什么前面我們計(jì)算end時(shí)要多渲染一個(gè)item,不然這里item7就沒法顯示了。

滾動距離在0-100之間時(shí),渲染的DOM沒有變化,我們完全是復(fù)用瀏覽器的滾動,并沒有進(jìn)行任何處理。

當(dāng)scrollTop的值為100時(shí),也就是剛剛把item1滾到可視區(qū)外面時(shí)。此時(shí)item1已經(jīng)不需要渲染了,因?yàn)橐呀?jīng)看不見他了。所以此時(shí)的start的值就應(yīng)該從0更新為1,同理如果scrollTop的值為110,start的值也一樣是1。所以得出start.value = Math.floor(scrollTop / itemSize);如下圖:

此時(shí)的start從item2開始渲染,但是由于前面我們復(fù)用了瀏覽器的滾動,所以實(shí)際渲染的DOM第一個(gè)已經(jīng)在可視區(qū)外面了。此時(shí)可視區(qū)看見的第一個(gè)是item3,很明顯是不對的,應(yīng)該看見的是第一個(gè)是item2。

此時(shí)應(yīng)該怎么辦呢?

很簡單,使用translate將列表向下偏移一個(gè)item的高度就行,也就是100px。列表偏移后就是下面這樣的了:

如果當(dāng)前scrollTop的值為200,那么偏移值就是200px。所以我們得出

offset.value = scrollTop - (scrollTop % itemSize);

為什么這里要減去scrollTop % itemSize呢?

因?yàn)樵跐L動時(shí)如果是在item的高度范圍內(nèi)滾動,我們是復(fù)用瀏覽器的滾動,此時(shí)無需進(jìn)行偏移,所以計(jì)算偏移值時(shí)需要減去scrollTop % itemSize

實(shí)際上從一個(gè)item滾動到另外一個(gè)item時(shí),比如從item0滾動到item1。此時(shí)會做兩件事情:將start的值從0更新為1和根據(jù)scrollTop計(jì)算得到列表的偏移值100,從而讓新的start對應(yīng)的item1重新回到可視范圍內(nèi)。

這個(gè)是運(yùn)行效果圖:

下面是完整的代碼:

<template>
  <div ref="container" class="container" @scroll="handleScroll($event)">
    <div class="placeholder" :style="{ height: listHeight + 'px' }"></div>
    <div class="list-wrapper" :style="{ transform: getTransform }">
      <div
        class="card-item"
        v-for="item in renderList"
        :key="item.id"
        :style="{
          height: itemSize + 'px',
          lineHeight: itemSize + 'px',
          backgroundColor: `rgba(0,0,0,${item.value / 100})`,
        }"
      >
        {{ item.value + 1 }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed } from "vue";
const { listData, itemSize } = defineProps({
  listData: {
    type: Array,
    default: () => [],
  },
  itemSize: {
    type: Number,
    default: 100,
  },
});

const container = ref(null);
const containerHeight = ref(0);
const renderCount = computed(() => Math.ceil(containerHeight.value / itemSize));
const start = ref(0);
const offset = ref(0);
const end = computed(() => start.value + renderCount.value);
const listHeight = computed(() => listData.length * itemSize);
const renderList = computed(() => listData.slice(start.value, end.value + 1));

const getTransform = computed(() => `translate3d(0,${offset.value}px,0)`);

onMounted(() => {
  containerHeight.value = container.value.clientHeight;
});

function handleScroll(e) {
  const scrollTop = e.target.scrollTop;
  start.value = Math.floor(scrollTop / itemSize);
  offset.value = scrollTop - (scrollTop % itemSize);
}
</script>

<style scoped>
.container {
  height: 100%;
  overflow: auto;
  position: relative;
}

.placeholder {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

.card-item {
  padding: 10px;
  color: #777;
  box-sizing: border-box;
  border-bottom: 1px solid #e1e1e1;
}
</style>

這個(gè)是父組件的代碼:

<template>
  <div style="height: 100vh; width: 100vw">
    <VirtualList :listData="data" :itemSize="100" />
  </div>
</template>

<script setup>
import VirtualList from "./common.vue";
import { ref } from "vue";

const data = ref([]);
for (let i = 0; i < 1000; i++) {
  data.value.push({ id: i, value: i });
}
</script>

<style>
html {
  height: 100%;
}
body {
  height: 100%;
  margin: 0;
}
#app {
  height: 100%;
}
</style>

總結(jié)

這篇文章我們講了如何實(shí)現(xiàn)一個(gè)定高的虛擬列表,首先根據(jù)可視區(qū)域的高度和item的高度計(jì)算出視口內(nèi)可以渲染出來的item數(shù)量renderCount。然后根據(jù)滾動的距離去計(jì)算start的位置,計(jì)算end的位置時(shí)使用start + renderCount預(yù)渲染一個(gè)item。在每個(gè)item范圍內(nèi)滾動時(shí)直接復(fù)用瀏覽器的滾動,此時(shí)無需進(jìn)行任何處理。當(dāng)從一個(gè)item滾動到另外一個(gè)item時(shí),此時(shí)會做兩件事情:更新start的值和根據(jù)scrollTop計(jì)算列表的偏移值讓新的start對應(yīng)的item重新回到可視范圍內(nèi)。

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

相關(guān)文章

  • vue-cli配置flexible過程詳解

    vue-cli配置flexible過程詳解

    這篇文章主要介紹了vue-cli配置flexible過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • vue上傳圖片組件編寫代碼

    vue上傳圖片組件編寫代碼

    這篇文章主要為大家詳細(xì)介紹了vue上傳圖片組件的編寫代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • vue發(fā)送websocket請求和http post請求的實(shí)例代碼

    vue發(fā)送websocket請求和http post請求的實(shí)例代碼

    這篇文章主要介紹了vue發(fā)送websocket請求和http post請求的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-07-07
  • 如何獲取this.$store.dispatch的返回值

    如何獲取this.$store.dispatch的返回值

    這篇文章主要介紹了如何獲取this.$store.dispatch的返回值問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • vue滾動固定頂部及修改樣式的實(shí)例代碼

    vue滾動固定頂部及修改樣式的實(shí)例代碼

    這篇文章主要介紹了vue滾動固定頂部及修改樣式,本文給大家提到了滾動固定位置有多種方法,感興趣的朋友跟隨小編一起看看吧
    2019-05-05
  • 詳解如何制作并發(fā)布一個(gè)vue的組件的npm包

    詳解如何制作并發(fā)布一個(gè)vue的組件的npm包

    這篇文章主要介紹了詳解如何制作并發(fā)布一個(gè)vue的組件的npm包,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • vue復(fù)制內(nèi)容到剪切板代碼實(shí)現(xiàn)

    vue復(fù)制內(nèi)容到剪切板代碼實(shí)現(xiàn)

    這篇文章主要給大家介紹了關(guān)于vue復(fù)制內(nèi)容到剪切板代碼實(shí)現(xiàn)的相關(guān)資料,在Web應(yīng)用程序中剪貼板(Clipboard)操作是非常常見的操作之一,需要的朋友可以參考下
    2023-08-08
  • vue vantUI實(shí)現(xiàn)文件(圖片、文檔、視頻、音頻)上傳(多文件)

    vue vantUI實(shí)現(xiàn)文件(圖片、文檔、視頻、音頻)上傳(多文件)

    這篇文章主要介紹了vue vantUI實(shí)現(xiàn)文件(圖片、文檔、視頻、音頻)上傳(多文件),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • 前端必知必會之Vue?$emit()方法詳解

    前端必知必會之Vue?$emit()方法詳解

    這篇文章主要介紹了前端必知必會之Vue?$emit()方法的相關(guān)資料,Vue.js中的$emit()方法用于在子組件中創(chuàng)建自定義事件,并在父組件中捕獲這些事件,這在需要從子組件向父組件傳遞信息的大型項(xiàng)目中非常有用,需要的朋友可以參考下
    2025-02-02
  • Vue.use()的作用及原理解析

    Vue.use()的作用及原理解析

    這篇文章主要介紹了Vue.use()的作用及原理解析,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09

最新評論