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

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

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

前言

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

什么是虛擬列表

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

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

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

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

實現(xiàn)滾動條

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

<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設置高度100%,也可以是一個固定高度值。并且設置overflow: auto;讓內(nèi)容在可視區(qū)域中滾動。

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

答案很簡單,我們知道每個item的高度itemSize,并且知道有多少條數(shù)據(jù)listData.length。那么itemSize * listData.length不就是真實的列表高度了嗎。所以我們可以在可視區(qū)域container中新建一個名為placeholder的空div,將他的高度設置為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)渲染的列表,所以將其設置為z-index: -1

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

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

只要有個item在可視區(qū)域漏了一點出來,我們也應該將其渲染。

此時我們就能得到幾個變量:

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

監(jiān)聽滾動事件

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

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

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

如果當前itemSize的值為100。

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

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

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

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

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

此時應該怎么辦呢?

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

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

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

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

因為在滾動時如果是在item的高度范圍內(nèi)滾動,我們是復用瀏覽器的滾動,此時無需進行偏移,所以計算偏移值時需要減去scrollTop % itemSize。

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

這個是運行效果圖:

下面是完整的代碼:

<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>

這個是父組件的代碼:

<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é)

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

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

相關(guān)文章

  • vue實現(xiàn)關(guān)鍵字高亮效果的示例代碼

    vue實現(xiàn)關(guān)鍵字高亮效果的示例代碼

    這篇文章主要為大家詳細介紹了如何使用vue實現(xiàn)關(guān)鍵字高亮效果,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-11-11
  • 關(guān)于Vue的異步組件

    關(guān)于Vue的異步組件

    在大型項目中,我們可能需要拆分應用為更小的塊,并僅在需要時再從服務器加載相關(guān)組件。Vue 提供了defineAsyncComponent 方法來實現(xiàn)此功能,需要的朋友可以參考下
    2023-04-04
  • Vue使用xlsx插件導出excel文件的詳細指南

    Vue使用xlsx插件導出excel文件的詳細指南

    第三方庫xlsx提供了強大的功能來處理Excel文件,它可以簡化導出Excel文件這個過程,下面小編就來為大家詳細講講Vue如何通過xlsx導出excel,需要的小伙伴可以了解下
    2025-04-04
  • Vue3中watchEffect的用途淺析

    Vue3中watchEffect的用途淺析

    這篇文章主要給大家介紹了關(guān)于Vue3中watchEffect用途的相關(guān)資料, watch和watchEffect都是監(jiān)聽器,但在寫法和使用上有所區(qū)別,本文進行了詳細的介紹,需要的朋友可以參考下
    2021-07-07
  • vue3使用reactive包裹數(shù)組正確賦值問題

    vue3使用reactive包裹數(shù)組正確賦值問題

    這篇文章主要介紹了vue3使用reactive包裹數(shù)組正確賦值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 如何在Vue中使localStorage具有響應式(思想實驗)

    如何在Vue中使localStorage具有響應式(思想實驗)

    這篇文章主要介紹了如何在Vue中使localStorage具有響應式,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • vue?elementUI之this.$confirm的使用方式

    vue?elementUI之this.$confirm的使用方式

    這篇文章主要介紹了vue?elementUI之this.$confirm的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • 解決Vue2跨域報錯AxiosError?net::ERR_FAILED、?Network?Error、ERR_NETWORK

    解決Vue2跨域報錯AxiosError?net::ERR_FAILED、?Network?Error、ERR_N

    這篇文章主要給大家介紹了關(guān)于解決Vue2跨域報錯AxiosError?net::ERR_FAILED、?Network?Error、ERR_NETWORK的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2022-11-11
  • Nuxt.js nuxt-link與router-link的區(qū)別說明

    Nuxt.js nuxt-link與router-link的區(qū)別說明

    這篇文章主要介紹了Nuxt.js nuxt-link與router-link的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • vue中關(guān)于$emit和$on的使用及說明

    vue中關(guān)于$emit和$on的使用及說明

    這篇文章主要介紹了vue中關(guān)于$emit和$on的使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10

最新評論

衡水市| 乌海市| 布拖县| 庆城县| 南溪县| 正定县| 青州市| 独山县| 兰坪| 尼玛县| 宣武区| 阜宁县| 武汉市| 盐亭县| 麻阳| 渝中区| 鹰潭市| 聊城市| 乌拉特前旗| 宣威市| 澄江县| 曲麻莱县| 平定县| 洪洞县| 凉城县| 页游| 吐鲁番市| 塔城市| 丹棱县| 湘阴县| 马龙县| 鄱阳县| 榆树市| 乌鲁木齐市| 深水埗区| 九龙县| 星子县| 尼勒克县| 谷城县| 涿州市| 子长县|