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

vue實(shí)現(xiàn)虛擬列表組件解決長列表性能問題

 更新時(shí)間:2022年07月19日 11:05:54   作者:abner105  
這篇文章主要介紹了在vue中實(shí)現(xiàn)虛擬列表組件,解決長列表性能問題,本文給大家分享實(shí)現(xiàn)思路及實(shí)例代碼,需要的朋友可以參考下

最近項(xiàng)目中需要用到列表的展示,且不分頁。當(dāng)數(shù)據(jù)加載太多時(shí)會(huì)造成性能問題。因此采用虛擬列表來優(yōu)化

一、虛擬列表

真實(shí)列表:每條數(shù)據(jù)都展示到html上,數(shù)據(jù)越多,DOM元素也就越多,性能也就越差。

虛擬列表:只展示部分?jǐn)?shù)據(jù)(可見區(qū)域展示數(shù)據(jù)),當(dāng)屏幕滾動(dòng)時(shí)替換展示的數(shù)據(jù),DOM元素的數(shù)量是固定的,相比較真實(shí)列表更高效。

二、實(shí)現(xiàn)思路

難點(diǎn)與思考:

1. 如何計(jì)算需要渲染的數(shù)據(jù)

  • 數(shù)據(jù)可分為總數(shù)據(jù),與需要渲染的數(shù)據(jù),需要渲染的數(shù)據(jù)包括了可見區(qū)域與緩沖區(qū)域的數(shù)據(jù)
  • 通過單條數(shù)據(jù)占位的高度與可見區(qū)域的高度,算出可見區(qū)域的列表?xiàng)l數(shù),再往上和往下擴(kuò)展幾條緩沖區(qū)域的數(shù)據(jù)(本次代碼是以3倍可見區(qū)域的條數(shù)作為需要渲染的數(shù)據(jù)條數(shù))

2. 何時(shí)替換數(shù)據(jù)

  • 監(jiān)聽滾動(dòng)事件,渲染元素的第一條數(shù)據(jù)滾動(dòng)出緩沖區(qū)域后(也就是可見區(qū)域第一個(gè)元素的index大于緩沖區(qū)域的條數(shù)時(shí)),就開始替換數(shù)據(jù)了,每次往上滑動(dòng)一個(gè)元素,就替換一次數(shù)據(jù)。

3. 為何需要空白占位,如何計(jì)算空白占位的高度

  • 由于列表在滾動(dòng)過程中會(huì)替換數(shù)據(jù),如果沒有空白占位的話,會(huì)導(dǎo)致第一個(gè)元素消失后,第二個(gè)元素立馬替換了第一個(gè)元素的位置,會(huì)導(dǎo)致錯(cuò)位。如下圖所示:

  • 因此滾動(dòng)時(shí),需要在元素消失后,補(bǔ)一個(gè)相同高度的空白占位
  • 上方的空白占位 = 消失的元素個(gè)數(shù)(也就是第一個(gè)渲染元素的index) * 單個(gè)元素的高度
  • 下方的空白占位 = 剩下需要渲染的元素個(gè)數(shù)(也就是最后一個(gè)元素的index與總數(shù)據(jù)條數(shù)的差值)* 單個(gè)元素的高度

其他注意事項(xiàng):

  • 在使用v-for遍歷渲染數(shù)據(jù)時(shí),key的值使用index,不用itemid,可以避免該dom元素被重新渲染,只替換數(shù)據(jù)。
  • 下拉加載更多時(shí),不要將整個(gè)數(shù)據(jù)替換了,而是追加到數(shù)據(jù)的后面,避免之前展示的數(shù)據(jù)被替換了。
  • 空白占位可以使用padding來占位,也可以使用DOM元素占位,使用DOM元素占位監(jiān)聽滾動(dòng)事件時(shí),應(yīng)使用touchmovemousemove監(jiān)聽,避免dom元素高度變化后,又觸發(fā)了scroll滾動(dòng)事件。
  • 監(jiān)聽滾動(dòng)事件應(yīng)該采用節(jié)流的方式,避免程序頻繁執(zhí)行。
  • 監(jiān)聽滾動(dòng)時(shí)加上passive修飾符,可以提前告知瀏覽器需要執(zhí)行preventDefault,使?jié)L動(dòng)更流暢,具體功能可以參考vue官網(wǎng)。
  • 外層包裹的元素需要有固定高度,并且overflowauto,才能監(jiān)聽scroll滾動(dòng)事件。

三、實(shí)現(xiàn)

最終實(shí)現(xiàn)效果

實(shí)現(xiàn)代碼

<template>
  <div id="app">
    <!-- 監(jiān)聽滾動(dòng)事件使用passive修飾符 -->
    <div class="container" ref="container" @scroll.passive="handleScroll">
      <div :style="paddingStyle">
        <!-- key使用index,可避免多次渲染該dom -->
        <div class="box" v-for="(item, index) in showList" :key="index">
          <h2>{{ item.title }} - {{ item.id }}</h2>
          <h3>{{ item.from }}</h3>
        </div>
        <div>到低了~~~</div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
  name: "App",
  data() {
    return {
      allList: [], // 所有數(shù)據(jù) 
      isRequest: false,// 是否正在請求數(shù)據(jù)
      oneHeight: 150,  // 單條數(shù)據(jù)的高度
      showNum: 0,    // 可見區(qū)域最多能展示多少條數(shù)據(jù)
      startIndex: 0,   // 渲染元素的第一個(gè)索引
      canScroll: true,  // 可以監(jiān)聽滾動(dòng),用于節(jié)流
      scrollTop: 0,// 當(dāng)前滾動(dòng)高度,再次返回頁面時(shí)能定位到之前的滾動(dòng)高度
      lower: 150,// 距離底部多遠(yuǎn)時(shí)觸發(fā)觸底事件
    };
  },
  created() {
    this.getData();// 請求數(shù)據(jù)
  },
  activited() {
    this.$nextTick(()=>{
      // 定位到之前的高度
      this.$refs.container.scrollTop = this.scrollTop
    })
  },
  mounted() {
    this.canShowNum();  // 獲取可見區(qū)域能展示多少條數(shù)據(jù)
    window.onresize = this.canShowNum;  // 監(jiān)聽窗口變化,需要重新計(jì)算一屏能展示多少條數(shù)據(jù)
    window.onorientationchange = this.canShowNum;  // 監(jiān)聽窗口翻轉(zhuǎn)
  },
  computed: {
    // 渲染元素最后的index
    endIndex() {
      let end = this.startIndex + this.showNum * 3; // 3倍是需要預(yù)留緩沖區(qū)域
      let len = this.allList.length
      return end >= len ? len : end;  // 結(jié)束元素大于所有元素的長度時(shí),就取元素長度
    },
    // 需要渲染的數(shù)據(jù)
    showList() {
      return this.allList.slice(this.startIndex, this.endIndex)
    },
    // 空白占位的高度
    paddingStyle() {
      return {
        paddingTop: this.startIndex * this.oneHeight + 'px',
        paddingBottom: (this.allList.length - this.endIndex) * this.oneHeight + 'px'
      }
    }
  },
  methods: {
    // 請求數(shù)據(jù)
    getData() {
      this.isRequest = true  // 正在請求中
      axios.get("http://localhost:4000/data?num=10").then((res) => {
        // 將結(jié)果追加到allList
        this.allList = [...this.allList, ...res.data.list];
        this.isRequest = false
      });
    },
    // 計(jì)算可見區(qū)域能展示的條數(shù)
    canShowNum() {
      // ~~ 按位兩次取反,得到整數(shù)
      this.showNum = ~~(this.$refs.container.offsetHeight / this.oneHeight) + 2;
    },
    // 監(jiān)聽滾動(dòng)
    handleScroll(e) {
      if (this.canScroll) {
        this.canScroll = false
        // 處理數(shù)據(jù)
        this.handleData(e)
        // 節(jié)流
        let timer = setTimeout(() => {
          this.canScroll = true
          clearTimeout(timer)
          timer = null
        }, 30)
      }
    },
    handleData(e) {
      // 記錄當(dāng)前元素滾動(dòng)的高度
      this.scrollTop = e.target.scrollTop
      // 可見區(qū)域第一個(gè)元素的index
      const curIndex = ~~(e.target.scrollTop / this.oneHeight)
      // 渲染區(qū)域第一個(gè)元素的index,這里緩沖區(qū)域的列表?xiàng)l數(shù)使用的是this.showNum
      this.startIndex = curIndex < this.showNum ? 0 : curIndex - this.showNum
      // 滾動(dòng)距離底部,還有this.lower距離時(shí),觸發(fā)觸底事件,正在請求中不發(fā)送數(shù)據(jù)
      if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - this.lower && !this.isRequest) {
        this.getData()
      }
    }
  },
};
</script>

<style>
#app {
  height: 100vh;
}
.container {
  height: 100%;
  /* 設(shè)置overflow為auto才能監(jiān)聽scroll滾動(dòng)事件 */
  overflow: auto;
}
.box {
  width: 96vw;
  height: 150px;
  background: #eee;
  border: 2px navajowhite solid;
  box-sizing: border-box;
}
</style>

模擬數(shù)據(jù)的后端代碼

  • 這是本次用于模擬后端數(shù)據(jù)的代碼,采用mockexpress。
const Mock = require('mockjs')
const express = require('express')
const app = express()

let sum = 1 // mock的ID

// 根據(jù)入?yún)⑸蒼um條模擬數(shù)據(jù)
function generatorList(num) {
  return Mock.mock({
    [`list|${num}`]: [
      {
        'id|+1': sum,
        title: "@ctitle(15,25)",
        from: "@ctitle(3,10)",
      }
    ]
  })
}
// 允許跨域
app.all('*', function (req, res, next) {
  res.setHeader("Access-Control-Allow-Origin", '*');
  res.setHeader("Access-Control-Allow-Headers", '*');
  res.setHeader("Access-Control-Allow-Method", '*');
  next()
})
app.get('/data', function (req, res) {
  const { num } = req.query
  const data = generatorList(num)
  sum += parseInt(num)
  return res.send(data)
})
const server = app.listen(4000, function () {
  console.log('4000端口正在監(jiān)聽~~')
})

四、封裝為組件

也可以封裝為插件,此處為了方便就封裝為組件

props:

  • allList : 所有數(shù)據(jù)
  • oneHeight : 單條元素的高度
  • lower : 距離底部多遠(yuǎn)時(shí)觸發(fā)觸底事件,默認(rèn)50

event:

  • @scrollLower : 觸底時(shí)觸發(fā)

虛擬列表組件代碼

<template>
    <!-- 監(jiān)聽滾動(dòng)事件使用passive修飾符 -->
    <div class="container" ref="container" @scroll.passive="handleScroll">
      <div :style="paddingStyle">
        <!-- key使用index,可避免多次渲染該dom -->
        <div v-for="(item, index) in showList" :key="index">
          <!-- 使用作用域插槽,將遍歷后的數(shù)據(jù)item和index傳遞出去 -->
          <slot :item="item" :$index="index"></slot>
        </div>
        <div>到低了~~~</div>
      </div>
    </div>
</template>

<script>
export default {
  name: "App",
  props:{
    // 所有數(shù)據(jù)
    allList:{
      type:Array,
      default(){
        return []
      }
    },
    // 單條數(shù)據(jù)的高度
    oneHeight:{
      type:Number,
      default:0
    },
    // 距離底部多遠(yuǎn)時(shí)觸發(fā)觸底事件
    lower:{
      type:Number,
      default:50
    }
  },
  data() {
    return {
      showNum: 0,    // 可見區(qū)域最多能展示多少條數(shù)據(jù)
      startIndex: 0,   // 渲染元素的第一個(gè)索引
      canScroll: true,  // 可以監(jiān)聽滾動(dòng),用于節(jié)流
      scrollTop: 0,// 當(dāng)前滾動(dòng)高度,再次返回頁面時(shí)能定位到之前的滾動(dòng)高度
    };
  },
  activited() {
    this.$nextTick(()=>{
      // 定位到之前的高度
      this.$refs.container.scrollTop = this.scrollTop
    })
  },
  mounted() {
    this.canShowNum();  // 獲取可見區(qū)域能展示多少條數(shù)據(jù)
    window.onresize = this.canShowNum;  // 監(jiān)聽窗口變化,需要重新計(jì)算一屏能展示多少條數(shù)據(jù)
    window.onorientationchange = this.canShowNum;  // 監(jiān)聽窗口翻轉(zhuǎn)
  },
  computed: {
    // 渲染元素最后的index
    endIndex() {
      let end = this.startIndex + this.showNum * 3; // 3倍是需要預(yù)留緩沖區(qū)域
      let len = this.allList.length
      return end >= len ? len : end;  // 結(jié)束元素大于所有元素的長度時(shí),就取元素長度
    },
    // 需要渲染的數(shù)據(jù)
    showList() {
      return this.allList.slice(this.startIndex, this.endIndex)
    },
    // 空白占位的高度
    paddingStyle() {
      return {
        paddingTop: this.startIndex * this.oneHeight + 'px',
        paddingBottom: (this.allList.length - this.endIndex) * this.oneHeight + 'px'
      }
    }
  },
  methods: {
    // 計(jì)算可見區(qū)域能展示的條數(shù)
    canShowNum() {
      // ~~ 按位兩次取反,得到整數(shù)
      this.showNum = ~~(this.$refs.container.offsetHeight / this.oneHeight) + 2;
    },
    // 監(jiān)聽滾動(dòng)
    handleScroll(e) {
      if (this.canScroll) {
        this.canScroll = false
        // 處理數(shù)據(jù)
        this.handleData(e)
        // 節(jié)流
        let timer = setTimeout(() => {
          this.canScroll = true
          clearTimeout(timer)
          timer = null
        }, 30)
      }
    },
    handleData(e) {
      // 記錄當(dāng)前元素滾動(dòng)的高度
      this.scrollTop = e.target.scrollTop
      // 可見區(qū)域第一個(gè)元素的index
      const curIndex = ~~(e.target.scrollTop / this.oneHeight)
      // 渲染區(qū)域第一個(gè)元素的index,這里緩沖區(qū)域的列表?xiàng)l數(shù)使用的是this.showNum
      this.startIndex = curIndex < this.showNum ? 0 : curIndex - this.showNum
      // 滾動(dòng)距離底部,還有this.lower距離時(shí),觸發(fā)觸底事件,正在請求中不發(fā)送數(shù)據(jù)
      if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - this.lower) {
        this.$emit('scrollLower') // 傳遞觸底事件
      }
    }
  },
};
</script>

<style>
.container {
  height: 100%;
  /* 設(shè)置overflow為auto才能監(jiān)聽scroll滾動(dòng)事件 */
  overflow: auto;
}
</style>

使用代碼

<template>
  <div id="app">
    <VScroll :allList="allList" :oneHeight="150" :lower="150" @scrollLower="scrollLower">
      <!-- 作用域插槽,使用slot-scope取出在組件中遍歷的數(shù)據(jù) -->
      <template slot-scope="{item}">
        <div class="box">
          <h2>{{ item.title }} - {{ item.id }}</h2>
          <h3>{{ item.from }}</h3>
        </div>
      </template>
    </VScroll>
  </div>
</template>

<script>
import axios from "axios";
import VScroll from "./components/VScroll.vue";
export default {
  name: "App",
  data() {
    return {
      allList: [], // 所有數(shù)據(jù)
      isRequest: false  // 是否正在請求數(shù)據(jù)
    };
  },
  created() {
    this.getData(); // 請求數(shù)據(jù)
  },
  methods: {
    // 請求數(shù)據(jù)
    getData() {
      this.isRequest = true; // 正在請求中
      axios.get("http://localhost:4000/data?num=10").then((res) => {
        // 將結(jié)果追加到allList
        this.allList = [...this.allList, ...res.data.list];
        this.isRequest = false;
      });
    },
    // 滾動(dòng)到底部
    scrollLower() {
      if (!this.isRequest) this.getData()
    }
  },
  components: { VScroll }
};
</script>

<style>
#app {
  height: 100vh;
}
.box {
  width: 96vw;
  height: 150px;
  background: #eee;
  border: 2px navajowhite solid;
  box-sizing: border-box;
}
</style>

到此這篇關(guān)于在vue中實(shí)現(xiàn)虛擬列表組件,解決長列表性能問題的文章就介紹到這了,更多相關(guān)vue虛擬列表組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決vue2+vue-router動(dòng)態(tài)路由添加及路由刷新后消失問題

    解決vue2+vue-router動(dòng)態(tài)路由添加及路由刷新后消失問題

    這篇文章主要介紹了解決vue2+vue-router動(dòng)態(tài)路由添加及路由刷新后消失問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • vue實(shí)現(xiàn)消息的無縫滾動(dòng)效果的示例代碼

    vue實(shí)現(xiàn)消息的無縫滾動(dòng)效果的示例代碼

    本篇文章主要介紹了vue實(shí)現(xiàn)消息的無縫滾動(dòng)效果的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • vuejs項(xiàng)目打包之后的首屏加載優(yōu)化及打包之后出現(xiàn)的問題

    vuejs項(xiàng)目打包之后的首屏加載優(yōu)化及打包之后出現(xiàn)的問題

    這篇文章主要介紹了vuejs項(xiàng)目打包之后的首屏加載優(yōu)化及打包之后可能出現(xiàn)的問題,需要的朋友可以參考下
    2018-04-04
  • Vue 組件化基本使用詳情

    Vue 組件化基本使用詳情

    這篇文章主要給大家分享的是Vue 組件化基本使用,所謂組件化,就是把頁面拆分成多個(gè)組件,每個(gè)組件依賴的 CSS、JS、模板、圖片等資源放在一起開發(fā)和維護(hù)。 因?yàn)榻M件是資源獨(dú)立的,所以組件在系統(tǒng)內(nèi)部可復(fù)用,組件和組件之間可以嵌套,下面來看文章學(xué)習(xí)內(nèi)容吧
    2021-10-10
  • Vue3+Three.js實(shí)現(xiàn)為模型添加點(diǎn)擊事件

    Vue3+Three.js實(shí)現(xiàn)為模型添加點(diǎn)擊事件

    本文主要介紹了如何在Vue3和Three.js環(huán)境中為模型添加點(diǎn)擊事件監(jiān)聽,具體方法是利用Three.js的Vector2和Raycaster,首先,創(chuàng)建一個(gè)Vector2對象來獲取鼠標(biāo)位置;然后,創(chuàng)建一個(gè)Raycaster對象來投射光線
    2024-10-10
  • electron中使用本地?cái)?shù)據(jù)庫的方法詳解

    electron中使用本地?cái)?shù)據(jù)庫的方法詳解

    眾所周知,electron是可以開發(fā)桌面端的框架,那我們有一些數(shù)據(jù)不想讓別人看到,只能在自己的電腦上展示時(shí)怎么辦呢,這個(gè)時(shí)候就可以用到本地?cái)?shù)據(jù)庫,本文將以sqlite3為例介紹一下electron如何使用本地?cái)?shù)據(jù)庫
    2023-10-10
  • 少女風(fēng)vue組件庫的制作全過程

    少女風(fēng)vue組件庫的制作全過程

    這篇文章主要給大家介紹了關(guān)于少女風(fēng)vue組件庫的制作全過程,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • vue路由--網(wǎng)站導(dǎo)航功能詳解

    vue路由--網(wǎng)站導(dǎo)航功能詳解

    這篇文章主要介紹了vue路由--網(wǎng)站導(dǎo)航功能詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • vue2模擬vue-element-admin手寫角色權(quán)限的實(shí)現(xiàn)

    vue2模擬vue-element-admin手寫角色權(quán)限的實(shí)現(xiàn)

    本文主要介紹了vue2模擬vue-element-admin手寫角色權(quán)限的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 詳解vue-meta如何讓你更優(yōu)雅的管理頭部標(biāo)簽

    詳解vue-meta如何讓你更優(yōu)雅的管理頭部標(biāo)簽

    這篇文章主要介紹了詳解vue-meta如何讓你更優(yōu)雅的管理頭部標(biāo)簽,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01

最新評論

扬中市| 恭城| 德州市| 石泉县| 兴隆县| 奉新县| 班玛县| 辛集市| 南溪县| 营山县| 秭归县| 南昌县| 枣强县| 旌德县| 达拉特旗| 峡江县| 满城县| 嵊州市| 安远县| 新巴尔虎右旗| 洪雅县| 洪洞县| 广汉市| 平武县| 中阳县| 南丹县| 阳谷县| 兴海县| 邵阳县| 镇雄县| 蛟河市| 扶沟县| 信阳市| 万源市| 桐城市| 颍上县| 平阴县| 高密市| 寿光市| 周口市| 义乌市|