最近项目中遇到了实现直播弹幕的功能,现有的插件要么是太过繁琐,要么是不支持小程序,于是自己手写了一个简易的弹幕功能,技术很菜,不喜可过。话不多说,先上代码。
<template>
<view
class="barrage-container"
:style="{
width: barrageObject.barrageContainerWidth + 'rpx',
height: barrageObject.barrageContainerHeight + 'rpx'
}"
>
<view
v-for="index in orbitalNum"
:key="index"
:id="`orbital${index + 1}`"
class="barrage-orbital"
:style="{ height: barrageObject.barrageItemContainerHeight + 'px' }"
>
<view
class="barrage-item"
:style="{
backgroundColor: item.backgroundColor,
}"
v-for="item of orbitalBarrage[`orbital${index + 1}`]"
:key="item.id"
:id="item.id"
>{{ item.content }}</view>
</view>
</view>
</template>
<script>
export default {
name: 'Barrage',
data() {
return {
barrageObject: {
barrageContainerWidth: 750,
barrageContainerHeight: 244,
barrageContainerTime: 27, //单位s 控制速度,值越大越慢,修改之后需要修改css中动画执行时间
barrageItemContainerHeight: 62, //一条弹幕容器高度
maxLength: 30 // 弹幕最大保存数量,超过之后从头部开始删除
},
barrageList: [],
color: {
message_user: 'rgba(0, 0, 0, 0.3)',
message_apply: 'rgba(251, 73, 107, 0.7)',
message_join: 'rgba(144, 139, 226, 0.7)'
},
barrageSpeed: 100,
transformTime: 0,
orbitalNum: 1, // 弹幕轨道数量
orbitalListObject: [], // 弹幕轨道状态数组
orbitalBarrage: {},
screenWidth: 0,
moveScreenTime: 0
}
},
props: {
barrage: {
required: false,
type: Object
}
},
created() {
this.barrageObject = { ...this.barrageObject, ...this.barrage }
},
mounted() {
this.screenWidth = wx.getSystemInfoSync().windowWidth
this.barrageInit()
},
watch: {
barrage(val) {
// 合并传入的弹幕相关属性和默认属性
this.barrageObject = { ...this.barrageObject, ...this.barrage }
},
barrageList(val) {
if (val.length) {
// 弹幕队列中还存在弹幕就去执行挂载事件
this.checkOrbital(val)
} else {
// 如果弹幕队列已经清空就不执行任何操作
return
}
}
},
methods: {
// 初始化相关数据
barrageInit() {
// 初始化速度
// 计算弹幕轨道数量
this.orbitalNum = Math.floor(
this.barrageObject.barrageContainerHeight / this.barrageObject.barrageItemContainerHeight
)
//初始化轨道状态
for (var key = 0; key < this.orbitalNum; key++) {
this.orbitalListObject.push(true)
this.orbitalBarrage[`orbital${key + 1}`] = []
}
},
// 生成key
getUuiD() {
return (Math.random() + new Date().getTime()).toString(32).slice(0, 16)
},
// 弹幕消息是一个队列,用数组模拟队列
inQuene(val) {
if (this.barrageList.length >= this.barrageObject.maxLength) {
this.barrageList.shift()
}
if (val instanceof Object) {
this.barrageList.push({
id: this.getUuiD(),
content: val.content,
backgroundColor: this.color[val.type]
})
} else {
this.barrageList.push({
id: this.getUuiD(),
content: val,
backgroundColor: this.color['message_user']
})
}
},
outQuene() {
return this.barrageList.shift() // 拿出第一个元素并返回
},
// 选择弹幕轨道
checkOrbital() {
// 如果轨道有空-任意一条轨道中最后一条弹幕距离最右边的距离大于0,在这里为了视觉效果设定为大于10
// 如果所有都有空,那么就采用随机的方式
if (!this.barrageList.length) {
return
}
if (this.orbitalListObject.includes(true)) {
let orbitalIndex
// 如果所有轨道都有空
if (this.orbitalListObject.every((item) => item)) {
// 获取一个随机数
orbitalIndex = Math.floor(Math.random() * this.orbitalNum) + 1
} else {
//仅仅是存在空轨道的话就拿出第一个空轨道
orbitalIndex = this.orbitalListObject.findIndex((item) => item) + 1
}
// 调用挂载事件
this.mountBarrage(orbitalIndex)
}
// 如果轨道没空
return false
},
// 挂载弹幕至轨道
mountBarrage(orbitalIndex) {
const data = this.outQuene()
this.orbitalBarrage[`orbital${orbitalIndex}`].push(data)
this.orbitalBarrage = JSON.parse(JSON.stringify(this.orbitalBarrage))
// 轨道设置为false
this.orbitalListObject[orbitalIndex - 1] = false
var query = wx.createSelectorQuery().in(this)
let currentBarrageWidth
this.$nextTick(() => {
query
.selectAll(`.barrage-item`)
.boundingClientRect((rect) => {
try {
currentBarrageWidth = Math.ceil(rect.find((item) => item.id === data.id).width)
let timer1 = setTimeout(() => {
clearTimeout(timer1)
timer1 = null
this.orbitalListObject[orbitalIndex - 1] = true
this.checkOrbital()
}, Math.ceil((currentBarrageWidth / (2000 / 27)) * 1000 + 200))
} catch (error) {
this.orbitalListObject[orbitalIndex - 1] = true
this.orbitalBarrage[`orbital${orbitalIndex}`].pop()
this.checkOrbital()
}
})
.exec()
})
// 销毁元素
let timer2 = setTimeout(() => {
clearTimeout(timer2)
timer2 = null
this.orbitalBarrage[`orbital${orbitalIndex}`].shift()
this.orbitalBarrage = JSON.parse(JSON.stringify(this.orbitalBarrage))
}, 21 * 1000) // 多3秒,容错
}
}
}
</script>
<style>
.barrage-container {
display: flex;
margin: auto;
overflow: hidden;
flex-direction: column;
justify-content: space-between;
}
.barrage-orbital {
position: relative;
height: 62rpx;
}
.barrage-item {
position: absolute;
/* 2px容错,报错的时候直接销毁元素用,这样视觉上看不出来 */
left: calc(100% + 2px);
display: inline-block;
height: 62rpx;
padding: 0 30rpx;
line-height: 62rpx;
color: #fff;
white-space: nowrap;
border-radius: 40rpx;
/* 1.5倍时间 */
animation: move 27s linear;
}
@keyframes move {
from {
/* left: 100%; */
transform: translate(0);
}
to {
/* left: -300%; */
transform: translate(-2000px);
}
}
</style>
*通过外部传参的方式可以修改barrageObject对象中定义的参数,同时也可以直接修改组件内的参数来达到相应的效果。弹幕轨道数量是根据弹幕显示的范围和单个弹幕的高度计算得出的,在外部通过调用inQuene方法来添加弹幕,同时为弹幕设置了最大缓存数量为三十条,可根据业务具体需求进行更改,这里设置了三种弹幕类型,不同的弹幕类型对应不同的背景颜色,可以自己修改,删除与新增。同时,在高版本ios手机中因为使用wx获取dom的api会出现偶然性报错,所以ios手机会存在偶尔被吞弹幕的情况,概率较低。 *