vue弹幕实现不重叠
实现 Vue 弹幕不重叠的核心思路
弹幕不重叠的关键在于动态计算每条弹幕的垂直位置,确保新弹幕与已有弹幕在运动轨迹上不发生碰撞。可以通过轨道管理、实时碰撞检测或固定轨道分配来实现。
基于轨道分配的实现方案
轨道预计算 将屏幕垂直方向划分为若干虚拟轨道,每条弹幕在固定轨道上运动。初始化时创建轨道状态数组,记录每个轨道是否被占用。
data() {
return {
tracks: 5, // 轨道数量
trackStatus: Array(5).fill(false), // 轨道占用状态
danmus: [] // 弹幕数据
}
}
轨道分配算法 新弹幕生成时遍历轨道状态,找到第一个空闲轨道并标记为占用。弹幕离开屏幕后释放轨道。
methods: {
addDanmu(text) {
const trackIndex = this.trackStatus.findIndex(used => !used)
if (trackIndex === -1) return // 无可用轨道
this.trackStatus[trackIndex] = true
this.danmus.push({
text,
track: trackIndex,
left: '100%',
id: Date.now()
})
},
releaseTrack(trackIndex) {
this.trackStatus[trackIndex] = false
}
}
CSS 轨道定位 通过计算轨道位置确定弹幕的 top 值,使用 CSS 动画控制水平移动。
.danmu-item {
position: absolute;
white-space: nowrap;
animation: move linear;
}
@keyframes move {
from { transform: translateX(100%); }
to { transform: translateX(-100%); }
}
基于动态碰撞检测的实现方案
实时位置检测 记录所有活跃弹幕的当前水平位置和宽度,新弹幕生成时计算垂直位置避免重叠。
checkCollision(newDanmu) {
const newWidth = this.calculateWidth(newDanmu.text)
return this.activeDanmus.some(danmu => {
return danmu.top === newDanmu.top &&
danmu.left + danmu.width > newDanmu.left
})
}
动态定位调整 未找到合适位置时可延迟生成或调整弹幕速度,确保不会与现有弹幕冲突。
findAvailableTop() {
let top = 0
while (top < maxHeight) {
if (!this.checkCollision({ top, left: screenWidth })) {
return top
}
top += rowHeight
}
return null
}
性能优化建议
- 使用
requestAnimationFrame替代 CSS 动画实现更精确控制 - 对弹幕文本进行宽度预计算,避免频繁 DOM 操作
- 设置合理的轨道数量和弹幕速度,平衡视觉效果和性能
- 对于大量弹幕考虑虚拟滚动技术,只渲染可视区域内元素
完整示例组件
<template>
<div class="danmu-container">
<div
v-for="danmu in activeDanmus"
:key="danmu.id"
class="danmu-item"
:style="{
top: `${danmu.track * 30}px`,
color: danmu.color
}"
>
{{ danmu.text }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
tracks: 8,
trackStatus: Array(8).fill(false),
activeDanmus: []
}
},
methods: {
addDanmu(text, color = '#fff') {
const track = this.findAvailableTrack()
if (track === null) return
const id = Date.now()
this.activeDanmus.push({
id,
text,
color,
track
})
setTimeout(() => {
this.releaseTrack(track)
this.activeDanmus = this.activeDanmus.filter(d => d.id !== id)
}, 10000)
},
findAvailableTrack() {
return this.trackStatus.findIndex(used => !used)
},
releaseTrack(track) {
this.trackStatus[track] = false
}
}
}
</script>
<style>
.danmu-container {
position: relative;
height: 240px;
overflow: hidden;
}
.danmu-item {
position: absolute;
left: 100%;
white-space: nowrap;
animation: danmu-move 10s linear;
}
@keyframes danmu-move {
to { transform: translateX(-100vw); }
}
</style>






