原生JavaScript实现弹幕组件的示例代码
前言
如今几乎所有的视频网站都有弹幕功能,那么今天我们就自己用原生JavaScript封装一个弹幕类。这个类希望有如下属性和实例方法:
属性
- el容器节点的选择器,容器节点应为绝对定位,设置好宽高
- height每条弹幕的高度
- mode弹幕模式,half则为一半容器高度,top为三分之一,full为占满
- speed弹幕划过屏幕的时间
- gapWidth后一条弹幕与前一条弹幕的距离
方法
- pushData添加弹幕元数据
- addData持续加入弹幕
- start开始调度弹幕
- stop停止弹幕
- restart重新开始弹幕
- clearData清空弹幕
- close关闭
- open重新显示弹幕
PS:有一些自封装的工具函数就不贴出来了,大概知道意思就好
初始化
引入JavaScript文件之后,我们希望如下使用,先采取默认配置。
letbarrage=newBarrage({
el:'#container'
})
参数初始化:
functionBarrage(options){
let{
el,
height,
mode,
speed,
gapWidth,
}=options
this.container=document.querySelector(el)
this.height=height||30
this.speed=speed||15000//2000ms
this.gapWidth=gapWidth||20
this.list=[]
this.mode=mode||'half'
this.boxSize=getBoxSize(this.container)
this.perSpeed=Math.round(this.boxSize.width/this.speed)
this.rows=initRows(this.boxSize,this.mode,this.height)
this.timeoutFuncs=[]
this.indexs=[]
this.idMap=[]
}
先接受好参数然后初始化,下面看看getBoxSize和initRows
functiongetBoxSize(box){
let{
height,
width
}=window.getComputedStyle(box)
return{
height:px2num(height),
width:px2num(width)
}
functionpx2num(str){
returnNumber(str.substring(0,str.indexOf('p')))
}
}
通过getComputedStyleapi计算出盒子的宽高,这里用来计算容器的宽高,之后也会用到。
functioninitRows(box,mode,height){
letdivisor=getDivisor(mode)
rows=Math.ceil(box.height*divisor/height)
returnrows
}
functiongetDivisor(mode){
letdivisor=.5
switch(mode){
case'half':
divisor=.5
break
case'top':
divisor=1/3
break;
case'full':
divisor=1;
break
default:
break;
}
returndivisor
}
根据高度算出弹幕应该有多少行,下面会有地方用到行数。
插入数据
有两种插入数据的方法,一种是添加源数据,一种是持续添加。先来看添加源数据的方法:
this.pushData=function(data){
this.initDom()
if(getType(data)=='[objectObject]'){
//插入单条
this.pushOne(data)
}
if(getType(data)=='[objectArray]'){
//插入多条
this.pushArr(data)
}
}
this.initDom=function(){
if(!document.querySelector(`${el}.barrage-list`)){
//注册dom节点
for(leti=0;i{
if(this.list[index]){
this.list[index]=this.list[index].concat(...item)
}else{
this.list[index]=item
}
})
}
//根据行数把一维的弹幕list切分成rows行的二维数组
functionsliceRowList(rows,list){
letsliceList=[],
perNum=Math.round(list.length/rows)
for(leti=0;i
持续加入数据的方法只是调用了添加源数据的方法,并且开始了调度而已
this.addData=function(data){
this.pushData(data)
this.start()
}
发射弹幕
下面来看看发射弹幕的逻辑
this.start=function(){
//开始调度list
this.dispatchList(this.list)
}
this.dispatchList=function(list){
for(leti=0;i
this.dispatchItem=function(item,i){
//调度过一次的某条弹幕下一次在调度就不需要了
if(!item||this.idMap[item.id]){
return
}
letindex=this.indexs[i]
this.idMap[item.id]=item.id
letdiv=document.createElement('div'),
parent=document.querySelector(`${el}.barrage-list-${i}`),
width,
pastTime
div.innerHTML=item.content
div.className='barrage-item'
parent.appendChild(div)
width=getBoxSize(div).width
div.style=`width:${width}px;display:none`
pastTime=this.computeTime(width)//计算出下一条弹幕应该出现的时间
//弹幕飞一会~
this.run(div)
if(index>this.list[i].length-1){
return
}
letlen=this.timeoutFuncs.length
//记录好定时器,后面清空
this.timeoutFuncs[len]=setTimeout(()=>{
this.indexs[i]=index+1
//递归调用下一条
this.dispatchItem(this.list[i][index+1],i,index+1)
},pastTime);
}
//用css动画,整体还是比较流畅的
this.run=function(item){
item.classList+='running'
item.style.left="left:100%"
item.style.display=''
item.style.animation=`run${this.speed/1000}slinear`
//已完成的打一个标记
setTimeout(()=>{
item.classList+='done'
},this.speed);
}
//根据弹幕的宽度和gapWth,算出下一条弹幕应该出现的时间
this.computeTime=function(width){
letlength=width+this.gapWidth
lettime=Math.round(length/this.boxSize.width*this.speed/2)
returntime
}
动画css具体如下
@keyframesrun{
0%{
left:100%;
}
50%{
left:0
}
100%{
left:-100%;
}
}
.run{
animation-name:run;
}
其余方法
停止
利用动画的paused属性停止
this.stop=function(){
letitems=document.querySelectorAll(`${el}.barrage-item`);
[...items].forEach(item=>{
item.className+='pause'
})
}
.pause{
animation-play-state:paused!important;
}
重新开始
移除pause类即可
this.restart=function(){
letitems=document.querySelectorAll(`${el}.barrage-item`);
[...items].forEach(item=>{
removeClassName(item,'pause')
})
}
打开关闭
做一个显示隐藏的逻辑即可
this.close=function(){
this.container.style.display='none'
}
this.open=function(){
this.container.style.display=''
}
清理弹幕
this.clearData=function(){
//清除list
this.list=[]
//清除dom
document.querySelector(`${el}`).innerHTML=''
//清除timeout
this.timeoutFuncs.forEach(fun=>clearTimeout(fun))
}
最后用一个定时器定时清理过期的弹幕:
setInterval(()=>{
letitems=document.querySelectorAll(`${el}.done`);
[...items].forEach(item=>{
item.parentNode.removeChild(item)
})
},this.speed*5);
最后
感觉这个的实现还是有缺陷的,如果是你设计这么一个类,你会怎么设计呢?
到此这篇关于原生JavaScript实现弹幕组件的示例代码的文章就介绍到这了,更多相关JavaScript弹幕组件内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!