<!--
标题:
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>
03-28
677
07-22
118
06-07
1360
05-18