一个很基础的虚拟滚动
由于项目中要写一个虚拟滚动,但不能下插件,所以就手写实现了一版,很基础。
主要难点是要动态计算第几屏
主要思想是算出真实高度是滚动高度的几倍,然后*100,在用视口已滚动的最大页数*这个倍数在除以这个倍数
不理解这个逻辑
如果不考虑精度
也可以通过滚动的真实距离除以视口大小,来计算第几屏
<template> <div class="listBox"> <div class="topBox"> <div class="left"> <DocumentSvg /> <span>待办事项</span> </div> <div class="right">共{{ currentPage+1 }}/{{ totalPages+1 }}页</div> </div> <ul ref="middleList" class="middleList scrollBox" @scroll="handleScroll"> <li v-for="(item, index) in agencyList.slice(0, sliceIndex)" :key="item.msgSendId+item.msgReceiveId"> <span class="order">{{ index + 1 }}</span> <span class="middleList-content">{{ item.msgTitle }}</span> <div class="btn">处理</div> </li> </ul> <!-- <div v-if="agencyList.length>8&&sliceIndex===8" class="more" @click="sliceIndex=9999">查看更多</div> <div v-else-if="sliceIndex>8&¤tPage>1" class="more" @click="backTop">收起</div> <div v-else class="more" style="opacity:0" >占位元素</div> --> </div> </template> <script> import DocumentSvg from './documentSvg.vue' import { queryNoReadAgencyList } from '../../index.api' export default { components: { DocumentSvg }, data() { return { agencyList: [], count: 0, sliceIndex: 8, currentPage: 0, hasMore: false, prevScrollPosition: 0, prePage: 0 } }, computed: { totalPages() { return Math.ceil(this.count / 8) } }, mounted() { const params = { msgType: 'agency', receiveUser: this.$store.getters.userInfo.userId, appId: this.$store.getters.userInfo.appId, tenantId: this.$store.getters.userInfo.tenantId, searchDate: 'sms', pageSize: 9999 } queryNoReadAgencyList(params).then((res) => { this.agencyList = res.datas this.count = res.count console.log(res, 'r0-') }) }, methods: { handleScroll() { // 获取滚动容器 const scrollBox = this.$refs.middleList const scrollTop = scrollBox.scrollTop // clientHeight:可见高度 // scrollBox.scrollHeight:整个容器的高度 // scrollHeight:滚动高度 const scrollHeight = scrollBox.scrollHeight - scrollBox.clientHeight const scrollPercentage = (scrollTop / scrollHeight) * 100 // 如果加载完成 if (this.hasMore) { // 计算当前所在的页数 this.currentPage = Math.ceil((this.totalPages * scrollPercentage) / 100) return } // 如果回滚 if (scrollTop < this.prevScrollPosition) { this.currentPage = Math.ceil((this.prePage * scrollPercentage) / 100) this.prevScrollPosition = scrollTop return } this.prevScrollPosition = scrollTop const scrollBottom = scrollBox.scrollHeight - (scrollBox.scrollTop + scrollBox.clientHeight) // 如果并非加载完成也并非回滚,但距离底部大于50 if (scrollBottom > 50) { this.currentPage = Math.ceil((this.prePage * scrollPercentage) / 100) return } else { this.sliceIndex = this.sliceIndex + 8 if (this.currentPage < this.totalPages) { this.currentPage++ this.prePage = this.currentPage } else { this.hasMore = true } } } } } </script> <style lang='scss' scoped> .listBox { width: 38vw; margin: 0 auto; .topBox { width: 100%; display: flex; justify-content: space-between; align-items: center; color: #fff; .left { display: flex; align-items: center; } .right { margin-right: 20px; } } .middleList { height: 69vh; // display: flex; // flex-direction: column; // justify-content: flex-start; overflow-y: scroll; li { cursor: pointer; width: 100%; height: 7vh; border-radius: 10px; color: #fff; list-style-type: none; display: flex; justify-content: space-between; align-items: center; white-space: nowrap; /* 防止文本换行 */ overflow: hidden; /* 控制超出部分的隐藏 */ text-overflow: ellipsis; /* 显示省略号 */ font-size: 16px; margin: 1.5vh 0; .order { text-align: center; flex: 1; } .middleList-content { flex: 8; } .btn { user-select: none; text-align: center; margin-right: 10px; width: 5.6vw; height: 4.5vh; line-height: 4.5vh; border-radius: 20px; font-size: 16px; // transition: all 1s; } } } .more { user-select: none; opacity: 0.57; color: #ffffff; font-size: 16px; text-align: center; cursor: pointer; } } </style> <style lang="scss" scoped> @import "./../loginAfterCss/themeModule.scss"; </style>