需求是不重疊彈幕
公司使用的是云信接發(fā)消息,我的思路是給直播間四條彈幕跑道,接收到彈幕之后往跑道內(nèi)插入彈幕
以下是其中一條彈幕跑道的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前先判斷當(dāng)前跑道能不能插入彈幕,確保當(dāng)前彈幕不會(huì)和上一條彈幕重疊,如果不能(即當(dāng)前跑道有一條彈幕正在移動(dòng)),則把當(dāng)前彈幕插入下一條彈幕跑道里(我這里一共設(shè)了四條跑道),下面是方法:
注:我這邊定義的是彈幕在屏幕上移動(dòng)的時(shí)長(zhǎng)是6s,彈幕移動(dòng)動(dòng)畫用的是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 //元素插入需要的時(shí)間
obj.startTime = Date.now() //元素動(dòng)畫開始的時(shí)間
obj.endTime = obj.startTime+6000+obj.second //元素動(dòng)畫結(jié)束的時(shí)間
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) { //根據(jù)文本內(nèi)容獲取元素寬度
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;
}
上面的方法的作用是
計(jì)算出并記錄下
1.彈幕從屏幕左邊出現(xiàn)到完全出現(xiàn)需要的時(shí)間
2.彈幕插入動(dòng)畫開始的時(shí)間(在屏幕左邊出現(xiàn)前一秒)
3彈幕動(dòng)畫結(jié)束的時(shí)間(在屏幕右邊消失的后一秒)
用于判斷↓
//判斷當(dāng)前彈幕是否能插入跑道
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] //當(dāng)前跑道最后一條數(shù)據(jù)
if(this.list[i].length==0){ //如果是當(dāng)前跑道的第一條數(shù)據(jù) 直接插入跑道
this.list[i].push(this.formatObj(obj))
return
}
if(Date.now()-lastChatData.startTime>lastChatData.second){ //如果不是當(dāng)前跑道的第一條數(shù)據(jù) 過了等待時(shí)間插入跑道
this.list[i].push(this.formatObj(obj))
return
}
}
//都不是則進(jìn)入等待隊(duì)列
this.toWait.push(obj)
},
this.toWait 是等待隊(duì)列 ,在所有跑道都有彈幕在插入的時(shí)候,新的彈幕消息進(jìn)入等待隊(duì)列,然后定時(shí)詢問是否有跑道能插入數(shù)據(jù) ,我這里使用的是watch
watch:{
YxLiveRoomBarrageVo(val){ //接收到消息的時(shí)候判斷是否能插入彈幕
this.getTrack(val)
},
toWait(newVal){ //等待隊(duì)列有值得時(shí)候定時(shí)詢問是否能插入彈幕
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:[] //等待插入的彈幕
}
},
監(jiān)聽它是為了定時(shí)清理數(shù)據(jù) ,每條跑道的彈幕數(shù)據(jù)大于20的時(shí)候清理一次,
清理的方法是
delectItem(arr){ //定時(shí)清除
for(let i = 0;i<arr.length;i++){
if(arr[i].endTime<Date.now()){
arr[i].trumpetItemShow = false
}
}
},
所以上面的endTime是用來判斷但是是不是已經(jīng)完成動(dòng)畫了,完成了的彈幕就能清理掉,最后
destroyed() {
this.timer && clearInterval(this.timer)
this.timer1 && clearInterval(this.timer1)
}
清一下定時(shí)器
