最近公司要求开发一个网页吐槽广场的弹幕,要求鼠标浮动上去可以悬停,可以控制速度,且弹幕不会相互碰撞。可实时添加弹幕,自定义个性化弹幕效果,现在就把源码以及文档附上,希望能够帮到大家。
下载地址
效果
普通效果
自定义网页弹幕效果
API 预览
全局 api
create(opts: Options) : barrageManager
// 这将创建一个弹幕 manager,用于管理弹幕
const manager = Danmuku.create({})
barrageManager
属性
runing: boolean: 是否正在运行中length: number: 总弹幕数量,包括未渲染和已经渲染的specialLength: number: 特殊弹幕数量showLength: number: 已经渲染的弹幕数量stashLength: number: 暂存在内存中还没有渲染的弹幕数量containerWidth: number: 容器宽度containerHeight: number: 容器高度
API
send(barrageData: any | Array<any>, hooks?: Object, isForward?: boolean) : booleansendSpecial(specialBarrageData: any | Array<any>) : booleanshow() : voidhidden() : voideach(cb: Function) : voidstart() : voidstop() : voidsetOptions(option: Options) : voidresize() : voidclear() : voidclone(option?: Options) : barrageManageruse(plugin: (...args: any) => any, ...args) : ReturnType<typeof plugin>
Barrage
属性
node: number: 弹幕的HTMLElement元素paused: boolean: 弹幕是否在暂停中duration : number: 弹幕渲染停留时长key: string: 唯一标识符isSpecial: boolean: 是否是特殊弹幕isChangeDuration: boolean: 这个属性普通弹幕才有,判断当前弹幕是否被修正过渲染时长data: any: send 时传入的数据
API
getWidth() : numbergetHeight() : numberdestory() : voidpause() : voidresume() : void
** 配置项**
barrageManager Options 预览
创建弹幕 manager 的参数
container: HTMLElement: 弹幕容器,为一个 html 元素limit: number: 页面上允许渲染的弹幕数量。默认为100height: number: 轨道的高。默认为50rowGap: number:同一条轨道上两条弹幕的起始间隔,如果小于等于 0,将使弹幕不进行碰撞检测计算。默认为50isShow: boolean:默认是否显示。默认为truecapacity: number:内存中能存放的弹幕数量,超过这个数量,send方法将返回false。默认为1024times: Array<number>: 弹幕移动时间取值的范围。默认为1024interval: number: 渲染频率。默认为2sdirection: 'left' | 'right':弹幕移动方向。默认为righthooks: Object:钩子函数,下面会详细介绍。默认为{}
options.hooks
通过定义钩子,能够参与到整个弹幕的创建,渲染和销毁等过程,完全能够自定义样式的样式和行为,这是整个弹幕库强大的扩展性的来源
所有与单个弹幕相关的钩子都以 barrage 开头,下面的钩子函数出现的先后顺序也是执行顺序,也就是说 barrageCreate最先执行,barrageDestroy 最后执行。如果是特殊弹幕的创建,还会调用自身的钩子,在后面的内容会介绍
而 manager 的钩子没有先后顺序之分
-
barrageCreate(barrage: Barrage, node: HTMLElement) -
barrageAppend(barrage: Barrage, node: HTMLElement) -
barrageMove(barrage: Barrage, node: HTMLElement) -
barrageRemove(barrage: Barrage, node: HTMLElement) -
barrageDestroy(barrage: Barrage, node: HTMLElement) -
send(manager: barrageManager, data: any) -
sendSpecial(manager: barrageManager, data: any) -
show(manager: barrageManager) -
hidden(manager: barrageManager) -
start(manager: barrageManager) -
stop(manager: barrageManager) -
resize(manager: barrageManager) -
clear(manager: barrageManager) -
setOptions(manager: barrageManager, options: Options) -
willRender(manager: barrageManager, barrage | barrageData, isSpecial: boolean) : boolean | void -
render(manager: barrageManager) -
ended(manager: barrageManager)
特殊弹幕 Options 预览
hooks: Object: 特殊弹幕创建的钩子。默认为{}duration: number: 特殊弹幕的渲染时长。默认为0direction: 'left' | 'right' | 'none': 特殊弹幕的移动方向,为none时,弹幕将不会移动。默认为noneposition: (barrage: Barrage) => ({x: number, y: number }): 特殊弹幕的位置信息,必须是一个函数,返回一个带有x和y的对象
两种模式
- 如果指定了
rowGap,danmuku默认会进行碰撞检测(弹幕的外边距和边框宽度将不计算在内),你可能会很需要他。但这将导致弹幕的发送不是实时。弹幕会在一个合适的时机进行渲染,这是默认的模式。这样做的目的避免了弹幕重叠和渲染数量过多导致的用户体验变差和内存 cpu 压力过大。但是有时候我们是需要实时响应弹幕 - 将
rowGap设置为一个小于等于的0的数将会取消掉上述的碰撞检测计算。这会让弹幕实时出现。但是你如果设置了limit,还是会受到限制,所有你需要把limit设置为Infinity取消限制,这就是实时响应模式
// 自己封装一个方法
function realTimeResponse () {
const { limit, rowGap } = manager.opts
manager.setOptions({
rowGap: 0,
limit: Infinity,
})
// return 一个切换回去的函数
return () => manager.setOptions({ limit, rowGap })
}
注意事项
由于本弹幕库使用 css 进行动画操作,所以弹幕的 style 属性值有些被占用,除非你很了解他们,否则不应该使用这些 style。以下 css style 被占用
style.leftstyle.rightstyle.opacitystyle.displaystyle.positionstyle.transformstyle.transitionstyle.visibilitystyle.pointerEventsstyle.transitionDuration
如果 conatainer 的 position 没有被设置或者为 static,那么 container 的 position 将会被设置为 relative
BarrageManage 类API
- 以下弹幕实例统一使用
barrage - 以下 BarrageManager 统一使用
manager
属性
runing: boolean
runing 属性标记当前 manager 是否正在渲染中。你可以用他来判断当前的运行状态,并做一些其他的事情
// 如果正在运行中就暂停,否则就启动
manager.runing
? manager.stop()
: manager.start()
length: number
length 属性记录着当前所有的弹幕数量,包括还未渲染的弹幕,已经渲染在容器上的弹幕,和特殊弹幕
// 如果当前弹幕数量到达一定数量,去做一些其他的事情
if (manager.length > 1000) {
...
}
specialLength: number
specialLength 属性记录着当前特殊弹幕的数量
console.log(manager.specialLength)
showLength: number
showLength 属性记录这个当前渲染在容器中的弹幕数量
// 每渲染一次,就打印一次现在容器中渲染的弹幕数量
setInterval(() => {
console.log(managere.showLength)
}, manager.opts.interval)
stashLength: number
stashLength 属性记录着当前还未渲染的弹幕数量,但是不包括特殊弹幕,意思是这个属性等于 managere.length - manager.showLength - managere.specialLength
// 每渲染一次,就打印一次还为渲染的普通弹幕数量
setInterval(() => {
console.log(managere.stashLength)
}, manager.opts.interval)
containerWidth: number 和 containerHeight: number
containerWidth 属性记录着当前容器的宽度,它是动态的,当调用 manager.resize 方法的时候,containerWidth 也将随之变化。你可能会在发送特殊弹幕的时候用到他
console.log(manager.containerWidth)
console.log(manager.containerHeight)
** API**
send(barrageData: any | Array<any>, hooks?: Hooks, isForward?: boolean) : boolean
send 方法将发送一个普通弹幕或者一批普通弹幕,所以如果传入的是一个数组,他将判断是多个弹幕。send 方法将不会去检测传入的参数,所以即使传入的为 undefined,他同样将创建一个弹幕。当发送弹幕失败时,他将返回 false,同样的,发送成功将返回 true。send 方法调用时传入的参数将保持在弹幕实例中,你可以通过 barrage.data 拿到他。send 方法调用时会同步触发 send 钩子
第二个参数为 hooks,为当前弹幕的 hooks,如果是一个数组则是多个弹幕共用一套
第三个参数为 isForward, 如果为 true 将从头入栈,优先渲染
// 这将发送三个普通弹幕,他会在合适的时机渲染到容器中
const manager = Danmuku.create({
hooks: {
send (manager, data) {
console.log(data)
},
barrageCreate (barrage, node) {
if (!barrage.isSpecial) {
console.log(barrage.data) // -> { content: 'one' }
// 设置弹幕内容和样式
node.textContent = barrage.data.content
node.classList.add('barrage-style')
}
}
}
})
manager.send({ content: 'one' })
manager.send([
{ content: 'two' },
{ content: 'two' },
])
sendSpecial(specialBarrageData: any | Array<any>) : boolean
sendSpecial 方法用于发送特殊弹幕。特殊弹幕的特性与差异,请看这里。sendSpecial 与 send 很相似,他同样接受一个或多个弹幕,返回一个 boolean 值标识是否发送成功。唯一不同的是,他将不参与碰撞计算,所以如果容器的渲染数量没有到达临界值,他将立即渲染在视图上。sendSpecial 方法调用时会同步触发 sendSpecial 钩子
// 下面将发送一个特殊的弹幕,渲染在左上角
// 最后会先打印 1,再打印 2,这也代表弹幕自身的 hook 先于 manager 的 hook 执行
const manager = Danmuku.create({
hooks: {
sendSpecial (manager, data) {
console.log(data)
},
barrageCreate (barrage, node) {
if (barrage.isSpecial) {
console.log(2)
}
}
}
})
// options 的介绍请看 barrage 相关介绍
manager.sendSpecial({
data: 'chentao',
direction: 'none',
diration: 5,
position (barrage) {
// 位置信息最后将通过作用于 node 的 css 样式生效,单位将统一设置为 px
return { x: 0, y: 0 }
},
hooks: {
create (barrage, node) {
console.log(1)
console.log(barrage.data) // -> 'chentao'
// 设置弹幕内容和样式
node.textContent = barrage.data
node.classList.add('barrage-style')
}
}
})
each(cb: Function) : void
each 方法将遍历所有渲染在容器中的弹幕。这允许你对所有渲染中的弹幕(包括特殊弹幕)进行操作。下面的 show 和 hidden 方法都是使用的此方法
manager.each(barrage => {
if (barrage.isSpecial) {
...
} else {
...
}
})
hidden() : void
hidden 方法将隐藏所有渲染的视图弹幕,并将接下来渲染的弹幕也隐藏。他作用于普遍弹幕和特殊弹幕。他将调用弹幕的 hidden 和 全局 hidden 钩子
manager.hidden()
show() : void
show 方法将显示所有的渲染视图弹幕,同上。他将调用弹幕的 show 和 全局 show 钩子
manager.show()
start() : void
start 方法将开始轮询的从缓存的弹幕池中获取一部分弹幕,渲染在视图上。默认是开启的,也可以用于恢复暂停了的 manager。他将调用 start 钩子
manager.start()
stop() : void
stop 方法将停止 manager 的轮询,不会再从缓存区获取弹幕渲染。他将调用 stop 钩子
manager.stop()
setOptions(option: Options) : void
setOptions 方法将充值 manager 的 options。他将调用 setOptions 钩子
// 扩展内存区的大小,并充值弹幕渲染时间取值范围和轨道高度
manager.setOptions({
height: 20,
times: [2, 10]
capacity: 1000,
})
resize() : void
resize 方法将重新计算容器轨道。他适用于容器缩放时或者轨道高度变化时,重置 manager 内部的计算参数。例如用来做半屏。他将调用 resize 钩子
const container = document.getElementById('container')
container.style.height = '50%'
manager.resize()
clear() : void
clear 方法将清空所有在视图中渲染的弹幕(包括特殊弹幕)和缓存区的弹幕。并停止 manager 的轮询渲染。这将会很好的缓解内存压力。他将调用 stop 和 clear 钩子
// 这将会清空所有弹幕,然后重新开始
manager.clear()
manager.start()
clone(option?: Options) : barrageManager
clone 方法将复制当前 manager 的参数,返回一个全新的 manager。如果传入了 option,他将会与当前实例的 option 进行合并
const newManager = manager.clone()
use(plugin: (...args: any) => any, ...args) : ReturnType<typeof plugin>
use 方法用于添加插件
function plugin(opts) {
console.log(opts) // { a: 1 }
return 'plugin'
}
const pm = manager.use(plugin, { a: 1 })
console.log(pm) // 'plugin'
** Options**
limit: number
limit 将限制容器实时渲染的最大数量。如果当前容器视图中渲染的数量已经达到此配置设置的数,将不会有新的弹幕进行渲染,直到有渲染中的弹幕销毁了。默认值为 100
height: number
height 属性为轨道的高度,普通弹幕的将会随机出现在一条轨道上。轨道数目为 containerHeight / height
rowGap: number
rowGap 为同一轨道相邻两个弹幕的间距,只有前一个弹幕的移动距离大于这个值,当前轨道的下一个弹幕才被允许出现,但不代表两个相邻的弹幕永远都处于这个间距,他们的间距依赖于他们运动的时间。如果 rowGap 是一个大于 0 的值,同一个轨道相邻弹幕将进行碰撞计算,即使速度不一样,他们也不会再容器视图区域进行碰撞(由于是基于 css 动画,所以可能会有一点点的误差)。默认值为 50
- 如果 rowGap 为 20
- 前一个弹幕移动 10
- 下一个弹幕将不会出现
isShow: boolean
isShow 设置是否默认显示。默认为 true
capacity: number
capacity 限制了缓存区能够缓存的弹幕数量,如果大于这个数,manager.send 和 manager.sendSpecial 方法将不能够发送弹幕(返回 false)。默认 1024
times: Array<number>
times 设置了普通弹幕的渲染时间范围。普通弹幕出现的时间将会从 times 区间内随机取一个值。默认为 [5, 10]
interval: number
interval 设置了 manager 的渲染频率,单位为 s。如果过快,将会加大 cup 的计算压力。默认为 2s
direction: 'left' | 'right'
direction 设置了普遍弹幕的移动方向,规定了弹幕是从左边出来还是右边出来,默认为 right
hooks: Object
hooks 为钩子函数的集合。默认为 {}
Hooks
barrageCreate(barrage: Barrage, node: HTMLElement)
barrageCreate 将在弹幕(普通和特殊)的 HTMLElement 创建之后调用,你可以在此对弹幕进行自定义
Danmuku.create({
hooks: {
barrageCreate (barrage, node) {
// 对弹幕进行一些自定义的行为
...
}
}
})
barrageAppend(barrage: Barrage, node: HTMLElement)
barrageCreate 将在弹幕(普通和特殊)的 HTMLElement 添加到视图之后调用
barrageMove(barrage: Barrage, node: HTMLElement)
barrageCreate 将在弹幕(普通和特殊)的 HTMLElement 开始移动时调用
barrageRemove(barrage: Barrage, node: HTMLElement)
barrageRemove 将在弹幕(普通和特殊)的 HTMLElement 从视图中删除之后调用
barrageDestroy(barrage: Barrage, node: HTMLElement)
barrageRemove 将在弹幕(普通和特殊)销毁时调用。调用 barrage.destroy 也会触发此钩子
send(manager: barrageManager, data: any)
send 钩子将在 manager.send 调用时触发
sendSpecial(manager: barrageManager, data: any)
sendSpecial 钩子将在 manager.sendSpecial 调用时触发
show(manager: barrageManager)
show 钩子将在 manager.show 调用时触发
hidden(manager: barrageManager)
hidden 钩子将在 manager.hidden 调用时触发
start(manager: barrageManager)
start 钩子将在 manager.start 调用时触发
stop(manager: barrageManager)
stop 钩子将在 manager.stop 调用时触发
resize(manager: barrageManager)
resize 钩子将在 manager.resize 调用时触发
clear(manager: barrageManager)
clear 钩子将在 manager.clear 调用时触发
setOptions(manager: barrageManager, options: Options)
setOptions 钩子将在 manager.setOptions 调用时触发
willRender(manager: barrageManager, barrage | barrageData, isSpecial: boolean) : boolean | void
willRender 钩子将在 manager 每次渲染之前(包括特殊弹幕)触发,return false 将会阻止当前这条弹幕渲染
render(manager: barrageManager)
render 钩子将在 manager 每次渲染的时候触发(特殊弹幕的渲染将不会触发)
capacityWarning(manager: barrageManager)
capacityWarning 将在弹幕数量超过 barrageManager.opts.capacity 时触发
ended(manager: barrageManager)
如果发现 manager.length 等于 0 的时候,将会调用此钩子。但是不保证 manager.length 永远为 0。所以 ended 钩子将会有可能被多次调用
Barrage类API介绍
- 以下弹幕实例统一使用
barrage - 以下 BarrageManager 统一使用
manager
** 属性**
node: number: 弹幕的HTMLElement元素paused: boolean: 弹幕是否在暂停中duration : number: 弹幕渲染停留时长key: string: 唯一标识符isSpecial: boolean: 是否是特殊弹幕isChangeDuration: boolean: 这个属性普通弹幕才有,判断当前弹幕是否被修正过渲染时长data: any:send或sendSpecial调用时传入的数据
data
- data 具有特殊性,如果是普通弹幕,data 属性就是你传入的 option,manager 不会做任何更改
- 如果是特殊弹幕,data 属性为 option.data
** API**
getWidth() : number
getWidth 方法将会返回当前弹幕的元素的宽度
getHeight() : number
getHeight 方法将会返回当前弹幕的元素的高度
destory() : void
destory 方法将会销毁当前弹幕,会立即从视图和内存中删除
pause() : void
pause 方法暂定当前弹幕的移动
resume() : void
resume 方法恢复当前弹幕的移动
demo
// 所有的弹幕鼠标进入暂停,移除继续移动,点击销毁
Danmuku.create({
hooks: {
barrageApeed (barrage, node) {
node.onmouseenter = e => barrage.pause()
node.onmouseleave = e => barrage.resume()
node.onclick = e => barrage.destroy()
}
}
})
** 特殊弹幕的 Options** 特殊弹幕与普通弹幕的区别在于,特殊弹幕允许自定义弹幕的位置和渲染时长。由于可以自定义弹幕位置,导致特殊弹幕将不参与碰撞检测计算。这意味着特殊弹幕会相互重叠和与普通弹幕重叠。如果需要碰撞检测,则需要开发者自己手动计算。
特殊弹幕出现的初衷是允许开发者高度自定义弹幕,由于普通弹幕已经足够灵活和强大,所以特殊弹幕的很多计算与限制都被取消了。而且特殊弹幕只能是实时响应(发送后,如果页面渲染数量在 manager 的 limit 允许的范围内,则会立即渲染)
hooks: Object: 特殊弹幕创建的钩子。默认为{}duration: number: 特殊弹幕的渲染时长,时间为 0 将不会被渲染。默认为0direction: 'left' | 'right' | 'none': 特殊弹幕的移动方向,为none时,弹幕将不会移动。默认为noneposition: (barrage: Barrage) => ({x: number, y: number }): 特殊弹幕的位置信息,必须是一个函数,返回一个带有x和y的对象,默认都是返回0。你可以通过barrage的api来计算位置信息,例如以下 demo
demo
// 这将使得整个特殊弹幕出现在容器居中的位置,而且弹幕的背景色为红色
manager.sendSpecial({
duration: 5,
direction: 'right',
position (barrage) {
return {
x: (manager.containerWidth - barrage.getWidth()) / 2,
y: (manager.containerHeight- barrage.getHeight()) / 2
}
},
hooks: {
create (barrage) {
barrage.node.style.background = 'red'
}
}
})
弹幕的 hooks 弹幕有自己的 hooks,这与 manager 的 hooks 并不冲突,而且弹幕的 hooks 的优先级比 manager 的 hooks 高(优先调用)。
普通弹幕
manager.send({ content: 'one' }, {
create (barrage, node) {
if (!barrage.isSpecial) {
console.log(barrage.data) // -> { content: 'one' }
// 设置弹幕内容和样式
node.textContent = barrage.data.content
node.classList.add('barrage-style')
}
},
append (barrage, node) {
...
},
move (barrage, node) {
...
},
remove (barrage, node) {
...
},
destroy (barrage, node) {
...
},
show (barrage, node) {
...
},
hidden (barrage, node) {
...
},
})
特殊弹幕
const data = {}
manager.sendSpecial({
data, // 特殊弹幕与普通弹幕在 data 上的行为不一样,特殊弹幕的 data 需要手动传入
duration: 5,
direction: 'right',
position: () => ({ x: 100, y: 100 }),
hooks: {
create (barrage, node) {
node.style.background = 'red'
console.log(barrage.data === data) // true
},
append (barrage, node) {
...
},
move (barrage, node) {
...
},
remove (barrage, node) {
...
},
destroy (barrage, node) {
...
},
show (barrage, node) {
...
},
hidden (barrage, node) {
...
},
}
})
时间轴
当需要弹幕与视频结合起来使用时,就需要时间轴这个插件了
API
add(timestamp: number, barrageData: any | Array<any>, hooks?: Object, isForward?: boolean) : voidaddSpecial(timestamp: number, specialBarrageData: any | Array<any>) : voidemit(timestamp: number, clearOld?: boolean) : voidemitInterval(timestamp: number, clearOld?: boolean): voiddestroy(): void
demo
const manager = Danmuku.create({})
// forceRender 的作用是开启碰撞检测,当弹幕数量超过视图容器的阈值时,将取消碰撞检测,因为要保证弹幕的实时性
// 当弹幕数量低于视图容器的阈值时,又会重新开启碰撞检测,如果不开启将会导致弹幕不是实时性的
const timeline = manager.use(Danmuku.Timeline, { forceRender: true })
// 添加一个 10s 的弹幕
timeline.add(10, 'barrageText', {
// 弹幕渲染到页面上时
append (barrage, node) {
node.onmouseenter = e => {
barrage.pause()
}
node.onmouseleave = e => {
barrage.resume()
}
node.onclick = e => {
barrage.destroy()
}
},
})
// 触发 10s 这个时间点的弹幕
timeline.emit(10)
// 如果 emit 时, clearOld 为 ture,将会在触发后清空当前时间点的弹幕数据
timeline.emit(10, true)
** Tips**
emitInterval方法与emit方法的区别是,你触发时,不允许连续触发相同的时间,也就是说你要触发同一个时间点的弹幕,得间隔的去触发timeline.add与manager.send方法参数一样,唯一的区别是多一个timestamp参数。同理,timeline.addSpecial和manager.sendSpecial也是一样的- 可以看到时间轴这个插件就是一个简易的 eventbus 系统