147 lines
3.1 KiB
Vue
147 lines
3.1 KiB
Vue
<template>
|
|
<view class="danmu-container" v-if="visible">
|
|
<view
|
|
v-for="item in danmuItems"
|
|
:key="item.id"
|
|
class="danmu-item"
|
|
:style="{
|
|
top: item.top + 'rpx',
|
|
animationDuration: item.speed + 's',
|
|
color: item.color
|
|
}"
|
|
@animationend="finishDanmu(item.id, item.lane)"
|
|
>
|
|
{{ item.text }}
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
list: Array,
|
|
visible: { type: Boolean, default: true },
|
|
row: { type: Number, default: 5 } // 轨道数
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
danmuItems: [],
|
|
laneBusy: [], // 轨道是否被占用
|
|
pendingQueue: [] // 等待队列
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.laneBusy = Array(this.row).fill(false)
|
|
this.startDanmu()
|
|
},
|
|
|
|
methods: {
|
|
startDanmu() {
|
|
let index = 0
|
|
setInterval(() => {
|
|
if (this.list && this.list.length > 0) {
|
|
const text = this.list[index].content
|
|
this.createDanmu(text)
|
|
index = (index + 1) % this.list.length
|
|
}
|
|
}, 2200)
|
|
},
|
|
|
|
createDanmu(text) {
|
|
const lane = this.getFreeLane()
|
|
|
|
// 如果没有空闲轨道,加入等待队列
|
|
if (lane === -1) {
|
|
this.pendingQueue.push(text)
|
|
return
|
|
}
|
|
|
|
const id = Date.now() + Math.random()
|
|
|
|
const item = {
|
|
id,
|
|
text,
|
|
top: lane * 80 + 20, // 每条轨道高度 80rpx,顶部留20rpx间距
|
|
speed: 14 + Math.random()*6,
|
|
color: this.randomColor(),
|
|
lane
|
|
}
|
|
|
|
this.danmuItems.push(item)
|
|
},
|
|
|
|
getFreeLane() {
|
|
// 找到第一个空轨道
|
|
for (let i = 0; i < this.row; i++) {
|
|
if (!this.laneBusy[i]) {
|
|
this.laneBusy[i] = true
|
|
return i
|
|
}
|
|
}
|
|
// 如果都满了,返回-1,不创建弹幕
|
|
return -1
|
|
},
|
|
|
|
finishDanmu(id, lane) {
|
|
// 移除弹幕项
|
|
this.danmuItems = this.danmuItems.filter(i => i.id !== id)
|
|
|
|
// 释放轨道
|
|
if (lane !== undefined && lane >= 0) {
|
|
this.laneBusy[lane] = false
|
|
|
|
// 检查等待队列,如果有等待的弹幕,立即创建
|
|
if (this.pendingQueue.length > 0) {
|
|
const text = this.pendingQueue.shift()
|
|
this.$nextTick(() => {
|
|
this.createDanmu(text)
|
|
})
|
|
}
|
|
}
|
|
},
|
|
|
|
randomColor() {
|
|
const colors = ["#fff", "#00F6FF", "#FFEA00", "#FF66C4"]
|
|
return colors[Math.floor(Math.random() * colors.length)]
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.danmu-container {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 450rpx;
|
|
width: 100%;
|
|
height: 600rpx;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
z-index: 100;
|
|
}
|
|
|
|
.danmu-item {
|
|
position: absolute;
|
|
right: -400rpx;
|
|
font-size: 26rpx;
|
|
white-space: nowrap;
|
|
animation-name: danmu-move;
|
|
animation-timing-function: linear;
|
|
animation-fill-mode: forwards;
|
|
background: rgb(0 0 0 / 60%);
|
|
padding: 5px 14px;
|
|
border-radius: 20px;
|
|
line-height: 1.5;
|
|
max-width: 600rpx;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
@keyframes danmu-move {
|
|
from { transform: translateX(0); }
|
|
to { transform: translateX(-150vw); }
|
|
}
|
|
</style>
|