vue 实现数据滚动显示_通过 Vue 实现无限滚动列表(二)—— 代码逻辑与 Demo

本文详细介绍了如何使用Vue实现数据滚动显示,特别是无限滚动列表的实现。通过一个名为ScrollManager的类,管理滚动逻辑,包括初始化、滚动更新、细胞高度计算等方法。文章提供了具体的代码示例,解释了如何实例化和使用ScrollManager,以及在滚动事件中处理数据更新和滚动条定位。最后,提到了在高度不定和数据变化时可能出现的问题以及解决方案。
摘要由CSDN通过智能技术生成

上一篇介绍了无限列表实现的简单思路,下面说一下代码的实现逻辑。

主要逻辑通过一个 ScrollManager 类来完成,在这个类中,会根据滚动条高度计算当前需要渲染的 items 以及上下需要支撑起来的高度。该类中主要由如下这些方法:下面代码注释中,cell 与 item 为同一个东西,都是列表中的一项,因为是先完成的代码后添加的注释,所以请不要在意这些细节:)

class ScrollManager {

// 构造器方法 constructor ( {

list, // 待渲染的列表数据 Array scrollViewHeight, // 滚动视图的高度,即滚动区域可见部分的高度 cellHeight, // 每个 item 的高度,如果设置了该值则认为是固定高度列表 cellCacheNumber, // 上下两方缓冲的item数量 firstRenderNumber // 动态高度时单屏初次渲染的列表数量 } ) { ... }

// 初始化滚动列表 // 计算首屏需要渲染的items和缓冲items initScroll () { ... }

// 滚动时更新数据 // 根据滚动条高度计算已经划出屏幕并且不再需要渲染的items // 更新需要渲染的items和缓冲items // 并更新列表上方和下方需要支撑起的高度 updateScroll (scrollTop) { ... }

// 内部调用的调整items相关数据的方法 // 包括已经不需要渲染的items和需要渲染的items _adjustCells () { ... }

// 动态高度时根据已缓存的cell高度计算平均高度,方法接受当前渲染的cells的高度数组 // 对已经渲染过的cell高度进行缓存,保证上方的支撑高度计算准确 // 对未渲染过的cell高度进行预估,保证下方的支撑高度尽量靠近实际高度 // 调整整个滑动列表的总高度 updateCellHeight (cellsHeightInfo) { ... }

// 获取待渲染的items及相关数据 getRenderInfo () { ... }

}

当然,上面这个类完全可以脱离 Vue 来使用,那这个类是怎么使用的呢?

// 1. 实例化 ScrollManager 类const manager = new ScrollManager({ ... })

// 2. 实例化完成后,通过 getRenderInfo 获取首次渲染的数据let renderList = manager.getRenderInfo()

// 3. 需要注意的是,当列表重新渲染后可能会引发滚动条位置的改变,所以需要在页面完成渲染后重新将滚动条定位到准确的位置// $scrollElement 为滚动列表容器// lastScrollTop 为上一次滚动后的滚动条高度,初始值为 0// 该值需要在每次触发滚动事件时进行更新$scrollElement.scrollTop = lastScrollTop

// 4. 对于高度不定的列表来说,需要在渲染完成后调用更新cell高度的方法manager.updateCellHeight([cellHeight1, cellHeight2, ...])

// 可以通过下面的方式获取到cell的高度值// $cell 为单个cell节点let height = $cell.getBoundingClientRect().height

// 5. 最重要的一点是,需要监听滚动列表容器的滚动时间,监听到滚动后触发 manager 的更新列表方法并更新 lastScrollTop// 然后重复执行 2 3 4 步$scrollElement.onScroll = () => {

lastScrollTop = this.$refs.$scroll.scrollTop

manager.updateScroll(lastScrollTop)

// TODO 2,3,4}

以上就是整个 demo 的代码逻辑了,并不负责。当然,暂时还有一些功能并没有去实现,比如上一篇说到的一些问题。这篇再提出两个问题:当快速滚动列表时,或突然将高度定位到某一点时,对于不定高度的列表来说,由于上面的列表项还未来得及渲染和计算高度,此时会出现比较大的bug,上下支撑和整体高度计算都会出现比较大甚至很大的误差。

如果只是用在移动端,滚动速度并不会很快,所以在移动端使用时并不会出现明显的bug.

当列表中的数据更新时(如从原来的 20 项变为 30 项),此时需要对所有的渲染数据进行更新,包括上下撑起的高度、总高度以及不定高度列表的 cellHeight,同时还要保证滚动条的位置不变,即使新增了数据,用户看到的依然是未新增之前的内容。

这一点比较容易实现,但是由于时间原因并没有去完成。感兴趣的小伙伴可以自己完成以下。TroyXL/vue-components​link.jianshu.com

正文内容到此结束,下面附上整个demo的源码,不想去 github 看的小伙伴可以直接看这里

Demo文件 Scroll.vue

:style="props.cell.style">{{props.cell.text}}

import InfiniteScroll from '@/src/infiniteScroll/InfiniteScroll'

export default {

name: 'Scroll',

components: { InfiniteScroll },

computed: {

cells () {

return new Array(1000).fill(1).map((item, index) => {

return {

style: {

height: Math.floor(Math.random() * 100 + 100) + 'px',

// height: '100px',

color: '#ffffff',

fontSize: '30px',

background: this.getRandomColor()

},

text: '#' + (index + 1)

}

})

}

},

methods: {

getRandomColor () {

const colors = new Array(3).fill(1).map(item => Math.floor(Math.random() * 255))

return `rgb(${colors.join(',')})`

}

}

}

无限滚动组件文件 InfiniteScroll.vue

ref="$scroll"

:style="{ height: this.scrollViewHeight + 'px' }"

@scroll.passive="onScroll">

import ScrollManager from './ScrollManager'

let manager

let lastScrollTop = 0

let heightFixed = true

export default {

name: 'InfiniteScroll',

props: {

scrollViewHeight: {

type: Number,

required: true

},

list: {

type: Array,

required: true

},

// cell缓存数量 即不在可视区域内的预加载数量

cellCacheNumber: {

type: Number,

default: 3

},

// cell高度值 如果为0或不传则为动态高度 不为0则为固定高度

cellHeight: {

type: Number,

default: 0

},

},

data () {

return {

scrollData: {

scrollHeight: 0,

paddingTop: 0,

paddingBottom: 0,

displayCells: []

}

}

},

methods: {

initScrollManager () {

manager = new ScrollManager({

list: this.list,

scrollViewHeight: this.scrollViewHeight,

cellHeight: this.cellHeight,

cellCacheNumber: this.cellCacheNumber,

firstRenderNumber: 10

})

},

updateScrollRender () {

this.scrollData = manager.getRenderInfo()

this.$forceUpdate()

// 更新完成后矫正滚动条位置

this.$nextTick(() => {

this.$refs.$scroll.scrollTop = lastScrollTop

if (!heightFixed) manager.updateCellHeight(

this.$refs.$cell.map(item => item.getBoundingClientRect().height)

)

})

},

onScroll () {

lastScrollTop = this.$refs.$scroll.scrollTop

manager.updateScroll(lastScrollTop)

this.updateScrollRender()

}

},

watch: {

list () {

manager.updateList(this.list)

}

},

mounted () {

if (!this.cellHeight) heightFixed = false

this.initScrollManager()

this.updateScrollRender()

}

}

.t-scroll {

position: relative;

background: #eeeeee;

overflow: scroll;

}

.t-scroll-cell {

color: #ffffff;

font-size: 30px;

font-weight: bolder;

}

无限滚动类文件 ScrollManager.js

export default class ScrollManager {

// 构造器方法 constructor ( {

list, // 待渲染的列表数据 Array scrollViewHeight, // 滚动视图的高度,即滚动区域可见部分的高度 cellHeight, // 每个 item 的高度,如果设置了该值则认为是固定高度列表 cellCacheNumber, // 上下两方缓冲的item数量 firstRenderNumber // 动态高度时单屏初次渲染的列表数量 } ) {

// 滚动可视区域与滚动列表高度 this.scrollViewHeight = this.scrollHeight = scrollViewHeight

// cell平均高度 等于0则为动态高度 this.cellHeight = cellHeight

this.heightFixed = cellHeight ? true : false

// 预加载的cell数量 this.cellCacheNumber = cellCacheNumber || 3

// 单屏渲染数量 this.renderNumber = firstRenderNumber || 10

// 滚动区域上下撑开的高度 this.paddingTop = this.paddingBottom = 0

// cell的高度数据缓存,只在不固定高度时有效 this.heightCache = new Array(list ? list.length : 0).fill(this.cellHeight)

// 渲染列表 this.list = list

// 待渲染列表 this.displayCells = []

// 当前待渲染列表的第一个元素为在全部列表中的位置 this.passedCells = 0

// 当前渲染的cells的总高度 this.currentCellsTotalHeight = 0

this.initScroll()

}

// 初始化滚动列表 // 计算首屏需要渲染的items和缓冲items initScroll () {

if (this.heightFixed) { // cell高度固定时,校正滑动区域总高度,计算单屏渲染的cell数量及底部支撑高度 this.scrollHeight = this.list.length * this.cellHeight

this.renderNumber = Math.ceil(this.scrollViewHeight / this.cellHeight)

this.displayCells = this.list.slice(0, this.renderNumber + this.cellCacheNumber * 2)

this.paddingBottom = this.scrollHeight - this.displayCells.length * this.cellHeight

} else { // cell高度不固定时,渲染初次加载的单屏cell数量 this.displayCells = this.list.slice(0, this.renderNumber + this.cellCacheNumber * 2)

}

}

// 滚动时更新数据 // 根据滚动条高度计算已经划出屏幕并且不再需要渲染的items // 更新需要渲染的items和缓冲items // 并更新列表上方和下方需要支撑起的高度 updateScroll (scrollTop) {

if (this.heightFixed) {

this.passedCells = Math.floor(scrollTop / this.cellHeight)

this._adjustCells()

this.currentCellsTotalHeight = this.displayCells.length * this.cellHeight

this.paddingTop = this.passedCells * this.cellHeight

} else {

let passedCellsHeight = 0

for (let i = 0; i < this.heightCache.length; i++) {

if (scrollTop >= passedCellsHeight) this.passedCells = i

else break

passedCellsHeight += this.heightCache[i] ? this.heightCache[i] : this.cellHeight

}

this._adjustCells()

this.paddingTop = this.heightCache.reduce((sum, height, index) => {

if (index < this.passedCells) return sum + height

return sum

}, 0)

}

this.paddingBottom = this.scrollHeight - this.paddingTop - this.currentCellsTotalHeight

if (this.paddingBottom < 0) this.paddingBottom = 0

}

// 内部调用的调整items相关数据的方法 // 包括已经不需要渲染的items和需要渲染的items _adjustCells () {

this.passedCells = this.passedCells > this.cellCacheNumber ? this.passedCells - this.cellCacheNumber : 0

this.displayCells = this.list.slice(this.passedCells, this.renderNumber + this.cellCacheNumber * 2 + this.passedCells)

}

// 动态高度时根据已缓存的cell高度计算平均高度,方法接受当前渲染的cells的高度数组 // 对已经渲染过的cell高度进行缓存,保证上方的支撑高度计算准确 // 对未渲染过的cell高度进行预估,保证下方的支撑高度尽量靠近实际高度 // 调整整个滑动列表的总高度 updateCellHeight (cellsHeightInfo) {

if (this.heightFixed) return

// 更新平均cell高度 this.currentCellsTotalHeight = cellsHeightInfo.reduce((sum, height) => sum + height, 0)

this.cellHeight = Math.round(this.currentCellsTotalHeight / cellsHeightInfo.length)

this.renderNumber = Math.ceil(this.scrollViewHeight / this.cellHeight)

// 保存已知cell的高度信息 this.heightCache.splice(this.passedCells, cellsHeightInfo.length, ...cellsHeightInfo)

// 预估滑动区域总高度 this.scrollHeight = this.heightCache.reduce((sum, height) => {

if (height) return sum + height

return sum + this.cellHeight

}, 0)

}

// 获取待渲染的items及相关数据 getRenderInfo () {

return {

scrollHeight: this.scrollHeight,

paddingTop: this.paddingTop,

paddingBottom: this.paddingBottom,

displayCells: this.displayCells

}

}

}

关注微信公众号【前端程序员的斜杠青年进化录】(微信搜索 weekly-front-end),感谢你的支持

微信扫码,给我赞赏一下~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值