需求是不重叠弹幕
公司使用的是云信接发消息,我的思路是给直播间四条弹幕跑道,接收到弹幕之后往跑道内插入弹幕
以下是其中一条弹幕跑道的html:
<div class="trumpet-box box1" v-for="(item,index) in list[0]">
<div class="trumpet-item">
<div class="top">
<i v-show="item.barrageType === 64"></i>
<span v-show="item.grade>5"></span>
<p>{{item.userNickname}}</p>
</div>
<div class="bottom">
<img :src="item.userAvatar">
<p>{{item.content}}</p>
<i v-show="item.barrageType === 64"></i>
</div>
</div>
</div>
trumpet-item 为弹幕 如果接收到消息就往list里push,push前先判断当前跑道能不能插入弹幕,确保当前弹幕不会和上一条弹幕重叠,如果不能(即当前跑道有一条弹幕正在移动),则把当前弹幕插入下一条弹幕跑道里(我这里一共设了四条跑道),下面是方法:
注:我这边定义的是弹幕在屏幕上移动的时长是6s,弹幕移动动画用的是translate
首先是先把从云信接收的消息体obj整理成自己想要的格式
formatObj(obj){
let width = this.textSize(14,obj.content)>70?this.textSize(14,obj.content)+70:140//元素的宽度
let boxWidth = document.getElementsByClassName('center')[0].offsetWidth //屏幕宽度
let speed = (width+boxWidth)/6 //弹幕速度
obj.second = (width/speed).toFixed(1)*1000 //元素插入需要的时间
obj.startTime = Date.now() //元素动画开始的时间
obj.endTime = obj.startTime+6000+obj.second //元素动画结束的时间
obj.trumpetItemShow = true
let style = document.styleSheets[0];
let transformWidth = width+boxWidth;
transformWidth = (Math.ceil(transformWidth))%2 ==0 ? Math.ceil(transformWidth) :Math.ceil(transformWidth)+1 //防止字体模糊
style.insertRule("@keyframes bounce-in{from{ transform: translateX(0); }to{ transform: translateX(-"+transformWidth+"px);}}",1);//写入样式
return obj
},
textSize(fontSize, text) { //根据文本内容获取元素宽度
let span = document.createElement("span");
span.innerText = text
span.style.visibility = "hidden";
span.style.fontSize = fontSize+'px';
span.style.id = 'fakeSpan';
document.getElementsByClassName("center")[0].appendChild(span);
let newWidth = span.offsetWidth;
document.getElementsByClassName("center")[0].removeChild(span);
return newWidth;
}
上面的方法的作用是
计算出并记录下
1.弹幕从屏幕左边出现到完全出现需要的时间
2.弹幕插入动画开始的时间(在屏幕左边出现前一秒)
3弹幕动画结束的时间(在屏幕右边消失的后一秒)
用于判断↓
//判断当前弹幕是否能插入跑道
getTrack(val){ //val是格式化好的弹幕
let obj = _.cloneDeep(val)
for(let i = 0;i<this.list.length;i++){ //遍历四条跑道
let lastChatData = this.list[i][this.list[i].length-1] //当前跑道最后一条数据
if(this.list[i].length==0){ //如果是当前跑道的第一条数据 直接插入跑道
this.list[i].push(this.formatObj(obj))
return
}
if(Date.now()-lastChatData.startTime>lastChatData.second){ //如果不是当前跑道的第一条数据 过了等待时间插入跑道
this.list[i].push(this.formatObj(obj))
return
}
}
//都不是则进入等待队列
this.toWait.push(obj)
},
this.toWait 是等待队列 ,在所有跑道都有弹幕在插入的时候,新的弹幕消息进入等待队列,然后定时询问是否有跑道能插入数据 ,我这里使用的是watch
watch:{
YxLiveRoomBarrageVo(val){ //接收到消息的时候判断是否能插入弹幕
this.getTrack(val)
},
toWait(newVal){ //等待队列有值得时候定时询问是否能插入弹幕
if(newVal.length>0){
this.timer = setInterval(()=>{
for(let item of newVal){
this.getTrack(item)
this.toWait.shift()
}
}, 500)
}else{
this.timer && clearInterval(this.timer)
}
},
list(newVal){
this.list.forEach((val,index)=>{
if(val.length>=20){
this.delectItem(val)
}
})
}
},
list是我的四条跑道
data(){
return {
list:[[],[],[],[]],
timer:null,
timer1:null,
toWait:[] //等待插入的弹幕
}
},
监听它是为了定时清理数据 ,每条跑道的弹幕数据大于20的时候清理一次,
清理的方法是
delectItem(arr){ //定时清除
for(let i = 0;i<arr.length;i++){
if(arr[i].endTime<Date.now()){
arr[i].trumpetItemShow = false
}
}
},
所以上面的endTime是用来判断但是是不是已经完成动画了,完成了的弹幕就能清理掉,最后
destroyed() {
this.timer && clearInterval(this.timer)
this.timer1 && clearInterval(this.timer1)
}
清一下定时器
成果.jpg
网友评论