介绍
弹幕组件,用于实现类似视频网站中实时滚动的弹幕效果。弹幕会从容器一侧进入,以固定速度穿过屏幕,并支持多轨道、循环播放、暂停与继续等能力。
组件是泛型的,list 项的类型由你决定;通过 item 插槽自行渲染每条弹幕(当项为字符串或数字时会直接展示)。
- 方向:跟随文档
dir自动判断 RTL; - 自动暂停:页面隐藏、组件滚出视口时会自动暂停(
autoPause),恢复后自动继续; - 性能:可配合
maxItems限制同时在飞数量。
代码演示
基础使用
通过 list 传入弹幕数据,设置 loop 循环播放。
vue
<template>
<div class="danmaku-box">
<s-danmaku :list="list" loop />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref(Array.from({ length: 20 }, (_, i) => `用户${i + 1} 进入直播间`))
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
</style>vue
<template>
<div class="danmaku-box">
<s-danmaku :list="list" loop />
</div>
</template>
<script setup lang="js">
import { ref } from 'vue'
const list = ref(Array.from({ length: 20 }, (_, i) => `用户${i + 1} 进入直播间`))
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
</style>自定义内容
通过 item 插槽可以自定义弹幕内容,例如头像加昵称加文本的组合。
vue
<template>
<div class="danmaku-box">
<s-danmaku :list="list" :rows="4" :speed="120" loop>
<template #item="{ item }">
<div class="bullet">
<img class="bullet-avatar" :src="item.avatar" alt="" />
<span class="bullet-name">{{ item.name }}</span>
<span class="bullet-text">{{ item.text }}</span>
</div>
</template>
</s-danmaku>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref(
[
['小明', '小明送出火箭 x1'],
['小红', '小红送出跑车 x2'],
['小刚', '小刚送出玫瑰 x66'],
['小美', '小美送出爱心 x520'],
['阿伟', '阿伟送出飞机 x1'],
['阿花', '阿花送出皇冠 x3'],
['老铁', '老铁送出啤酒 x10'],
['小玉', '小玉送出星星 x99'],
].map(([name, text]) => ({
name,
text,
avatar: `https://api.dicebear.com/9.x/thumbs/svg?seed=${name}`,
})),
)
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
.bullet {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 999px;
background-color: rgba(0, 0, 0, 0.45);
.bullet-avatar {
width: 20px;
height: 20px;
margin-inline-end: 6px;
border-radius: 50%;
}
.bullet-name {
margin-inline-end: 6px;
color: #9ed6ff;
font-size: 12px;
}
.bullet-text {
font-size: 12px;
}
}
</style>vue
<template>
<div class="danmaku-box">
<s-danmaku :list="list" :rows="4" :speed="120" loop>
<template #item="{ item }">
<div class="bullet">
<img class="bullet-avatar" :src="item.avatar" alt="" />
<span class="bullet-name">{{ item.name }}</span>
<span class="bullet-text">{{ item.text }}</span>
</div>
</template>
</s-danmaku>
</div>
</template>
<script setup lang="js">
import { ref } from 'vue'
const list = ref(
[
['小明', '小明送出火箭 x1'],
['小红', '小红送出跑车 x2'],
['小刚', '小刚送出玫瑰 x66'],
['小美', '小美送出爱心 x520'],
['阿伟', '阿伟送出飞机 x1'],
['阿花', '阿花送出皇冠 x3'],
['老铁', '老铁送出啤酒 x10'],
['小玉', '小玉送出星星 x99'],
].map(([name, text]) => ({
name,
text,
avatar: `https://api.dicebear.com/9.x/thumbs/svg?seed=${name}`,
})),
)
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
.bullet {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 999px;
background-color: rgba(0, 0, 0, 0.45);
.bullet-avatar {
width: 20px;
height: 20px;
margin-inline-end: 6px;
border-radius: 50%;
}
.bullet-name {
margin-inline-end: 6px;
color: #9ed6ff;
font-size: 12px;
}
.bullet-text {
font-size: 12px;
}
}
</style>动态发送与播放控制
通过 Expose 中的 send 方法动态发送弹幕,也可以通过 pause、resume 控制播放。
vue
<template>
<div class="danmaku-box">
<s-danmaku ref="danmaku" :list="list" loop />
</div>
<div class="toolbar">
<s-button size="small" color="secondary" @click="danmakuRef?.pause()">暂停</s-button>
<s-button size="small" color="secondary" @click="danmakuRef?.resume()">继续</s-button>
<s-button size="small" @click="send">发送弹幕</s-button>
</div>
</template>
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
const danmakuRef = useTemplateRef('danmaku')
const list = ref(Array.from({ length: 10 }, (_, i) => `欢迎第 ${i + 1} 位观众`))
let seq = 0
const send = () => {
seq++
danmakuRef.value?.send(`这是一条新发送的弹幕 ${seq}`)
}
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
.toolbar {
display: flex;
gap: 8px;
margin-top: 12px;
}
</style>vue
<template>
<div class="danmaku-box">
<s-danmaku ref="danmaku" :list="list" loop />
</div>
<div class="toolbar">
<s-button size="small" color="secondary" @click="danmakuRef?.pause()">暂停</s-button>
<s-button size="small" color="secondary" @click="danmakuRef?.resume()">继续</s-button>
<s-button size="small" @click="send">发送弹幕</s-button>
</div>
</template>
<script setup lang="js">
import { ref, useTemplateRef } from 'vue'
const danmakuRef = useTemplateRef('danmaku')
const list = ref(Array.from({ length: 10 }, (_, i) => `欢迎第 ${i + 1} 位观众`))
let seq = 0
const send = () => {
seq++
danmakuRef.value?.send(`这是一条新发送的弹幕 ${seq}`)
}
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
.toolbar {
display: flex;
gap: 8px;
margin-top: 12px;
}
</style>RTL 方向
跟随文档 dir 自动判断方向;当容器处于 dir="rtl" 时,弹幕会从左侧进入、向右滚动。
vue
<template>
<div class="danmaku-box" dir="rtl">
<s-danmaku :list="list" loop />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref(Array.from({ length: 12 }, (_, i) => `弹幕 ${i + 1} 从左侧进入`))
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
</style>vue
<template>
<div class="danmaku-box" dir="rtl">
<s-danmaku :list="list" loop />
</div>
</template>
<script setup lang="js">
import { ref } from 'vue'
const list = ref(Array.from({ length: 12 }, (_, i) => `弹幕 ${i + 1} 从左侧进入`))
</script>
<style lang="scss" scoped>
.danmaku-box {
color: #fff;
border-radius: 8px;
background-color: #1c1c1e;
}
</style>API
DanmakuProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| list | 弹幕列表 | T[] | - |
| rows | 轨道数量 | number | 3 |
| speed | 滚动速度 (px/s) | number | 100 |
| speedRange | 速度浮动范围 (px/s) | number | 0 |
| gap | 同轨道相邻弹幕的间距 (px) | number | 40 |
| easing | 动画缓动函数 | string | 'linear' |
| autoPlay | 是否自动播放 | boolean | true |
| loop | 是否循环播放 | boolean | false |
| autoPause | 页面隐藏/滚出视口自动暂停 | boolean | true |
| maxItems | 同时在飞弹幕数量上限(0 不限) | number | 0 |
| rowHeight | 轨道高度(数字视为 px) | number | string | 34 |
开启
speedRange后,同一轨道内各弹幕速度略有不同,观感更自然;代价是较快的弹幕可能追上较慢的弹幕而形成轻微重叠(speedRange越大越明显)。默认0表示所有弹幕同速、严格不重叠。
DanmakuSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| item | 自定义弹幕内容 |
DanmakuEmits
| 事件 | 描述 | 类型 |
|---|---|---|
| play | 开始播放时触发 | () => void |
| pause | 暂停时触发 | () => void |
| finish | 全部弹幕播放完成时触发(非循环) | () => void |
DanmakuExpose
| 属性 | 描述 | 类型 |
|---|---|---|
| play | 开始播放 | () => void |
| pause | 暂停播放 | () => void |
| resume | 继续播放 | () => void |
| stop | 停止并清空弹幕 | () => void |
| send | 发送一条弹幕 | (item: T) => void |