最近公司要求开发一个网页吐槽广场的弹幕,要求鼠标浮动上去可以悬停,可以控制速度,且弹幕不会相互碰撞。可实时添加弹幕,自定义个性化弹幕效果,现在就把源码以及文档附上,希望能够帮到大家。
下载地址
效果
普通效果
自定义网页弹幕效果
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) : boolean
sendSpecial(specialBarrageData: any | Array<any>) : boolean
show() : void
hidden() : void
each(cb: Function) : void
start() : void
stop() : void
setOptions(option: Options) : void
resize() : void
clear() : void
clone(option?: Options) : barrageManager
use(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() : number
getHeight() : number
destory() : void
pause() : void
resume() : void
** 配置项**
barrageManager Options 预览
创建弹幕 manager 的参数
container: HTMLElement
: 弹幕容器,为一个 html 元素limit: number
: 页面上允许渲染的弹幕数量。默认为100
height: number
: 轨道的高。默认为50
rowGap: number
:同一条轨道上两条弹幕的起始间隔,如果小于等于 0,将使弹幕不进行碰撞检测计算。默认为50
isShow: boolean
:默认是否显示。默认为true
capacity: number
:内存中能存放的弹幕数量,超过这个数量,send
方法将返回false
。默认为1024
times: Array<number>
: 弹幕移动时间取值的范围。默认为1024
interval: number
: 渲染频率。默认为2
sdirection: 'left' | 'right'
:弹幕移动方向。默认为right
hooks: 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
: 特殊弹幕的渲染时长。默认为0
direction: 'left' | 'right' | 'none'
: 特殊弹幕的移动方向,为none
时,弹幕将不会移动。默认为none
position: (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.left
style.right
style.opacity
style.display
style.position
style.transform
style.transition
style.visibility
style.pointerEvents
style.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 的计算压力。默认为 2
s
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 将不会被渲染。默认为0
direction: 'left' | 'right' | 'none'
: 特殊弹幕的移动方向,为none
时,弹幕将不会移动。默认为none
position: (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) : void
addSpecial(timestamp: number, specialBarrageData: any | Array<any>) : void
emit(timestamp: number, clearOld?: boolean) : void
emitInterval(timestamp: number, clearOld?: boolean): void
destroy(): 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 系统