uniapp h5模拟的im消息聊天框,上滑加载,半虚拟滚动

<!-- 
标题:
uniapp h5模拟的im消息聊天框,上滑加载,半虚拟滚动
介绍:
vue2,js,uniapp框架模拟的im消息聊天框,只可用于h5。(如果要转为其他平台,需要修改dom操作的部分)。
功能:
点击‘加数据’会模拟im依次增加数据,如果滚动条在最底部,则滚动条保持底部位置,如果滚动条不在底部,则滚动条保持原来位置,信息dom继续向下生成;
当滚动条在最底部时,只会渲染20个信息dom;
当滚动条上滑,出现‘到底部’按钮;
scroll-view触顶时加载历史消息,滚动条保持原位,上面会渲染更多信息dom;
点击‘到底部’或者滚动到最底部时,只渲染最下面20个信息dom,移除历史信息dom
 -->

<template>
	<view class="test2-wrap">
		<view class="chat-com-wrap">
			<scroll-view :scroll-y="true" :scroll-with-animation="false" :refresher-enabled="false" :scroll-top="scrollTop" @scroll="onscroll" @scrolltolower="onscrolltolowerFn" @scrolltoupper="onscrolltoupper" class="chat-box-outer" id="my-scroll-view">
				<view class="chat-box" id="scroll-view-content" ref="chatBoxRef">
					<view class="chat-item" v-for="(item, index) in showDataList" :key="index" :id="`msg-${item.id}`">
						<!-- 此处替换单条消息组件 -->
						{{ item.name }}
					</view>
				</view>
			</scroll-view>
			<view v-if="!isNowBottom" class="to-bottom-box" @click="reachBottomBtnFn(2)">
				到底部
			</view>
		</view>
		<button @click="autoAddData">加数据</button>
	</view>
</template>

<script>
	// 节流
	const customThrottle = (fn, delay=500) => {
		let valid = true
		return function(){
		  if(valid){
		      valid = false
		      fn.apply(this, arguments)
		      setTimeout(() => {
		          valid = true
		      }, delay)
		  }
		}
	}
	export default {
		data(){
			return {
				allDataList: [], // 所有数据
				showDataList: [] ,// 渲染数据
				scrollTop:0, // 顶部距离
				scrollTopLast: 0, // 上一次顶部距离
				isNowBottom: true, // 当前是否处于最底部
				myScrollViewDomHeight: 0, // #my-scroll-view dom的高度
				newNum: 0, // 造假数据用的计数器
			}
		},
		props: {
			maxShowNum: { // 滚动条最底部时最大渲染数量
				type: Number,
				default: 20
			}
		},
		mounted(){
			this.getMyScrollViewDomHeight()
		},
		onLoad(){
			this.autoAddData()
		},
		methods: {
			getMyScrollViewDomHeight(){
				this.$nextTick(()=>{
					const myScrollViewDom = document.querySelector('#my-scroll-view')
					console.log('myScrollViewDom:',myScrollViewDom.clientHeight)
					this.myScrollViewDomHeight = myScrollViewDom.clientHeight
				})
			},
			// 获取未渲染的历史数据
			getHistory(num=20){
				let targetIndex = this.allDataList.findIndex(item => item.id==this.showDataList[0].id)
				if(targetIndex<0){
					return []
				}else{
					let startIndex = targetIndex-num<0 ? 0 : targetIndex-num
					let newArr = this.allDataList.slice(startIndex, targetIndex)
					return newArr
				}
			},
			// 模拟im依次增加假消息
			autoAddData(){
				const msgNum = 20 // 增加消息数量
				const gapTime = 100 // 每条消息间隔时间,单位:ms
				let newObj = {
					name: '我是消息'+ parseInt(+new Date()/10) ,
					id: +new Date() // 必须有唯一的id!!!!!!!!
				}
				this.addDataObjToList(newObj)
				this.newNum++
				if(this.newNum>msgNum){
					this.newNum = 0
					return
				}
				setTimeout(()=>{
					this.autoAddData()
				},gapTime)
			},
			// 添加新数据obj至队列
			addDataObjToList(newObj){
				this.allDataList.push(newObj)
				if(this.isNowBottom){ // 滚动条在最底部
					if(this.showDataList.length>=this.maxShowNum){
						this.showDataList.shift()
						this.showDataList.push(newObj)
					}else{
						this.showDataList.push(newObj)
					}
					this.reachBottomBtnFn(1)
				}else{
					this.showDataList.push(newObj)
				}
			},
			// 设置滚动条位置到底部
			reachBottomBtnFn(type){
				// type 1数据更新自动到底 2手动点击到底部
				if(type==2){
					this.showDataList = this.allDataList.slice(0-this.maxShowNum)
				}
				this.scrollTop = this.scrollTopLast
				this.$nextTick(()=>{
					let chatBoxDom = document.querySelector('#scroll-view-content')
					this.scrollTop = chatBoxDom.clientHeight
					this.isNowBottom = true
				})
			},
			// 监听-滚动
			onscroll:customThrottle(function(e){
				let { scrollTop } = e.detail
				if(this.scrollTopLast > scrollTop){
					console.log('向上滚动')
					// console.log('scrollTop:',scrollTop)
					this.isNowBottom = false
				}
				this.scrollTopLast = scrollTop
			},1000),
			// 监听-滚动到底部
			onscrolltolowerFn(e){
				console.log('滚动到底部')
				// console.log('onscrolltolowerFn-e:',e)
				if(this.showDataList.length>this.maxShowNum+this.maxShowNum/2){
					this.showDataList = this.showDataList.slice(0-this.maxShowNum)
				}
				this.$nextTick(()=>{
					this.isNowBottom = true
				})
			},
			// 监听-滚动到顶部
			onscrolltoupper(e){
				// console.log('滚动到顶部')
				let targetid = `#msg-${this.showDataList[0].id}`
				let historyArr = this.getHistory()
				this.showDataList = [...historyArr, ...this.showDataList]
				if(historyArr.length){
					this.$nextTick(()=>{
						this.setScrollTo(targetid)
					})
				}
			},
			// 设置滚动条位置(加载历史消息时使滚动条保持原位)
			setScrollTo(selector){
				// console.log('setScrollTo-selector:',selector)
				let targetDom = document.querySelector(selector)
				// console.log('targetDom:',targetDom.offsetTop)
				this.scrollTop = this.scrollTopLast
				this.$nextTick(()=>{
					this.scrollTop = targetDom.offsetTop-30
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	.test2-wrap{
		.chat-com-wrap{
			position: relative;
			width: 400rpx;
			height: 400rpx;
			.chat-box-outer{
				border: 2rpx solid red;
				width: 100%;
				height: 100%;
			}
			.chat-box{
				.chat-item{
					padding: 10rpx;
				}
			}
			.to-bottom-box{
				position: absolute;
				left: 50%;
				bottom: 30rpx;
				transform: translate(-50%, -50%);
				background-color: rgba(0,0,0,0.5);
				color: #fff;
				padding: 10rpx 30rpx;
				border-radius: 50rpx;
			}
		}
		
		
	}
</style>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值